diff --git a/.devenv/README.md b/.devenv/README.md index d56e327dd0..f56b1e09d1 100644 --- a/.devenv/README.md +++ b/.devenv/README.md @@ -52,7 +52,7 @@ built fresh from `shared/codex.toml` + `templates/codex.toml` at launch. `manage.sh`. * **`mcp/`** (Claude Code, opencode) is the result of merging `shared/` with the port-substituted `templates/`. `manage.sh` writes these on every - `run-devenv-agentic` pass. Gitignored, dedicated paths with no developer + `run-devenv --agentic` pass. Gitignored, dedicated paths with no developer content — never edit by hand, your edits will be overwritten on the next reconcile. * **`.vscode/mcp.json`** is the same merge, but written to the path VS Code diff --git a/.opencode/skills/create-issue/SKILL.md b/.opencode/skills/create-issue/SKILL.md new file mode 100644 index 0000000000..90a0fc3b75 --- /dev/null +++ b/.opencode/skills/create-issue/SKILL.md @@ -0,0 +1,27 @@ +--- +name: create-issue +description: Create or update GitHub issues (from PR, from draft body, retitle existing). Routes to the canonical flow in `mem:workflow/creating-issues`. +--- + +# Skill: create-issue + +Entry point for all GitHub issue work. All rules (title derivation, metadata, +body templates, Issue Type IDs), all flows, and all `gh` / GraphQL commands +live in `mem:workflow/creating-issues` (file: +`.serena/memories/workflow/creating-issues.md`). This skill routes to the +right flow. + +## When to Use + +- **Create from PR** — PR exists; the issue is the changelog/release unit, + the PR is the implementation. Issue = WHAT, PR = HOW. + → memory section **Creating Issues from PRs** +- **Create from draft body** — Taiga story, user report, discussion; no PR + yet. + → memory section **Creating Issues from Draft Body** +- **Retitle existing issue** — current title is vague, prefixed, or stale. + → memory section **Retitling an Existing Issue** + +Everything else (title derivation, metadata policy, body templates, Issue +Type IDs, create/verify/cleanup commands) lives in the memory — go to the +matching section there. diff --git a/.opencode/skills/gh-issue-from-pr/SKILL.md b/.opencode/skills/gh-issue-from-pr/SKILL.md deleted file mode 100644 index bb7fe10cf9..0000000000 --- a/.opencode/skills/gh-issue-from-pr/SKILL.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -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. Use the `issue-title` skill to generate this. | -| **Labels** | PR labels | Copy `community contribution` if present. Skip `bug` and `enhancement` (redundant with Issue Type). 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 "community contribution" \ # only if PR has this label - --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 `Closes #<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\nCloses #<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("Closes #<ISSUE_NUMBER>")' -``` - -**Note:** If the PR is already merged, `Closes` 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 | -|--------|-----------| -| `community contribution` | `community contribution` | -| `bug`, `enhancement` | *(skip — redundant with Issue Type)* | -| `backport candidate` | *(skip — workflow label)* | -| `team-qa` | *(skip — workflow label)* | - -## 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** — `Closes #<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/issue-title/SKILL.md b/.opencode/skills/issue-title/SKILL.md deleted file mode 100644 index 6cd8c14fc5..0000000000 --- a/.opencode/skills/issue-title/SKILL.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -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 <NUMBER> --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 <NUMBER> --repo penpot/penpot --title "<NEW TITLE>" -``` - -**If creating a new issue:** - -```bash -gh issue create --repo penpot/penpot --title "<NEW TITLE>" --body "<BODY>" -``` - -### 4. Confirm - -For updates, the command returns the issue URL. Verify by optionally fetching -again: - -```bash -gh issue view <NUMBER> --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/backend/core.md b/.serena/memories/backend/core.md index 34a272dedc..d815f69070 100644 --- a/.serena/memories/backend/core.md +++ b/.serena/memories/backend/core.md @@ -39,6 +39,9 @@ Backend RPC command areas without focused memories include access tokens, binfil Database migrations live in `backend/src/app/migrations/`; pure SQL migrations are under `backend/src/app/migrations/sql/`. SQL filenames conventionally start with a sequence and verb/table description, e.g. `0026-mod-profile-table-add-is-active-field`. Applied migrations are tracked in the `migrations` table. +For interactive PostgreSQL access with correct dev defaults, use `tools/psql`; to dump +the current DDL schema, use `tools/db-schema` (see `mem:tools/psql`). + For deeper details on transaction semantics, advisory locks, Transit vs JSON helpers, and dev/test DB URLs: `mem:backend/rpc-db-worker-subtleties`. ## Background tasks diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 23881c6f89..d0f83b656b 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -9,7 +9,11 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. # Development workflow -- Commit only when explicitly asked. Commit/PR format + changelog: `mem:workflow/creating-commits`, `mem:workflow/creating-prs`. Issue creation (titles, labels, body templates, Issue Types): `mem:workflow/creating-issues`. +- Commit/PR/issue creation is **on explicit request only**. Before any of these actions, read the relevant memory — don't infer format from prior examples: + - 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) +- **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. - You have access to the GitHub CLI `gh` or corresponding MCP tools. - Issues are also managed on Taiga. Read issues using the `read_taiga_issue` tool. - Before writing code, analyze the task in depth and describe your plan. If the task is complex, break it down into atomic steps. @@ -45,7 +49,8 @@ module. You can read it from `mem:<MODULE>/core` tmux session lifecycle, MinIO provisioning, or anything in `manage.sh`'s `*-devenv` commands, read `mem:devenv/core`. - `tools/` contains standalone dev utilities: `nrepl-eval.mjs` (backend REPL eval), - `paren-repair.bb` (delimiter-error fixer, see `mem:tools/paren-repair`), and + `paren-repair.bb` (delimiter-error fixer, see `mem:tools/paren-repair`), + `psql` / `db-schema` (PostgreSQL client and schema dump wrappers, see `mem:tools/psql`), and `taiga.py` / `gh.py` (issue management helpers). - `experiments/` contains standalone experimental HTML/JS/scripts; treat it as non-core unless the user explicitly asks about it. - `sample_media/` contains sample image/icon media and config used as fixtures/demo material; do not infer app behavior from it. diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index e612a5f923..82e4e704f3 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -25,7 +25,7 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par ## Worker policy -Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. ws0 must be running whenever any ws1+ is running, and is the last instance to stop — `run-devenv-agentic --ws N` (N≥1) auto-starts ws0 first; `stop-devenv` refuses to stop ws0 while any ws1+ is up. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`. +Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. ws0 must be running whenever any ws1+ is running, and is the last instance to stop — `run-devenv --agentic --ws N` (N≥1) auto-starts ws0 first; `stop-devenv` refuses to stop ws0 while any ws1+ is up. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`. ## Port layout @@ -44,13 +44,13 @@ Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin d ## Tmux + MCP routing -`docker/devenv/files/start-tmux.sh` is session-level idempotent. Reads `PENPOT_TMUX_ATTACH`. If the session exists it attaches or exits; otherwise creates 4 base windows (frontend watch / storybook / exporter / backend) plus `mcp` (when `enable-mcp` in `PENPOT_FLAGS`) and `serena` (when `SERENA_ENABLED=true`). `run-devenv-agentic` always sets both env vars. The legacy `run-devenv` alias doesn't, hence its 4-window-only session. To switch from a legacy session to agentic, `stop-devenv` then `run-devenv-agentic` — the conditional windows are only added at session create time. +`docker/devenv/files/start-tmux.sh` is session-level idempotent. Reads `PENPOT_TMUX_ATTACH`. If the session exists it attaches or exits; otherwise creates 4 base windows (frontend watch / storybook / exporter / backend) plus `mcp` (when `enable-mcp` in `PENPOT_FLAGS`) and `serena` (when `SERENA_ENABLED=true`). `run-devenv --agentic` always sets both env vars. The legacy `run-devenv` alias doesn't, hence its 4-window-only session. To switch from a legacy session to agentic, `stop-devenv` then `run-devenv --agentic` — the conditional windows are only added at session create time. MCP plugin routing is same-origin: frontend uses `<public-uri>/mcp/ws`, per-instance nginx proxies to MCP port 4401 in-container. For the plugin↔MCP server wiring (how the browser plugin discovers the URL, the in-memory connection registry, why DB-mediated routing isn't needed), see `mem:mcp/core`. ## Workspace orchestration (ws1+) -Workspace directories are user-maintained at `${PENPOT_WORKSPACES_DIR}/wsN`. `run-devenv-agentic --ws i` syncs only when `--sync` is passed, with one exception: if the workspace directory is missing on first use, sync runs implicitly to seed it. +Workspace directories are user-maintained at `${PENPOT_WORKSPACES_DIR}/wsN`. `run-devenv --agentic --ws i` syncs only when `--sync` is passed, with one exception: if the workspace directory is missing on first use, sync runs implicitly to seed it. `sync-workspace wsN`: 1. `assert-clean-git-state` — refuses on `.git/{rebase-apply,rebase-merge,MERGE_HEAD,CHERRY_PICK_HEAD,index.lock}`. No `--sync-force` escape. @@ -63,7 +63,7 @@ No `--delete` on the working-tree pass: gitignored caches in the workspace survi ## CLI surface -- `run-devenv-agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet). Auto-starts ws0 first when the target is ws1+ and ws0 is not yet up. +- `run-devenv --agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet). Auto-starts ws0 first when the target is ws1+ and ws0 is not yet up. - `stop-devenv [--ws main|0|wsN|N] [--all]`: stop instances. Flags mutually exclusive. `--ws N` (N≥1) stops just that workspace. `--ws 0` or no flag stops ws0 + shared infra, refused while any ws1+ is running. `--all` stops every ws highest-first then ws0, then infra. - `run-devenv`: legacy alias, ws0 non-agentic attached. - `attach-devenv [--ws main|0|wsN|N]`: pure attach. Fails fast if instance/session missing. diff --git a/.serena/memories/tools/psql.md b/.serena/memories/tools/psql.md new file mode 100644 index 0000000000..c502b4c1e9 --- /dev/null +++ b/.serena/memories/tools/psql.md @@ -0,0 +1,51 @@ +# PostgreSQL client wrapper + +`tools/psql` is a wrapper around the `psql` command with defaults preconfigured +for the Penpot development environment. + +## When to use + +- Running SQL queries against the dev database (`penpot`) or test database + (`penpot_test`). +- Inspecting table structures, running migrations manually, or debugging + database state. +- Any time you need PostgreSQL access and want the correct host/user/password + without typing them each time. + +## How to use + +```bash +# Interactive session (penpot database) +tools/psql + +# Interactive session (penpot_test database) +tools/psql --test + +# Inline query (penpot) +tools/psql -c "SELECT 1" + +# Inline query (penpot_test) +tools/psql --test -c "SELECT 1" + +# Override defaults +tools/psql -h other-host -U other-user -d other-db + +# Pipe SQL from a file +tools/psql -f some-query.sql +``` + +All standard `psql` flags are passed through after the wrapper's own flags. + +## Defaults + +| Setting | Default | Env override | +|----------|-----------|---------------------| +| Host | `postgres` | `PENPOT_DB_HOST` | +| User | `penpot` | `PENPOT_DB_USER` | +| Password | `penpot` | `PENPOT_DB_PASSWORD` | +| Database | `penpot` | `PENPOT_DB_NAME` | + +## See also + +`tools/db-schema` — a companion script that dumps the current DDL schema +using `pg_dump --schema-only`, with the same defaults and `--test` flag. diff --git a/.serena/memories/workflow/creating-issues.md b/.serena/memories/workflow/creating-issues.md index 42a07f7887..9f3831b165 100644 --- a/.serena/memories/workflow/creating-issues.md +++ b/.serena/memories/workflow/creating-issues.md @@ -79,6 +79,8 @@ Write the body to a temp file to avoid shell quoting issues: <version> ``` +Note: do not soft-wrap paragraphs in the body. Each paragraph is a single line in the source; newlines are reserved for structural breaks (section headers, list items, code-block fences, blank-line separators). List items stay on a single line each. GitHub renders single-line paragraphs correctly, and wrapping makes diffs noisy on every small wording change. Same rule applies to PR bodies. + ## Creating the Issue ```bash @@ -155,6 +157,199 @@ query { repository(owner: "penpot", name: "penpot") { rm -f /tmp/issue-body.md ``` +## Creating Issues from PRs + +Used when the project board needs an issue as the primary changelog/release +unit and the PR describes the implementation. The issue is the **WHAT** +(user-facing), the PR is the **HOW** (implementation). + +### Fetch the PR + +```bash +gh pr view <PR_NUMBER> --repo penpot/penpot \ + --json title,body,author,labels,baseRefName,mergedAt,state,milestone +``` + +Identify: + +- **WHAT** — user-facing problem or feature. Goes into the issue. + Describe symptoms and impact, not internal mechanisms. +- **HOW** — implementation details. These belong in the PR, not the issue. + +### Determine metadata + +- **Title:** rewrite from user perspective using the title rules above. Strip + leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on + observable behavior. +- **Labels:** copy `community contribution` if present on the PR. +- **Milestone:** always copy what's on the PR. + + ```bash + gh pr view <PR_NUMBER> --json milestone --jq '.milestone.title' + ``` + + If the PR has no milestone, create the issue without one. +- **Project:** `Main`. +- **Body:** extract the user-facing section (steps to reproduce or feature + description). Omit internal details. Use the templates above. +- **Issue Type:** use the mapping table above (also handles `:bug:` / + `:sparkles:` / `:tada:` title prefixes). + +### Create the issue + +```bash +cat > /tmp/issue-body.md << 'ISSUE_BODY' +<body content here> +ISSUE_BODY + +gh issue create \ + --repo penpot/penpot \ + --title "<Title>" \ + --label "community contribution" \ # only if PR has this label + --milestone "<milestone>" \ + --project "Main" \ + --body-file /tmp/issue-body.md +``` + +Output: `https://github.com/penpot/penpot/issues/<NUMBER>` + +### Assign to the PR author + +```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" +``` + +### Set Issue Type and verify + +See the **Setting the Issue Type** and **Verification** sections above — the +GraphQL mutations and `gh issue view` calls are identical regardless of how +the issue was sourced. + +### Link the PR to the issue + +Append `Closes #<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\nCloses #<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("Closes #<ISSUE_NUMBER>")' +``` + +**Note:** If the PR is already merged, `Closes` 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. + +### Clean up + +```bash +rm -f /tmp/issue-body.md /tmp/pr-body.md +``` + +### Rules for this flow + +- **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. +- **Don't put implementation details in the issue body** — the issue is for + users, QA, and changelog readers. + +## Creating Issues from Draft Body + +Used when the user provides a draft body from elsewhere (Taiga story, user +report, discussion transcript) and there is no PR yet. + +### Get the body + +Read the draft body from wherever it was provided. If the user gives only a +vague one-liner, ask them to expand it (steps to reproduce, expected vs. +actual, use case) before proceeding. + +### Derive the title + +Apply the title rules in the **Title Derivation** section above. Distinguish +bug vs. feature from the body content: + +- Steps to reproduce + expected vs. actual → bug +- "would be nice", "add support for", "allow users to" → feature / enhancement + +### Choose a body template + +Use the bug or enhancement template from the **Issue Body Template** section +above. Fill in placeholders with the user-provided details. If the body +doesn't fit either, ask the user which template to use. + +### Determine metadata + +- **Project:** `Main` (always). +- **Milestone:** ask the user if not obvious; otherwise omit. +- **Labels:** usually none for new user-reported issues. Add + `community contribution` if the user is a non-team contributor. +- **Issue Type:** use the mapping table above (bug description → Bug; feature + request → Enhancement or Feature). + +### Create the issue + +```bash +cat > /tmp/issue-body.md << 'ISSUE_BODY' +<body content here> +ISSUE_BODY + +gh issue create \ + --repo penpot/penpot \ + --title "<Title>" \ + --label "community contribution" \ # only if applicable + --milestone "<milestone>" \ # only if provided + --project "Main" \ + --body-file /tmp/issue-body.md +``` + +### Set Issue Type and verify + +Same GraphQL mutation and `gh issue view` commands as in the +**Setting the Issue Type** and **Verification** sections above. + +### Clean up + +```bash +rm -f /tmp/issue-body.md +``` + +## Retitling an Existing Issue + +Used when an issue's current title is vague, prefixed, or no longer matches +the body (e.g. `[PENPOT FEEDBACK]: ...`, `feature: ...`). + +### Fetch the issue + +```bash +gh issue view <NUMBER> --repo penpot/penpot --json title,body +``` + +### Derive a new title + +Read the body (not the current title) and apply the title rules in the +**Title Derivation** section above. + +### Apply the new title + +```bash +gh issue edit <NUMBER> --repo penpot/penpot --title "<NEW TITLE>" +``` + +### Confirm + +```bash +gh issue view <NUMBER> --repo penpot/penpot --json title +``` + ## See Also -- Creating issues **from PRs** (separating WHAT from HOW): `mem:workflow/creating-prs` +- End-to-end orchestration entry point: the `create-issue` skill at + `.opencode/skills/create-issue/SKILL.md`. The skill is a thin entry + point; this memory is the canonical home for all issue-creation rules. diff --git a/AGENTS.md b/AGENTS.md index 08103521e0..8c3e6ba431 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,20 @@ # AI AGENT GUIDE +## Hard rules (always apply — no exceptions) + +- **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 to surface the + agent's work (e.g. force-push after an amend), state this in the response and + wait for the user to push. Do not change the remote URL, do not switch SSH↔HTTPS. +- **Never amend a commit that has been pushed** unless the user explicitly asks. + If the user pushes, treat that commit as final from the agent's side. +- **Read the workflow memory BEFORE the corresponding action**: + - Before `git commit` → `mem:workflow/creating-commits` (commit format, AI-assisted-by trailer) + - Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, Issue Type) + - Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, AI note) + Don't infer format from the title of a previous commit/issue/PR — the memory + is the source of truth. + ## CRITICAL: Read module memories BEFORE writing any code Do this **before planning, before coding, before touching any file**: diff --git a/CHANGES.md b/CHANGES.md index b6c55f9ea3..12566c36d1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -64,6 +64,7 @@ - Add resource limits to font processing child processes [#10234](https://github.com/penpot/penpot/issues/10234) (PR: [#10274](https://github.com/penpot/penpot/pull/10274)) - Add color variants and positioning to selection size badge (by @bittoby) [#10258](https://github.com/penpot/penpot/issues/10258) (PR: [#9210](https://github.com/penpot/penpot/pull/9210)) - Use hard reload for render engine switching in the workspace menu [#10441](https://github.com/penpot/penpot/issues/10441) (PR: [#10444](https://github.com/penpot/penpot/pull/10444)) +- Rotate size badge when shape is rotated [#10386](https://github.com/penpot/penpot/issues/10386) (PR: [#10393](https://github.com/penpot/penpot/pull/10393)) ### :bug: Bugs fixed @@ -171,6 +172,14 @@ - Fix emojis without fill but with outer stroke not being rendered [#10473](https://github.com/penpot/penpot/issues/10473) (PR: [#10519](https://github.com/penpot/penpot/pull/10519)) - Fix texts with lots of emojis not being rendered across multiple tiles [#10474](https://github.com/penpot/penpot/issues/10474) (PR: [#10504](https://github.com/penpot/penpot/pull/10504)) - Fix artifacts in texts with inner strokes [#10476](https://github.com/penpot/penpot/issues/10476) (PR: [#10509](https://github.com/penpot/penpot/pull/10509)) +- Fix open overlay position control showing only center icon instead of full grid [#10176](https://github.com/penpot/penpot/issues/10176) (PR: [#10512](https://github.com/penpot/penpot/pull/10512)) +- Fix wrong text color in selection size badge [#10256](https://github.com/penpot/penpot/issues/10256) (PR: [#10393](https://github.com/penpot/penpot/pull/10393)) +- Fix several issues with plugins [#10394](https://github.com/penpot/penpot/issues/10394) +- Fix pixel grid rendered on top of the rulers [#10426](https://github.com/penpot/penpot/issues/10426) (PR: [#10430](https://github.com/penpot/penpot/pull/10430)) +- Fix double-clicking text without fills creating a black file [#10472](https://github.com/penpot/penpot/issues/10472) (PR: [#10483](https://github.com/penpot/penpot/pull/10483)) +- Fix SVG raw caching prevented by unnecessary Cow wrapping [#10488](https://github.com/penpot/penpot/issues/10488) (PR: [#10492](https://github.com/penpot/penpot/pull/10492)) +- Add component reset operation to plugin API [#10561](https://github.com/penpot/penpot/issues/10561) (PR: [#10533](https://github.com/penpot/penpot/pull/10533)) +- Fix blur menu alignment in Firefox [#10576](https://github.com/penpot/penpot/issues/10576) (PR: [#10575](https://github.com/penpot/penpot/pull/10575)) ## 2.16.2 diff --git a/docs/technical-guide/developer/agentic-devenv.md b/docs/technical-guide/developer/agentic-devenv.md index ed362ce385..8d943dcd93 100644 --- a/docs/technical-guide/developer/agentic-devenv.md +++ b/docs/technical-guide/developer/agentic-devenv.md @@ -1,30 +1,22 @@ --- -title: 3.11. Agentic Development Environment +title: 3.11. Agentic Dev Environment desc: Dive into agentic Penpot development. --- -# Agentic Development Environment +# Agentic Dev Environment The agentic DevEnv is an extension of the standard DevEnv (the [general DevEnv instructions](/technical-guide/developer/devenv/) apply), optimised for AI agent-based development. It adds MCP servers (Penpot, Serena, Playwright) and supports a launcher that wires them into your AI client. -Two things to know up front: - -- **Parallel workspaces are first-class.** Run several devenv instances side - by side - one per AI agent if you like - each with its own source-tree - clone, ports, and tmux session. Pass `--ws N` to target one. -- **Your existing AI-client config is preserved.** The launcher loads a - per-workspace MCP config on top of your global one. - ## Quick Start 1. **Bring up one or more workspaces**[^cfg]: ```bash - ./manage.sh run-devenv-agentic # ws0 (the live repo) - ./manage.sh run-devenv-agentic --ws 1 # ws1 (sibling clone) + ./manage.sh run-devenv --agentic --attach # ws0 (the live repo) + ./manage.sh run-devenv --agentic --ws 1 # ws1 (sibling clone) ``` Add `--ws 2`, `--ws 3`, … for more parallel workspaces. @@ -47,20 +39,20 @@ Two things to know up front: "MCP Server" on. The agentic DevEnv runs the MCP server in single-user mode - the key and proxied URL shown in the UI are not needed. -4. **Launch your AI client** against the workspace you want it to drive: +4. **Launch your AI client**. Either use your manually configured client + (as described below) or conveniently launch an explicitly supported client + against the workspace you want it to drive: ```bash - ./manage.sh start-coding-agent claude # ws0 + ./manage.sh start-coding-agent claude # ws0 (main) ./manage.sh start-coding-agent claude --ws 1 # ws1 ``` Supported clients: `claude` | `opencode` | `vscode` | `codex`. -5. **Attach to the tmux session** for the workspace (optional): + Note: The launcher loads a per-workspace MCP config on top of your global one (if any). - ```bash - ./manage.sh attach-devenv # ws0 - ./manage.sh attach-devenv --ws 1 # ws1 - ``` +5. **Work within your client**, which is now equipped with extended capabilities + for Penpot development (see below for details). 6. **Shut down workspaces** with `./manage.sh stop-devenv`, either one by one or all at once. You cannot shut down `ws0` if any other workspace is still running, since it's the worker-bearer. @@ -113,10 +105,10 @@ var penpotFlags = "enable-mcp"; ``` The file is gitignored and lives in the live repo only. On every -`run-devenv-agentic` call it is read directly for ws0; for wsN (N ≥ 1) it is +`run-devenv --agentic` call it is read directly for ws0; for wsN (N ≥ 1) it is copied into the workspace clone on the **initial** sync only - subsequent `--sync` passes leave the workspace's copy alone so per-workspace -customisations survive. `run-devenv-agentic` refuses to start if the file is +customisations survive. `run-devenv --agentic` refuses to start if the file is missing. **Browser remote debugging.** The Playwright MCP server drives a real @@ -148,13 +140,13 @@ devenv image itself (add a tool, change a base layer): ./manage.sh build-devenv --local ``` -The default `run-devenv-agentic` flow pulls the published image +The default `run-devenv --agentic` flow pulls the published image automatically, so regular users never run this. ### Bringing up workspaces ```bash -./manage.sh run-devenv-agentic \ +./manage.sh run-devenv --agentic \ [--ws N] [--sync] [--serena-context CTX] \ [--git-user-name NAME] [--git-user-email EMAIL] ``` @@ -171,7 +163,7 @@ every bring-up so you don't compute offsets by hand. See the semantics, and stop ordering. **Git identity for agent commits.** Coding agents typically need to commit -inside the devenv, so `run-devenv-agentic` wires a Git identity into the +inside the devenv, so `run-devenv --agentic` wires a Git identity into the container's global config on every bring-up. By default it propagates the host's effective `git config user.{name,email}` (local repo override wins over `~/.gitconfig`, matching what `git commit` on the host would record). @@ -189,7 +181,7 @@ the full mechanics. > > ```bash > ./manage.sh stop-devenv -> ./manage.sh run-devenv-agentic +> ./manage.sh run-devenv --agentic > ``` ### Launching an AI client @@ -198,7 +190,7 @@ The agentic environment supports any AI client, one just needs to set the right see [manual configuration](#manual-ai-client-configuration) below. For some popular clients, the `manage.sh` CLI offers direct support through the following mechanism: -Every `run-devenv-agentic` regenerates three MCP-client config files with +Every `run-devenv --agentic` regenerates three MCP-client config files with the workspace's ports baked in; Codex is wired up at launch instead (see below): diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md index 88ae8db6ca..5aaab86e7f 100644 --- a/docs/technical-guide/developer/devenv.md +++ b/docs/technical-guide/developer/devenv.md @@ -45,31 +45,38 @@ This is an incomplete list of devenv related subcommands found on manage.sh script: ```bash -./manage.sh build-devenv --local # builds the local devenv docker image -./manage.sh start-devenv # brings up the shared infra + ws0 in background -./manage.sh run-devenv # ws0 with non-agentic tmux, attached (legacy alias) -./manage.sh run-devenv-agentic # one agentic instance; --ws to target ws1+; see below -./manage.sh attach-devenv # re-attaches to the tmux session of a running instance -./manage.sh stop-devenv # stops one instance (or --all); infra stops with the last -./manage.sh drop-devenv # removes containers (data volumes preserved) +./manage.sh build-devenv --local # builds the local devenv docker image +./manage.sh start-devenv # brings up the shared infra + ws0 in background +./manage.sh run-devenv --attach # bring up main devenv instance and attach to its tmux session +./manage.sh run-devenv --agentic --attach # bring up main devenv instance in agentic mode and attach tmux +./manage.sh attach-devenv # re-attaches to the tmux session of a running instance +./manage.sh stop-devenv # stops one instance (or --all); infra stops with the last +./manage.sh drop-devenv # removes containers (data volumes preserved) ``` +### Agentic Mode + +The `--agentic` flag enables additional features for AI-assisted development. +See the dedicated section [Agentic Dev Environment](../agentic-devenv/) for details. + ### Parallel workspaces -The devenv runs as separate compose projects: shared infra (`penpotdev-infra`: -Postgres, MinIO, mailer, LDAP) plus one `penpotdev-wsN` project per runtime -instance. `ws0` (a.k.a. `main`) binds the live repo; `ws1+` bind clones the -developer maintains explicitly under `${PENPOT_WORKSPACES_DIR}/wsN/` -(default `~/.penpot/penpot_workspaces/`). +The devenv runs as separate compose projects: + * shared infra (`penpotdev-infra`: Postgres, MinIO, mailer, LDAP) + * `penpotdev-wsN` project per runtime instance. + - `ws0` (a.k.a. `main`) is the current state of your repo; + - `ws1` and up are clones that you maintain explicitly under `${PENPOT_WORKSPACES_DIR}/wsN/` + (default `~/.penpot/penpot_workspaces/`). You can explicitly sync them + with the `--sync` flag (automatic on first start). -Each call to `run-devenv-agentic` brings up one instance, and ws0 is always +Each call to `run-devenv` brings up one instance, and ws0 is always running whenever any ws1+ is — `--ws N` (N≥1) auto-starts ws0 first if it isn't already up: ```bash -./manage.sh run-devenv-agentic # main (ws0) -./manage.sh run-devenv-agentic --ws 1 # ws0 if needed, then ws1 -./manage.sh run-devenv-agentic --ws 2 --sync # ws2, re-seeding from the live repo +./manage.sh run-devenv # main (ws0) +./manage.sh run-devenv --ws 1 # ws0 if needed, then ws1 +./manage.sh run-devenv --ws 2 --sync # ws2, re-seeding from the live repo ``` Starting an instance that is already running is an error. `--sync` is only @@ -102,12 +109,12 @@ Host ports are offset by `10000 × N`: | Serena MCP | `http://localhost:14181` | `http://localhost:24181` | `http://localhost:34181` | Container-internal ports stay fixed. Target a specific instance with -`--ws N` on `attach-devenv`, `run-devenv-agentic`, `stop-devenv`, +`--ws N` on `attach-devenv`, `run-devenv`, `stop-devenv`, `start-coding-agent`, `run-devenv-shell`, and `isolated-shell`. `--ws` accepts a **non-negative integer only** — `--ws main` or `--ws ws1` is rejected, keeping the flag shape uniform across commands. `run-devenv` is -ws0-only and takes no workspace flag. `run-devenv-agentic` also accepts -`--serena-context CTX` and `--git-user-name NAME` / `--git-user-email +ws0-only and takes no workspace flag. `run-devenv` also accepts +`--serena-context CTX` (used together with `--agentic`) and `--git-user-name NAME` / `--git-user-email EMAIL` (see below). Configuration lives in one tracked file, `docker/devenv/defaults.env` (the @@ -116,7 +123,7 @@ derived and injected automatically, so there is no per-instance file to edit. ### Git identity inside the container -`run-devenv-agentic` wires a Git author identity into the container's +`run-devenv` wires a Git author identity into the container's **global** git config (`git config --global user.{name,email}`) so commits made from inside the devenv carry a real author/committer. Without this, the container would commit as the unconfigured `penpot@<container>` @@ -130,7 +137,7 @@ returns at the working directory `manage.sh` is invoked from — local `git commit` on the host would record. If neither is available the script prints a warning and continues — commits will fail inside the container until you set an identity. The values are applied every time -`run-devenv-agentic` brings an instance up (idempotent), so re-running +`run-devenv` brings an instance up (idempotent), so re-running with different flags is the way to change the in-container identity. ### Shared state and workers @@ -185,11 +192,10 @@ docker rm penpotdev-postgres-1 penpotdev-minio-1 penpotdev-minio-setup-1 \ docker network rm penpotdev_default 2>/dev/null # Bring up infra + ws0 under the new project layout. -./manage.sh run-devenv-agentic +./manage.sh run-devenv ``` -After the cleanup, normal `./manage.sh start-devenv` / `run-devenv` / -`run-devenv-agentic` commands work against the new layout. The legacy +After the cleanup, normal `./manage.sh start-devenv` / `run-devenv` work against the new layout. The legacy `penpotdev` compose project is no longer used. Having the container running and tmux opened inside the container, diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ae6458efa8..67621f3754 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -65,6 +65,7 @@ [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.variants :as dwva] [app.main.data.workspace.viewport :as dwv] + [app.main.data.workspace.wasm-text :as dwwt] [app.main.data.workspace.zoom :as dwz] [app.main.errors] [app.main.features :as features] @@ -441,6 +442,14 @@ ;; Keep comment thread positions in sync on undo/redo (rx/of (dwcm/watch-comment-thread-position-changes stoper-s)) + ;; Resize auto-grow text shapes whose selrect does not match + ;; the WASM text layout once their fonts finish loading. + (->> stream + (rx/filter (ptk/type? :app.render-wasm.api/stale-text-selrects)) + (rx/map deref) + (rx/map (fn [{:keys [ids]}] + (dwwt/resize-wasm-text-all ids)))) + (let [local-commits-s (->> stream (rx/filter dch/commit?) diff --git a/frontend/src/app/main/data/workspace/wasm_text.cljs b/frontend/src/app/main/data/workspace/wasm_text.cljs index 5ae5cb8bc7..fc97dba96e 100644 --- a/frontend/src/app/main/data/workspace/wasm_text.cljs +++ b/frontend/src/app/main/data/workspace/wasm_text.cljs @@ -48,12 +48,14 @@ (wasm.api/set-shape-text-images id content)) (let [dimension (when (not= :fixed grow-type) (wasm.api/get-text-dimensions))] - {:width (if (#{:fixed :auto-height} grow-type) - (:width selrect) - (:width dimension)) - :height (if (= :fixed grow-type) - (:height selrect) - (:height dimension))})))) + ;; nil dimension = shape not present in WASM state; skip the resize. + (when (or (= :fixed grow-type) (some? dimension)) + {:width (if (#{:fixed :auto-height} grow-type) + (:width selrect) + (:width dimension)) + :height (if (= :fixed grow-type) + (:height selrect) + (:height dimension))}))))) (defn resize-wasm-text-modifiers ([shape] diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index ad42bd12f3..1c4384f1e6 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -575,15 +575,19 @@ (wasm.api/push-ruler-theme-colors!) (wasm.api/request-render "rulers-colors-theme"))))) + ;; Ruler overlay updates below only change the UI surface, not the shapes. + ;; They use `render-from-cache!` (cached tiles + UI, atomic) instead of a full + ;; `request-render`, which would kick off a progressive tile-by-tile shape + ;; re-render that flashes on zoomed-in views (see penpot ruler-selection flash). (mf/with-effect [@canvas-init? frame-visible?] (when @canvas-init? (wasm.api/set-rulers-frame-visible! frame-visible?) - (wasm.api/request-render "rulers-frame"))) + (wasm.api/render-from-cache!))) (mf/with-effect [@canvas-init? show-rulers?] (when @canvas-init? (wasm.api/set-rulers-visible! show-rulers?) - (wasm.api/request-render "rulers-visible"))) + (wasm.api/render-from-cache!))) (mf/with-effect [@canvas-init? show-rulers? offset-x offset-y] (when (and @canvas-init? show-rulers?) @@ -594,7 +598,7 @@ (some-> ruler-selection :width) (some-> ruler-selection :height)] (when (and @canvas-init? show-rulers?) (wasm.api/set-rulers-selection! ruler-selection) - (wasm.api/request-render "rulers-selection"))) + (wasm.api/render-from-cache!))) ;; Paint background + rulers instantly, before shapes finish loading. Runs ;; after the ruler push effects so the WASM ruler state is already set. diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 2403814e18..cea8256980 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -58,6 +58,7 @@ [app.util.timers :as timers] [beicon.v2.core :as rx] [cuerdas.core :as str] + [potok.v2.core :as ptk] [promesa.core :as p] [rumext.v2 :as mf])) @@ -459,6 +460,20 @@ (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render_ui_only"))) +(defn render-from-cache! + "Blit the shapes from the cached tile atlas and redraw the UI overlay + (rulers, selection band) fresh on top, in a single atomic frame. The + *shapes* are the cached part (already-rasterized tiles, not rebuilt); the UI + is re-rendered every call, which is what lets it reflect a new selection. + + Use for UI-only updates that don't change shapes (e.g. the ruler selection + band): unlike `request-render`, it never kicks off a progressive, + tile-by-tile shape re-render, so it does not flash on zoomed-in views where + the scene spans multiple tiles." + [] + (when (and wasm/context-initialized? (not @wasm/context-lost?)) + (h/call wasm/internal-module "_render_from_cache" 0))) + ;; CSS-pixel blur radius for the page-transition snapshot (DPR-scaled in WASM). (def ^:private TRANSITION_BLUR_RADIUS 4.0) @@ -1282,17 +1297,19 @@ ([] (if-not (initialized?) {:x 0 :y 0 :width 0 :height 0 :max-width 0} - (let [offset (-> (h/call wasm/internal-module "_get_text_dimensions") - (mem/->offset-32)) - heapf32 (mem/get-heap-f32) - width (aget heapf32 (+ offset 0)) - height (aget heapf32 (+ offset 1)) - max-width (aget heapf32 (+ offset 2)) + (let [ptr (h/call wasm/internal-module "_get_text_dimensions")] + ;; NULL pointer when there is no current shape or it is not a text. + (when-not (zero? ptr) + (let [offset (mem/->offset-32 ptr) + heapf32 (mem/get-heap-f32) + width (aget heapf32 (+ offset 0)) + height (aget heapf32 (+ offset 1)) + max-width (aget heapf32 (+ offset 2)) - x (aget heapf32 (+ offset 3)) - y (aget heapf32 (+ offset 4))] - (mem/free) - {:x x :y y :width width :height height :max-width max-width})))) + x (aget heapf32 (+ offset 3)) + y (aget heapf32 (+ offset 4))] + (mem/free) + {:x x :y y :width width :height height :max-width max-width})))))) (defn intersect-position-in-shape [id position] @@ -1452,19 +1469,45 @@ [text-ids] (run! f/force-update-text-layout text-ids)) +(defn- text-selrect-stale? + "Check if the WASM-measured dimensions of an auto-grow text shape differ + from its stored selrect (same 0.1px tolerance as the classic renderer)." + [{:keys [id selrect grow-type]}] + (when-let [{:keys [width height]} (get-text-dimensions id)] + (case grow-type + :auto-width (or (not (mth/close? width (:width selrect) 0.1)) + (not (mth/close? height (:height selrect) 0.1))) + :auto-height (not (mth/close? height (:height selrect) 0.1)) + false))) + +(defn- sync-stale-text-selrects! + "Emit the ids of auto-grow text shapes whose selrect no longer matches the + measured layout, so the workspace resizes them (data-event instead of a + direct call to avoid a circular dependency; see the watcher in + `app.main.data.workspace/initialize-workspace`)." + [shapes] + (let [stale-ids (into [] + (comp (filter cfh/text-shape?) + (filter (comp #{:auto-width :auto-height} :grow-type)) + (filter text-selrect-stale?) + (map :id)) + shapes)] + (when (seq stale-ids) + (st/emit! (ptk/data-event ::stale-text-selrects {:ids stale-ids}))))) + (defn- relayout-after-fonts! - "Relayout text shapes once their pending fonts have resolved. Shapes in - `font-pending-ids` had a font fetched, so they get a forced relayout to pick - up the real glyph metrics; the remaining text shapes get a normal layout." + "Relayout text shapes once their pending fonts have resolved. Font fetches + are deduped per URL and storing a font does not invalidate cached layouts, + so every text shape (not only the fetch triggers in `font-pending-ids`) + needs a forced relayout; then re-sync selrects that drifted." [shapes font-pending-ids] - (let [force-ids (set font-pending-ids) - text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes) - forced (filterv force-ids text-ids) - rest-ids (filterv (complement force-ids) text-ids)] - (when (seq forced) - (force-update-text-layouts forced)) - (when (seq rest-ids) - (update-text-layouts rest-ids)))) + (let [text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)] + (when (seq text-ids) + (if (seq font-pending-ids) + (do + (force-update-text-layouts text-ids) + (sync-stale-text-selrects! shapes)) + (update-text-layouts text-ids))))) (defn process-pending [shapes thumbnails full font-pending-ids on-complete] diff --git a/mcp/bin/mcp-local.js b/mcp/bin/mcp-local.js index b77b245dea..feecbc9e39 100644 --- a/mcp/bin/mcp-local.js +++ b/mcp/bin/mcp-local.js @@ -1,21 +1,61 @@ #!/usr/bin/env node +// Entry point (bin) of the published @penpot/mcp package. +// This script is not intended to be run from a source checkout; there, use +// `pnpm run bootstrap` directly, as described in the README. + const { execSync } = require("child_process"); const fs = require("fs"); +const os = require("os"); const path = require("path"); -const root = path.resolve(__dirname, ".."); -const pkg = require(path.join(root, "package.json")); +const packageRoot = path.resolve(__dirname, ".."); +const pkg = require(path.join(packageRoot, "package.json")); function pnpmVersion() { const match = (pkg.packageManager || "").match(/^pnpm@([^+]+)/); return match ? match[1] : "latest"; } -function run(command) { - execSync(command, { cwd: root, stdio: "inherit" }); +/** + * Prepares the directory in which to bootstrap and run. + * + * The package contents are copied once per version to a runtime directory + * owned by this launcher. The npm-owned package directory cannot be used; + * we have to assume it is read-only (#9947). + * + * @returns {string} the directory to run the bootstrap in + */ +function prepareRuntimeRoot() { + const cacheBase = process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"); + const runtimeRoot = path.join(cacheBase, "penpot-mcp", pkg.version); + if (fs.existsSync(runtimeRoot)) { + return runtimeRoot; + } + + // copy to a temporary sibling first and rename, so an interrupted copy + // cannot leave a partial runtime directory behind + console.log(`Preparing runtime directory ${runtimeRoot} ...`); + const tempRoot = `${runtimeRoot}.tmp-${process.pid}`; + fs.rmSync(tempRoot, { recursive: true, force: true }); + fs.cpSync(packageRoot, tempRoot, { + recursive: true, + filter: (src) => path.basename(src) !== "node_modules", + }); + try { + fs.renameSync(tempRoot, runtimeRoot); + } catch (error) { + fs.rmSync(tempRoot, { recursive: true, force: true }); + if (!fs.existsSync(runtimeRoot)) { + throw error; + } + // a concurrent launch created the runtime directory in the meantime; use it + } + return runtimeRoot; } +const root = prepareRuntimeRoot(); + // pnpm-lock.yaml is hard-excluded by npm pack; it is shipped as pnpm-lock.dist.yaml // and restored here before bootstrap runs. const distLock = path.join(root, "pnpm-lock.dist.yaml"); @@ -25,7 +65,7 @@ if (fs.existsSync(distLock)) { } try { - run(`npx -y pnpm@${pnpmVersion()} run bootstrap`); + execSync(`npx -y pnpm@${pnpmVersion()} run bootstrap`, { cwd: root, stdio: "inherit" }); } catch (error) { process.exit(error.status ?? 1); } diff --git a/mcp/package.json b/mcp/package.json index 163d0d649a..5f6dea413e 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/mcp", - "version": "2.16.0", + "version": "2.17.0", "description": "MCP server for Penpot integration", "license": "MPL-2.0", "bin": { diff --git a/mcp/packages/server/data/api_types.yml b/mcp/packages/server/data/api_types.yml index de532d4eb9..0b53f1dfc0 100644 --- a/mcp/packages/server/data/api_types.yml +++ b/mcp/packages/server/data/api_types.yml @@ -11,7 +11,7 @@ Penpot: open: ( name: string, url: string, - options?: { width: number; height: number; hidden: boolean }, + options?: { width: number; height: number; hidden?: boolean }, ) => void; size: { width: number; height: number } | null; resize: (width: number, height: number) => void; @@ -73,7 +73,7 @@ Penpot: generateFontFaces(shapes: Shape[]): Promise<string>; openViewer(): void; createPage(): Page; - openPage(page: string | Page, newWindow?: boolean): void; + openPage(page: string | Page, newWindow?: boolean): Promise<void>; alignHorizontal( shapes: Shape[], direction: "center" | "left" | "right", @@ -97,11 +97,11 @@ Penpot: Properties: ui: |- ``` - ui: { + readonly ui: { open: ( name: string, url: string, - options?: { width: number; height: number; hidden: boolean }, + options?: { width: number; height: number; hidden?: boolean }, ) => void; size: { width: number; height: number } | null; resize: (width: number, height: number) => void; @@ -112,7 +112,7 @@ Penpot: Type Declaration - * open: ( name: string, url: string, options?: { width: number; height: number; hidden: boolean },) => void + * open: ( name: string, url: string, options?: { width: number; height: number; hidden?: boolean },) => void Opens the plugin UI. It is possible to develop a plugin without interface (see Palette color example) but if you need, the way to open this UI is using `penpot.ui.open`. There is a minimum and maximum size for this modal and a default size but it's possible to customize it anyway with the options parameter. @@ -122,6 +122,13 @@ Penpot: penpot.ui.open('Plugin name', 'url', {width: 150, height: 300}); ``` * size: { width: number; height: number } | null + + The current size of the modal, or `null` if the plugin UI is not open. + + Example + ``` + const size = penpot.ui.size;console.log(size); + ``` * resize: (width: number, height: number) => void Resizes the plugin UI. @@ -136,19 +143,19 @@ Penpot: Example ``` - this.sendMessage({ type: 'example-type', content: 'data we want to share' }); + penpot.ui.sendMessage({ type: 'example-type', content: 'data we want to share' }); ``` * onMessage: <T>(callback: (message: T) => void) => void - This is usually used in the `plugin.ts` file in order to handle the data sent by our plugin + This is usually used in the `plugin.ts` file in order to handle the messages sent from the plugin UI. Example ``` - penpot.ui.onMessage((message) => {if(message.type === 'example-type' { ...do something })}); + penpot.ui.onMessage((message) => { if (message.type === 'example-type') { ...do something } }); ``` utils: |- ``` - utils: ContextUtils + readonly utils: ContextUtils ``` Provides access to utility functions and context-specific operations. @@ -331,7 +338,9 @@ Penpot: * selectionchange: event emitted when the current selection changes. The callback will receive the list of ids for the new selection * themechange: event emitted when the user changes its theme. The callback will receive the new theme (currently: either `dark` or `light`) - * documentsaved: event emitted after the document is saved in the backend. + * filechange: event emitted when a different file is opened. The callback will receive the new file. + * contentsave: event emitted after the file content is saved in the backend. The callback receives no arguments. + * finish: event emitted when the current file is closed. The callback will receive the id of the closed file. Type Parameters @@ -355,7 +364,7 @@ Penpot: Example ``` - penpot.on('pagechange', () => {...do something}). + penpot.on('pagechange', () => {...do something}); ``` off: |- ``` @@ -508,7 +517,7 @@ Penpot: Example ``` - const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and if the selected shape is a group,if (selected.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.group(penpotShapesArray[0]);} + const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and that the selected shape is a groupif (penpotShapesArray.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.ungroup(penpotShapesArray[0]);} ``` createRectangle: |- ``` @@ -643,11 +652,11 @@ Penpot: * text: string - The text content for the Text shape. + The text content for the Text shape. Must be a non-empty string. Returns Text | null - Returns the new created shape, if the shape wasn't created can return null. + Returns the new created shape. Returns null if an empty string is provided or the shape couldn't be created. Example ``` @@ -663,8 +672,12 @@ Penpot: Parameters * shapes: Shape[] + + the shapes to generate the markup for * options: { type?: "html" | "svg" } + `type` of the markup to generate. Defaults to `'html'`. + Returns string Example @@ -688,8 +701,13 @@ Penpot: Parameters * shapes: Shape[] + + the shapes to generate the styles for * options: { type?: "css"; withPrelude?: boolean; includeChildren?: boolean } + `type` of the styles to generate (defaults to `'css'`), `withPrelude` to include the style prelude + (defaults to `false`) and `includeChildren` to also generate the styles for the shape children (defaults to `true`). + Returns string Example @@ -727,15 +745,28 @@ Penpot: createPage(): Page ``` - Creates a new page. Requires `content:write` permission. + Creates a new page and returns it. Requires `content:write` permission. + + IMPORTANT: creating a page does **not** make it the active page. + To build content inside the new page, activate it first with + Context.openPage (and `await` it) before mutate shapes: Returns Page + + Example + ``` + const page = penpot.createPage();page.name = 'New Page';await penpot.openPage(page); // make the new page active firstconst board = penpot.createBoard();board.resize(375, 812);page.root.appendChild(board); + ``` openPage: |- ``` - openPage(page: string | Page, newWindow?: boolean): void + openPage(page: string | Page, newWindow?: boolean): Promise<void> ``` - Changes the current open page to given page. Requires `content:read` permission. + Changes the current open page to the given page, making it the **active page**. + The active page is the one all shape creation and structural operations + (`createBoard`, `appendChild`, `insertChild`, property setters, etc.) act upon, + so call this (and `await` it) after Context.createPage before adding + shapes to the newly created page. Requires `content:read` permission. Parameters @@ -746,11 +777,11 @@ Penpot: if true opens the page in a new window, defaults to false - Returns void + Returns Promise<void> Example ``` - context.openPage(page); + await context.openPage(page); ``` alignHorizontal: |- ``` @@ -965,7 +996,6 @@ Blur: ``` interface Blur { id?: string; - type?: "layer-blur"; value?: number; hidden?: boolean; } @@ -980,13 +1010,6 @@ Blur: ``` The optional unique identifier for the blur effect. - type: |- - ``` - type?: "layer-blur" - ``` - - The optional type of the blur effect. - Currently, only 'layer-blur' is supported. value: |- ``` value?: number @@ -1053,6 +1076,7 @@ Board: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -1078,6 +1102,7 @@ Board: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -1139,6 +1164,7 @@ Board: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -1156,7 +1182,7 @@ Board: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -1188,7 +1214,7 @@ Board: showInViewMode: boolean ``` - WHen true the board will be displayed in the view mode + When true the board will be displayed in the view mode grid: |- ``` readonly grid?: GridLayout @@ -1221,7 +1247,7 @@ Board: The horizontal sizing behavior of the board. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'auto': The container fits the content. verticalSizing: |- ``` @@ -1231,7 +1257,7 @@ Board: The vertical sizing behavior of the board. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'auto': The container fits the content. fills: |- ``` @@ -1309,17 +1335,13 @@ Board: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -1356,6 +1378,12 @@ Board: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -1426,6 +1454,14 @@ Board: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -1473,9 +1509,7 @@ Board: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -1619,27 +1653,31 @@ Board: Example ``` - const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5 + const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5; ``` addRulerGuide: |- ``` addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide ``` - Creates a new ruler guide. + Creates a new ruler guide attached to the board. Parameters * orientation: RulerGuideOrientation + + `horizontal` or `vertical` * value: number + the position of the guide relative to the board + Returns RulerGuide removeRulerGuide: |- ``` removeRulerGuide(guide: RulerGuide): void ``` - Removes the `guide` from the current page. + Removes the `guide` from the board. Parameters @@ -1889,12 +1927,27 @@ Board: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -1907,7 +1960,7 @@ Board: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -1976,7 +2029,7 @@ Board: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -2040,6 +2093,10 @@ Board: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -2079,7 +2136,7 @@ Board: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -2089,7 +2146,7 @@ Board: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -2172,6 +2229,7 @@ VariantContainer: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -2197,6 +2255,7 @@ VariantContainer: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -2258,6 +2317,7 @@ VariantContainer: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -2275,7 +2335,7 @@ VariantContainer: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -2306,7 +2366,7 @@ VariantContainer: showInViewMode: boolean ``` - WHen true the board will be displayed in the view mode + When true the board will be displayed in the view mode grid: |- ``` readonly grid?: GridLayout @@ -2339,7 +2399,7 @@ VariantContainer: The horizontal sizing behavior of the board. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'auto': The container fits the content. verticalSizing: |- ``` @@ -2349,7 +2409,7 @@ VariantContainer: The vertical sizing behavior of the board. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'auto': The container fits the content. fills: |- ``` @@ -2431,17 +2491,13 @@ VariantContainer: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -2478,6 +2534,12 @@ VariantContainer: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -2548,6 +2610,14 @@ VariantContainer: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -2595,9 +2665,7 @@ VariantContainer: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -2741,27 +2809,31 @@ VariantContainer: Example ``` - const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5 + const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5; ``` addRulerGuide: |- ``` addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide ``` - Creates a new ruler guide. + Creates a new ruler guide attached to the board. Parameters * orientation: RulerGuideOrientation + + `horizontal` or `vertical` * value: number + the position of the guide relative to the board + Returns RulerGuide removeRulerGuide: |- ``` removeRulerGuide(guide: RulerGuide): void ``` - Removes the `guide` from the current page. + Removes the `guide` from the board. Parameters @@ -3011,12 +3083,27 @@ VariantContainer: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -3029,7 +3116,7 @@ VariantContainer: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -3098,7 +3185,7 @@ VariantContainer: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -3162,6 +3249,10 @@ VariantContainer: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -3201,7 +3292,7 @@ VariantContainer: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -3211,7 +3302,7 @@ VariantContainer: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -3281,6 +3372,7 @@ Boolean: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -3306,6 +3398,7 @@ Boolean: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -3367,6 +3460,7 @@ Boolean: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -3384,7 +3478,7 @@ Boolean: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -3403,10 +3497,10 @@ Boolean: readonly type: "boolean" ``` - The type of the shape, which is always 'bool' for boolean operation shapes. + The type of the shape, which is always 'boolean' for boolean operation shapes. content: |- ``` - content: string + readonly content: string ``` The content of the boolean shape, defined as the path string. @@ -3416,13 +3510,13 @@ Boolean: Use either `d` or `commands`. d: |- ``` - d: string + readonly d: string ``` The content of the boolean shape, defined as the path string. commands: |- ``` - commands: PathCommand[] + readonly commands: PathCommand[] ``` The content of the boolean shape, defined as an array of path commands. @@ -3494,17 +3588,13 @@ Boolean: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -3541,6 +3631,12 @@ Boolean: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -3611,6 +3707,14 @@ Boolean: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -3658,9 +3762,7 @@ Boolean: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -4025,12 +4127,27 @@ Boolean: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -4043,7 +4160,7 @@ Boolean: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -4112,7 +4229,7 @@ Boolean: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -4176,6 +4293,10 @@ Boolean: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -4215,7 +4336,7 @@ Boolean: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -4225,7 +4346,7 @@ Boolean: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -4265,7 +4386,7 @@ CloseOverlay: interface CloseOverlay { type: "close-overlay"; destination?: Board; - animation: Animation; + animation?: Animation; } ``` @@ -4286,10 +4407,10 @@ CloseOverlay: The overlay to be closed with this action. animation: |- ``` - readonly animation: Animation + readonly animation?: Animation ``` - Animation displayed with this interaction. + Animation displayed with this interaction. Omit it to close with no transition. Color: overview: |- Interface Color @@ -4760,7 +4881,7 @@ CommonLayout: The `horizontalSizing` property specifies the horizontal sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. verticalSizing: |- @@ -4771,7 +4892,7 @@ CommonLayout: The `verticalSizing` property specifies the vertical sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. Methods: @@ -4845,7 +4966,7 @@ Context: removeListener(listenerId: symbol): void; openViewer(): void; createPage(): Page; - openPage(page: string | Page, newWindow?: boolean): void; + openPage(page: string | Page, newWindow?: boolean): Promise<void>; alignHorizontal( shapes: Shape[], direction: "center" | "left" | "right", @@ -5138,7 +5259,7 @@ Context: Example ``` - const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and if the selected shape is a group,if (selected.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.group(penpotShapesArray[0]);} + const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and that the selected shape is a groupif (penpotShapesArray.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.ungroup(penpotShapesArray[0]);} ``` createRectangle: |- ``` @@ -5273,11 +5394,11 @@ Context: * text: string - The text content for the Text shape. + The text content for the Text shape. Must be a non-empty string. Returns Text | null - Returns the new created shape, if the shape wasn't created can return null. + Returns the new created shape. Returns null if an empty string is provided or the shape couldn't be created. Example ``` @@ -5293,8 +5414,12 @@ Context: Parameters * shapes: Shape[] + + the shapes to generate the markup for * options: { type?: "html" | "svg" } + `type` of the markup to generate. Defaults to `'html'`. + Returns string Example @@ -5318,8 +5443,13 @@ Context: Parameters * shapes: Shape[] + + the shapes to generate the styles for * options: { type?: "css"; withPrelude?: boolean; includeChildren?: boolean } + `type` of the styles to generate (defaults to `'css'`), `withPrelude` to include the style prelude + (defaults to `false`) and `includeChildren` to also generate the styles for the shape children (defaults to `true`). + Returns string Example @@ -5401,15 +5531,28 @@ Context: createPage(): Page ``` - Creates a new page. Requires `content:write` permission. + Creates a new page and returns it. Requires `content:write` permission. + + IMPORTANT: creating a page does **not** make it the active page. + To build content inside the new page, activate it first with + Context.openPage (and `await` it) before mutate shapes: Returns Page + + Example + ``` + const page = penpot.createPage();page.name = 'New Page';await penpot.openPage(page); // make the new page active firstconst board = penpot.createBoard();board.resize(375, 812);page.root.appendChild(board); + ``` openPage: |- ``` - openPage(page: string | Page, newWindow?: boolean): void + openPage(page: string | Page, newWindow?: boolean): Promise<void> ``` - Changes the current open page to given page. Requires `content:read` permission. + Changes the current open page to the given page, making it the **active page**. + The active page is the one all shape creation and structural operations + (`createBoard`, `appendChild`, `insertChild`, property setters, etc.) act upon, + so call this (and `await` it) after Context.createPage before adding + shapes to the newly created page. Requires `content:read` permission. Parameters @@ -5420,11 +5563,11 @@ Context: if true opens the page in a new window, defaults to false - Returns void + Returns Promise<void> Example ``` - context.openPage(page); + await context.openPage(page); ``` alignHorizontal: |- ``` @@ -5889,6 +6032,7 @@ Ellipse: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -5914,6 +6058,7 @@ Ellipse: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -5975,6 +6120,7 @@ Ellipse: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -5992,7 +6138,7 @@ Ellipse: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -6008,8 +6154,10 @@ Ellipse: Properties: type: |- ``` - type: "ellipse" + readonly type: "ellipse" ``` + + The type of the shape, which is always 'ellipse' for ellipse shapes. fills: |- ``` fills: Fill[] @@ -6072,17 +6220,13 @@ Ellipse: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -6119,6 +6263,12 @@ Ellipse: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -6189,6 +6339,14 @@ Ellipse: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -6236,9 +6394,7 @@ Ellipse: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -6548,12 +6704,27 @@ Ellipse: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -6566,7 +6737,7 @@ Ellipse: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -6635,7 +6806,7 @@ Ellipse: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -6699,6 +6870,10 @@ Ellipse: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -6738,7 +6913,7 @@ Ellipse: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -6748,7 +6923,7 @@ Ellipse: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -6807,48 +6982,50 @@ EventsMap: Properties: pagechange: |- ``` - pagechange: Page + readonly pagechange: Page ``` The `pagechange` event is triggered when the active page in the project is changed. filechange: |- ``` - filechange: File + readonly filechange: File ``` - The `filechange` event is triggered when there are changes in the current file. + The `filechange` event is triggered when a different file is opened. + The callback will receive the new file. selectionchange: |- ``` - selectionchange: string[] + readonly selectionchange: string[] ``` The `selectionchange` event is triggered when the selection of elements changes. This event passes a list of identifiers of the selected elements. themechange: |- ``` - themechange: Theme + readonly themechange: Theme ``` The `themechange` event is triggered when the application theme is changed. finish: |- ``` - finish: string + readonly finish: string ``` - The `finish` event is triggered when some operation is finished. + The `finish` event is triggered when the current file is closed. + The callback will receive the id of the closed file. shapechange: |- ``` - shapechange: Shape + readonly shapechange: Shape ``` This event will trigger whenever the shape in the props change. It's mandatory to send with the props an object like `{ shapeId: '<id>' }` contentsave: |- ``` - contentsave: void + readonly contentsave: void ``` - The `contentsave` event will trigger when the content file changes. + The `contentsave` event will trigger after the file content is saved in the backend. Export: overview: |- Interface Export @@ -6913,6 +7090,7 @@ File: ): Promise<Uint8Array<ArrayBufferLike>>; findVersions(criteria?: { createdBy: User }): Promise<FileVersion[]>; saveVersion(label: string): Promise<FileVersion>; + validate(): FileValidationError[]; getPluginData(key: string): string; setPluginData(key: string, value: string): void; getPluginDataKeys(): string[]; @@ -6938,19 +7116,19 @@ File: The `id` property is a unique identifier for the file. name: |- ``` - name: string + readonly name: string ``` The `name` for the file revn: |- ``` - revn: number + readonly revn: number ``` The `revn` will change for every document update pages: |- ``` - pages: Page[] + readonly pages: Page[] ``` List all the pages for the current file @@ -6963,12 +7141,19 @@ File: ): Promise<Uint8Array<ArrayBufferLike>> ``` + Export the current file to an archive. + Parameters * exportType: "penpot" | "zip" * libraryExportType: "all" | "merge" | "detach" Returns Promise<Uint8Array<ArrayBufferLike>> + + Example + ``` + const exportedData = await file.export('penpot', 'all'); + ``` findVersions: |- ``` findVersions(criteria?: { createdBy: User }): Promise<FileVersion[]> @@ -6994,6 +7179,22 @@ File: * label: string Returns Promise<FileVersion> + validate: |- + ``` + validate(): FileValidationError[] + ``` + + Runs the referential-integrity validation on the file and returns the list + of errors found. An empty array means the file is valid. Useful to detect + inconsistencies (dangling references, broken components/variants, …) that + the backend would otherwise reject when the file is saved. + + Returns FileValidationError[] + + Example + ``` + const errors = file.validate();if (errors.length) console.error(errors); + ``` getPluginData: |- ``` getPluginData(key: string): string @@ -7122,6 +7323,50 @@ File: ``` const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); ``` +FileValidationError: + overview: |- + Interface FileValidationError + ============================= + + A single referential-integrity error reported by File.validate. + + ``` + interface FileValidationError { + code: string; + hint: string; + shapeId: string | null; + pageId: string | null; + } + ``` + + Referenced by: File + members: + Properties: + code: |- + ``` + readonly code: string + ``` + + The validation error code (e.g. `'variant-component-bad-name'`, + `'child-not-found'`). + hint: |- + ``` + readonly hint: string + ``` + + A human-readable description of the error. + shapeId: |- + ``` + readonly shapeId: string | null + ``` + + The id of the offending shape, when the error is attached to one. + pageId: |- + ``` + readonly pageId: string | null + ``` + + The id of the page the offending shape lives in, when applicable. FileVersion: overview: |- Interface FileVersion @@ -7175,6 +7420,11 @@ FileVersion: restore(): void ``` + Restores the current version and replaces the content of the active file + for the contents of this version. + Requires the `content:write` permission. + Warning: Calling this will close the plugin because the workspace will reload + Returns void remove: |- ``` @@ -7258,7 +7508,7 @@ Flags: Interface Flags =============== - This subcontext allows the API o change certain defaults + This subcontext allows the API to change certain defaults ``` interface Flags { @@ -7275,9 +7525,9 @@ Flags: naturalChildOrdering: boolean ``` - If `true` the .children property will be always sorted in the z-index ordering. - Also, appendChild method will be append the children in the top-most position. - The insertchild method is changed acordingly to respect this ordering. + If `true` the .children property will always be sorted in the z-index ordering. + Also, the appendChild method will append the children in the top-most position. + The insertChild method is changed accordingly to respect this ordering. Defaults to false throwValidationErrors: |- ``` @@ -7286,7 +7536,8 @@ Flags: If `true` the validation errors will throw an exception instead of displaying an error in the debugger console. - Defaults to false + Defaults to `true` for plugins whose manifest declares `"version": 2` (or higher) + and to `false` for v1 plugins (or manifests that omit the version field). FlexLayout: overview: |- Interface FlexLayout @@ -7470,7 +7721,7 @@ FlexLayout: The `horizontalSizing` property specifies the horizontal sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. verticalSizing: |- @@ -7481,7 +7732,7 @@ FlexLayout: The `verticalSizing` property specifies the vertical sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. dir: |- @@ -7605,43 +7856,43 @@ Font: Properties: name: |- ``` - name: string + readonly name: string ``` This property holds the human-readable name of the font. fontId: |- ``` - fontId: string + readonly fontId: string ``` The unique identifier of the font. fontFamily: |- ``` - fontFamily: string + readonly fontFamily: string ``` The font family of the font. fontStyle: |- ``` - fontStyle?: "normal" | "italic" | null + readonly fontStyle?: "normal" | "italic" | null ``` The default font style of the font. fontVariantId: |- ``` - fontVariantId: string + readonly fontVariantId: string ``` The default font variant ID of the font. fontWeight: |- ``` - fontWeight: string + readonly fontWeight: string ``` The default font weight of the font. variants: |- ``` - variants: FontVariant[] + readonly variants: FontVariant[] ``` An array of font variants available for the font. @@ -7712,25 +7963,25 @@ FontVariant: Properties: name: |- ``` - name: string + readonly name: string ``` The name of the font variant. fontVariantId: |- ``` - fontVariantId: string + readonly fontVariantId: string ``` The unique identifier of the font variant. fontWeight: |- ``` - fontWeight: string + readonly fontWeight: string ``` The font weight of the font variant. fontStyle: |- ``` - fontStyle: "normal" | "italic" + readonly fontStyle: "normal" | "italic" ``` The font style of the font variant. @@ -7757,7 +8008,7 @@ FontsContext: Properties: all: |- ``` - all: Font[] + readonly all: Font[] ``` An array containing all available fonts. @@ -8037,7 +8288,7 @@ GridLayout: The `horizontalSizing` property specifies the horizontal sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. verticalSizing: |- @@ -8048,7 +8299,7 @@ GridLayout: The `verticalSizing` property specifies the vertical sizing behavior of the container. It can be one of the following values: - * 'fix': The containers has its own intrinsic fixed size. + * 'fix': The container has its own intrinsic fixed size. * 'fill': The container fills the available space. Only can be set if it's inside another layout. * 'auto': The container fits the content. dir: |- @@ -8327,6 +8578,7 @@ Group: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -8352,6 +8604,7 @@ Group: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -8415,6 +8668,7 @@ Group: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -8432,7 +8686,7 @@ Group: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -8512,17 +8766,13 @@ Group: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -8559,6 +8809,12 @@ Group: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -8629,6 +8885,14 @@ Group: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -8676,9 +8940,7 @@ Group: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. fills: |- ``` fills: Fill[] | "mixed" @@ -9060,12 +9322,27 @@ Group: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -9078,7 +9355,7 @@ Group: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -9147,7 +9424,7 @@ Group: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -9211,6 +9488,10 @@ Group: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -9250,7 +9531,7 @@ Group: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -9260,7 +9541,7 @@ Group: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -9294,7 +9575,7 @@ GuideColumn: Interface GuideColumn ===================== - Represents a goard guide for columns in Penpot. + Represents a board guide for columns in Penpot. This interface includes properties for defining the type, visibility, and parameters of column guides within a board. ``` @@ -9310,19 +9591,19 @@ GuideColumn: Properties: type: |- ``` - type: "column" + readonly type: "column" ``` The type of the guide, which is always 'column' for column guides. display: |- ``` - display: boolean + readonly display: boolean ``` Specifies whether the column guide is displayed. params: |- ``` - params: GuideColumnParams + readonly params: GuideColumnParams ``` The parameters defining the appearance and layout of the column guides. @@ -9350,13 +9631,13 @@ GuideColumnParams: Properties: color: |- ``` - color: { color: string; opacity: number } + readonly color: { color: string; opacity: number } ``` The color configuration for the column guides. type: |- ``` - type?: "center" | "left" | "right" | "stretch" + readonly type?: "center" | "left" | "right" | "stretch" ``` The optional alignment type of the column guides. @@ -9367,25 +9648,25 @@ GuideColumnParams: * 'right': Columns align to the right. size: |- ``` - size?: number + readonly size?: number ``` The optional size of each column. margin: |- ``` - margin?: number + readonly margin?: number ``` The optional margin between the columns and the board edges. itemLength: |- ``` - itemLength?: number + readonly itemLength?: number ``` The optional length of each item within the columns. gutter: |- ``` - gutter?: number + readonly gutter?: number ``` The optional gutter width between columns. @@ -9410,19 +9691,19 @@ GuideRow: Properties: type: |- ``` - type: "row" + readonly type: "row" ``` The type of the guide, which is always 'row' for row guides. display: |- ``` - display: boolean + readonly display: boolean ``` Specifies whether the row guide is displayed. params: |- ``` - params: GuideColumnParams + readonly params: GuideColumnParams ``` The parameters defining the appearance and layout of the row guides. @@ -9448,19 +9729,19 @@ GuideSquare: Properties: type: |- ``` - type: "square" + readonly type: "square" ``` The type of the guide, which is always 'square' for square guides. display: |- ``` - display: boolean + readonly display: boolean ``` Specifies whether the square guide is displayed. params: |- ``` - params: GuideSquareParams + readonly params: GuideSquareParams ``` The parameters defining the appearance and layout of the square guides. @@ -9484,13 +9765,13 @@ GuideSquareParams: Properties: color: |- ``` - color: { color: string; opacity: number } + readonly color: { color: string; opacity: number } ``` The color configuration for the square guides. size: |- ``` - size?: number + readonly size?: number ``` The optional size of each square guide. @@ -9549,6 +9830,11 @@ Image: Represents an image shape in Penpot. This interface extends `ShapeBase` and includes properties specific to image shapes. + Deprecated + + Image shapes exist only for backward compatibility with old files. + New images are embedded in a `Fill` via its `fillImage` (an `ImageData`). + ``` interface Image { type: "image"; @@ -9575,6 +9861,7 @@ Image: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -9600,6 +9887,7 @@ Image: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -9661,6 +9949,7 @@ Image: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -9678,7 +9967,7 @@ Image: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -9694,8 +9983,10 @@ Image: Properties: type: |- ``` - type: "image" + readonly type: "image" ``` + + The type of the shape, which is always 'image' for image shapes. fills: |- ``` fills: Fill[] @@ -9758,17 +10049,13 @@ Image: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -9805,6 +10092,12 @@ Image: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -9875,6 +10168,14 @@ Image: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -9922,9 +10223,7 @@ Image: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -10234,12 +10533,27 @@ Image: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -10252,7 +10566,7 @@ Image: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -10321,7 +10635,7 @@ Image: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -10385,6 +10699,10 @@ Image: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -10424,7 +10742,7 @@ Image: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -10434,7 +10752,7 @@ Image: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -11331,7 +11649,7 @@ LibraryComponent: transformInVariant(): void ``` - Creates a new Variant from this standard Component. It creates a VariantContainer, transform this Component into a VariantComponent, duplicates it, and creates a + Creates a new Variant from this standard Component. It creates a VariantContainer, transforms this Component into a VariantComponent, duplicates it, and creates a set of properties based on the component name and path. Similar to doing it with the contextual menu or the shortcut on the Penpot interface @@ -11515,13 +11833,13 @@ LibraryVariantComponent: readonly variantProps: { [property: string]: string } ``` - A list of the variants props of this VariantComponent. Each property have a key and a value + A list of the variant props of this VariantComponent. Each property has a key and a value variantError: |- ``` variantError: string ``` - If this VariantComponent has an invalid name, that does't follow the structure [property]=[value], [property]=[value] + If this VariantComponent has an invalid name, that doesn't follow the structure [property]=[value], [property]=[value] this field stores that invalid name id: |- ``` @@ -11586,7 +11904,7 @@ LibraryVariantComponent: transformInVariant(): void ``` - Creates a new Variant from this standard Component. It creates a VariantContainer, transform this Component into a VariantComponent, duplicates it, and creates a + Creates a new Variant from this standard Component. It creates a VariantContainer, transforms this Component into a VariantComponent, duplicates it, and creates a set of properties based on the component name and path. Similar to doing it with the contextual menu or the shortcut on the Penpot interface @@ -11609,8 +11927,12 @@ LibraryVariantComponent: Parameters * pos: number + + The position of the property to update * value: string + The new value of the property + Returns void getPluginData: |- ``` @@ -11991,7 +12313,7 @@ LibraryTypography: name: string; path: string; fontId: string; - fontFamilies: string; + fontFamily: string; fontVariantId: string; fontSize: string; fontWeight: string; @@ -12049,12 +12371,12 @@ LibraryTypography: ``` The unique identifier of the font used in the typography element. - fontFamilies: |- + fontFamily: |- ``` - fontFamilies: string + fontFamily: string ``` - The font families of the typography element. + The font family of the typography element. fontVariantId: |- ``` fontVariantId: string @@ -12294,7 +12616,7 @@ LocalStorage: Proxy for the local storage. Only elements owned by the plugin can be stored and accessed. Warning: other plugins won't be able to access this information but - the user could potentialy access the data through the browser information. + the user could potentially access the data through the browser information. ``` interface LocalStorage { @@ -12327,7 +12649,7 @@ LocalStorage: ``` Set the data given the key. If the value already existed it - will be overriden. The value will be stored in a string representation. + will be overridden. The value will be stored in a string representation. Requires the `allow:localstorage` permission. Parameters @@ -12620,6 +12942,7 @@ Page: name: string; rulerGuides: RulerGuide[]; root: Shape; + remove(): void; getShapeById(id: string): Shape | null; findShapes( criteria?: { @@ -12685,10 +13008,10 @@ Page: readonly rulerGuides: RulerGuide[] ``` - The ruler guides attached to the board + The ruler guides attached to the page root: |- ``` - root: Shape + readonly root: Shape ``` The root shape of the current page. Will be the parent shape of all the shapes inside the document. @@ -12700,6 +13023,22 @@ Page: The interaction flows defined for the page. Methods: + remove: |- + ``` + remove(): void + ``` + + Removes the page from the file. The last remaining page of the file + cannot be removed. If the removed page is the active one, another page + is activated. + Requires `content:write` permission. + + Returns void + + Example + ``` + const page = penpot.createPage();page.remove(); + ``` getShapeById: |- ``` getShapeById(id: string): Shape | null @@ -12740,7 +13079,7 @@ Page: ``` Finds all shapes on the page. - Optionaly it gets a criteria object to search for specific criteria + Optionally it gets a criteria object to search for specific criteria Parameters @@ -12815,9 +13154,15 @@ Page: Parameters * orientation: RulerGuideOrientation + + `horizontal` or `vertical` * value: number + + the position of the guide in absolute coordinates * board: Board + if provided, the guide will be attached to the board + Returns RulerGuide removeRulerGuide: |- ``` @@ -12836,8 +13181,7 @@ Page: addCommentThread(content: string, position: Point): Promise<CommentThread> ``` - Creates a new comment thread in the `position`. Optionaly adds - it into the `board`. + Creates a new comment thread in the `position`. Returns the thread created. Requires the `comment:write` permission. @@ -13046,6 +13390,7 @@ Path: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -13071,6 +13416,7 @@ Path: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -13132,6 +13478,7 @@ Path: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -13149,7 +13496,7 @@ Path: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -13174,7 +13521,7 @@ Path: content: string ``` - The content of the boolean shape, defined as the path string. + The content of the path shape, defined as the path string. Deprecated @@ -13184,13 +13531,13 @@ Path: d: string ``` - The content of the boolean shape, defined as the path string. + The content of the path shape, defined as the path string. commands: |- ``` commands: PathCommand[] ``` - The content of the boolean shape, defined as an array of path commands. + The content of the path shape, defined as an array of path commands. fills: |- ``` fills: Fill[] @@ -13253,17 +13600,13 @@ Path: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -13300,6 +13643,12 @@ Path: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -13370,6 +13719,14 @@ Path: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -13417,9 +13774,7 @@ Path: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -13743,12 +14098,27 @@ Path: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -13761,7 +14131,7 @@ Path: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -13830,7 +14200,7 @@ Path: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -13894,6 +14264,10 @@ Path: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -13933,7 +14307,7 @@ Path: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -13943,7 +14317,7 @@ Path: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -14339,7 +14713,7 @@ Push: readonly easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" ``` - Function that the dissolve effect will follow for the interpolation. + Function that the push effect will follow for the interpolation. Defaults to `linear` Rectangle: overview: |- @@ -14375,6 +14749,7 @@ Rectangle: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -14400,6 +14775,7 @@ Rectangle: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -14461,6 +14837,7 @@ Rectangle: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -14478,7 +14855,7 @@ Rectangle: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -14560,17 +14937,13 @@ Rectangle: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -14607,6 +14980,12 @@ Rectangle: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -14677,6 +15056,14 @@ Rectangle: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -14724,9 +15111,7 @@ Rectangle: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. strokes: |- ``` strokes: Stroke[] @@ -15036,12 +15421,27 @@ Rectangle: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -15054,7 +15454,7 @@ Rectangle: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -15123,7 +15523,7 @@ Rectangle: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -15187,6 +15587,10 @@ Rectangle: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -15226,7 +15630,7 @@ Rectangle: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -15236,7 +15640,7 @@ Rectangle: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -15278,6 +15682,7 @@ RulerGuide: orientation: RulerGuideOrientation; position: number; board?: Board; + remove(): void; } ``` @@ -15295,14 +15700,23 @@ RulerGuide: position: number ``` - `position` is the position in the axis in absolute positioning. If this is a board - guide will return the positioning relative to the board. + `position` is the position in the axis in absolute coordinates. If this is a board + guide it will return the position relative to the board. board: |- ``` board?: Board ``` If the guide is attached to a board this will retrieve the board shape + Methods: + remove: |- + ``` + remove(): void + ``` + + Removes the guide from its page. + + Returns void Shadow: overview: |- Interface Shadow @@ -15411,6 +15825,7 @@ ShapeBase: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -15436,6 +15851,7 @@ ShapeBase: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -15499,6 +15915,7 @@ ShapeBase: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -15516,7 +15933,7 @@ ShapeBase: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; } @@ -15591,17 +16008,13 @@ ShapeBase: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -15638,6 +16051,12 @@ ShapeBase: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -15708,6 +16127,14 @@ ShapeBase: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -15755,9 +16182,7 @@ ShapeBase: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. fills: |- ``` fills: Fill[] | "mixed" @@ -16073,12 +16498,27 @@ ShapeBase: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -16091,7 +16531,7 @@ ShapeBase: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -16160,7 +16600,7 @@ ShapeBase: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -16224,6 +16664,10 @@ ShapeBase: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -16263,7 +16707,7 @@ ShapeBase: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -16273,7 +16717,7 @@ ShapeBase: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -16358,7 +16802,7 @@ Slide: readonly easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" ``` - Function that the dissolve effect will follow for the interpolation. + Function that the slide effect will follow for the interpolation. Defaults to `linear`. Stroke: overview: |- @@ -16479,6 +16923,7 @@ SvgRaw: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -16504,6 +16949,7 @@ SvgRaw: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -16567,6 +17013,7 @@ SvgRaw: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -16584,7 +17031,7 @@ SvgRaw: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; type: "svg-raw"; @@ -16653,17 +17100,13 @@ SvgRaw: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -16700,6 +17143,12 @@ SvgRaw: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -16770,6 +17219,14 @@ SvgRaw: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -16817,9 +17274,7 @@ SvgRaw: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. fills: |- ``` fills: Fill[] | "mixed" @@ -16901,8 +17356,10 @@ SvgRaw: The interactions for the current shape. type: |- ``` - type: "svg-raw" + readonly type: "svg-raw" ``` + + The type of the shape, which is always 'svg-raw' for raw SVG shapes. Methods: getPluginData: |- ``` @@ -17139,12 +17596,27 @@ SvgRaw: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -17157,7 +17629,7 @@ SvgRaw: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -17226,7 +17698,7 @@ SvgRaw: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -17290,6 +17762,10 @@ SvgRaw: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -17329,7 +17805,7 @@ SvgRaw: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -17339,7 +17815,7 @@ SvgRaw: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -17400,6 +17876,7 @@ Text: proportionLock: boolean; constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; + fixedWhenScrolling: boolean; borderRadius: number; borderRadiusTopLeft: number; borderRadiusTopRight: number; @@ -17425,6 +17902,7 @@ Text: | "luminosity"; shadows: Shadow[]; blur?: Blur; + backgroundBlur?: Blur; exports: Export[]; boardX: number; boardY: number; @@ -17488,6 +17966,7 @@ Text: component(): LibraryComponent | null; detach(): void; swapComponent(component: LibraryComponent): void; + resetOverrides(): void; switchVariant(pos: number, value: string): void; combineAsVariants(ids: string[]): VariantContainer; isVariantHead(): boolean; @@ -17505,7 +17984,7 @@ Text: delay?: number, ): Interaction; removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; clone(): Shape; remove(): void; type: "text"; @@ -17592,17 +18071,13 @@ Text: readonly bounds: Bounds ``` - Returns - - Returns the bounding box surrounding the current shape + The bounding box surrounding the current shape center: |- ``` readonly center: Point ``` - Returns - - Returns the geometric center of the shape + The geometric center of the shape blocked: |- ``` blocked: boolean @@ -17639,6 +18114,12 @@ Text: ``` The vertical constraints applied to the shape. + fixedWhenScrolling: |- + ``` + fixedWhenScrolling: boolean + ``` + + Indicates whether the shape stays fixed in place while scrolling. borderRadius: |- ``` borderRadius: number @@ -17709,6 +18190,14 @@ Text: ``` The blur effect applied to the shape. + backgroundBlur: |- + ``` + backgroundBlur?: Blur + ``` + + The background blur effect applied to the shape. + Background blur creates a blur effect on the content behind the shape, + rather than on the shape's own content. exports: |- ``` exports: Export[] @@ -17756,9 +18245,7 @@ Text: rotation: number ``` - Returns - - Returns the rotation in degrees of the shape with respect to it's center. + The rotation in degrees of the shape with respect to its center. fills: |- ``` fills: Fill[] | "mixed" @@ -17938,7 +18425,7 @@ Text: verticalAlign: "center" | "top" | "bottom" | null ``` - The vertical alignment of the text shape. It can be a specific alignment or 'mixed' if multiple alignments are used. + The vertical alignment of the text shape. textBounds: |- ``` readonly textBounds: { x: number; y: number; width: number; height: number } @@ -18182,12 +18669,27 @@ Text: swapComponent(component: LibraryComponent): void ``` - TODO + Swaps the component instance for another component, keeping the overrides + when possible. Similar to the "swap component" action on the Penpot interface. + The current shape must be a component copy instance. Parameters * component: LibraryComponent + The new component to replace the current one + + Returns void + resetOverrides: |- + ``` + resetOverrides(): void + ``` + + Resets the overrides of the component copy, restoring all its attributes + (and those of its children) to the ones in the linked main component. + Similar to the "reset overrides" action on the Penpot interface. + The current shape must be a component copy instance. + Returns void switchVariant: |- ``` @@ -18200,7 +18702,7 @@ Text: * pos: number - The position of the poroperty to update + The position of the property to update * value: string The new value of the property @@ -18269,7 +18771,7 @@ Text: Angle in degrees to rotate. * center: { x: number; y: number } | null - Center of the transform rotation. If not send will use the geometri center of the shapes. + Center of the transform rotation. If not sent it will use the geometric center of the shape. Returns void @@ -18333,6 +18835,10 @@ Text: Adds a new interaction to the shape. + If the interaction starts a flow (for example a `navigate-to` action) and + the shape's board is not already part of any flow, a new flow starting at + that board is created automatically, matching the behavior of the editor. + Parameters * trigger: Trigger @@ -18372,7 +18878,7 @@ Text: ``` applyToken: |- ``` - applyToken(token: Token, properties: TokenProperty[] | undefined): void + applyToken(token: Token, properties?: TokenProperty[]): void ``` Applies one design token to one or more properties of the shape. @@ -18382,7 +18888,7 @@ Text: * token: Token is the Token to apply - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -18769,11 +19275,8 @@ TokenBase: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; } ``` @@ -18851,7 +19354,7 @@ TokenBase: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -18861,7 +19364,7 @@ TokenBase: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -18874,7 +19377,7 @@ TokenBase: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -18883,7 +19386,7 @@ TokenBase: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenBorderRadius: @@ -18902,11 +19405,8 @@ TokenBorderRadius: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "borderRadius"; value: string; resolvedValue: number | undefined; @@ -18995,7 +19495,7 @@ TokenBorderRadius: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19005,7 +19505,7 @@ TokenBorderRadius: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19018,7 +19518,7 @@ TokenBorderRadius: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19027,7 +19527,7 @@ TokenBorderRadius: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenShadowValue: @@ -19035,6 +19535,8 @@ TokenShadowValue: Interface TokenShadowValue ========================== + The value of a TokenShadow in its composite form. + ``` interface TokenShadowValue { color: string; @@ -19090,6 +19592,8 @@ TokenShadowValueString: Interface TokenShadowValueString ================================ + The value of a TokenShadow in its composite of strings form. + ``` interface TokenShadowValueString { color: string; @@ -19162,11 +19666,8 @@ TokenShadow: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "shadow"; value: string | TokenShadowValueString[]; resolvedValue: TokenShadowValue[] | undefined; @@ -19257,7 +19758,7 @@ TokenShadow: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19267,7 +19768,7 @@ TokenShadow: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19280,7 +19781,7 @@ TokenShadow: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19289,7 +19790,7 @@ TokenShadow: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenColor: @@ -19308,11 +19809,8 @@ TokenColor: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "color"; value: string; resolvedValue: string | undefined; @@ -19374,8 +19872,10 @@ TokenColor: readonly resolvedValue: string | undefined ``` - The value as defined in the token itself. - It's a rgb color or a reference. + The value calculated by finding all tokens with the same name in active sets + and resolving the references. + + It's a rgb color, or undefined if no value has been found in active sets. Methods: duplicate: |- ``` @@ -19399,7 +19899,7 @@ TokenColor: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19409,7 +19909,7 @@ TokenColor: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19422,7 +19922,7 @@ TokenColor: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19431,7 +19931,7 @@ TokenColor: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenDimension: @@ -19450,11 +19950,8 @@ TokenDimension: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "dimension"; value: string; resolvedValue: number | undefined; @@ -19543,7 +20040,7 @@ TokenDimension: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19553,7 +20050,7 @@ TokenDimension: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19566,7 +20063,7 @@ TokenDimension: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19575,7 +20072,7 @@ TokenDimension: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenFontFamilies: @@ -19594,11 +20091,8 @@ TokenFontFamilies: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "fontFamilies"; value: string | string[]; resolvedValue: string[] | undefined; @@ -19690,7 +20184,7 @@ TokenFontFamilies: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19700,7 +20194,7 @@ TokenFontFamilies: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19713,7 +20207,7 @@ TokenFontFamilies: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19722,7 +20216,7 @@ TokenFontFamilies: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenFontSizes: @@ -19741,11 +20235,8 @@ TokenFontSizes: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "fontSizes"; value: string; resolvedValue: number | undefined; @@ -19834,7 +20325,7 @@ TokenFontSizes: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19844,7 +20335,7 @@ TokenFontSizes: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -19857,7 +20348,7 @@ TokenFontSizes: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -19866,7 +20357,7 @@ TokenFontSizes: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenFontWeights: @@ -19885,11 +20376,8 @@ TokenFontWeights: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "fontWeights"; value: string; resolvedValue: string | undefined; @@ -19979,7 +20467,7 @@ TokenFontWeights: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -19989,7 +20477,7 @@ TokenFontWeights: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20002,7 +20490,7 @@ TokenFontWeights: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20011,7 +20499,7 @@ TokenFontWeights: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenLetterSpacing: @@ -20030,11 +20518,8 @@ TokenLetterSpacing: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "letterSpacing"; value: string; resolvedValue: number | undefined; @@ -20123,7 +20608,7 @@ TokenLetterSpacing: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20133,7 +20618,7 @@ TokenLetterSpacing: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20146,7 +20631,7 @@ TokenLetterSpacing: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20155,7 +20640,7 @@ TokenLetterSpacing: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenNumber: @@ -20174,11 +20659,8 @@ TokenNumber: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "number"; value: string; resolvedValue: number | undefined; @@ -20267,7 +20749,7 @@ TokenNumber: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20277,7 +20759,7 @@ TokenNumber: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20290,7 +20772,7 @@ TokenNumber: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20299,7 +20781,7 @@ TokenNumber: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenOpacity: @@ -20318,11 +20800,8 @@ TokenOpacity: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "opacity"; value: string; resolvedValue: number | undefined; @@ -20412,7 +20891,7 @@ TokenOpacity: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20422,7 +20901,7 @@ TokenOpacity: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20435,7 +20914,7 @@ TokenOpacity: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20444,7 +20923,7 @@ TokenOpacity: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenRotation: @@ -20463,11 +20942,8 @@ TokenRotation: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "rotation"; value: string; resolvedValue: number | undefined; @@ -20557,7 +21033,7 @@ TokenRotation: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20567,7 +21043,7 @@ TokenRotation: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20580,7 +21056,7 @@ TokenRotation: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20589,7 +21065,7 @@ TokenRotation: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenSizing: @@ -20608,11 +21084,8 @@ TokenSizing: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "sizing"; value: string; resolvedValue: number | undefined; @@ -20701,7 +21174,7 @@ TokenSizing: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20711,7 +21184,7 @@ TokenSizing: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20724,7 +21197,7 @@ TokenSizing: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20733,7 +21206,7 @@ TokenSizing: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenSpacing: @@ -20752,11 +21225,8 @@ TokenSpacing: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "spacing"; value: string; resolvedValue: number | undefined; @@ -20845,7 +21315,7 @@ TokenSpacing: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20855,7 +21325,7 @@ TokenSpacing: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -20868,7 +21338,7 @@ TokenSpacing: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -20877,7 +21347,7 @@ TokenSpacing: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenBorderWidth: @@ -20896,11 +21366,8 @@ TokenBorderWidth: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "borderWidth"; value: string; resolvedValue: number | undefined; @@ -20989,7 +21456,7 @@ TokenBorderWidth: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -20999,7 +21466,7 @@ TokenBorderWidth: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -21012,7 +21479,7 @@ TokenBorderWidth: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -21021,7 +21488,7 @@ TokenBorderWidth: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenTextCase: @@ -21040,11 +21507,8 @@ TokenTextCase: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "textCase"; value: string; resolvedValue: string | undefined; @@ -21134,7 +21598,7 @@ TokenTextCase: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -21144,7 +21608,7 @@ TokenTextCase: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -21157,7 +21621,7 @@ TokenTextCase: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -21166,7 +21630,7 @@ TokenTextCase: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenTextDecoration: @@ -21185,11 +21649,8 @@ TokenTextDecoration: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "textDecoration"; value: string; resolvedValue: string | undefined; @@ -21279,7 +21740,7 @@ TokenTextDecoration: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -21289,7 +21750,7 @@ TokenTextDecoration: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -21302,7 +21763,7 @@ TokenTextDecoration: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -21311,7 +21772,7 @@ TokenTextDecoration: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenTypographyValue: @@ -21319,6 +21780,8 @@ TokenTypographyValue: Interface TokenTypographyValue ============================== + The value of a TokenTypography in its composite form. + ``` interface TokenTypographyValue { letterSpacing: number; @@ -21381,6 +21844,8 @@ TokenTypographyValueString: Interface TokenTypographyValueString ==================================== + The value of a TokenTypography in its composite of strings form. + ``` interface TokenTypographyValueString { letterSpacing: string; @@ -21426,9 +21891,9 @@ TokenTypographyValueString: lineHeight: string ``` - The line height, as a number. Note that there not exists an individual - token type line height, only part of a Typography token. If you need to - put here a reference, use a NumberToken. + The line height, as a number. Note that there is no individual line-height + token type; it only exists as part of a Typography token. If you need to + put a reference here, use a Number token. textCase: |- ``` textCase: string @@ -21459,11 +21924,8 @@ TokenTypography: duplicate(): Token; remove(): void; resolvedValueString: string | undefined; - applyToShapes( - shapes: Shape[], - properties: TokenProperty[] | undefined, - ): void; - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; + applyToSelected(properties?: TokenProperty[]): void; type: "typography"; value: string | TokenTypographyValueString; resolvedValue: TokenTypographyValue[] | undefined; @@ -21554,7 +22016,7 @@ TokenTypography: Returns void applyToShapes: |- ``` - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void ``` Applies this token to one or more properties of the given shapes. @@ -21564,7 +22026,7 @@ TokenTypography: * shapes: Shape[] is an array of shapes to apply it. - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] an optional list of property names. If omitted, the default properties will be applied. @@ -21577,7 +22039,7 @@ TokenTypography: Returns void applyToSelected: |- ``` - applyToSelected(properties: TokenProperty[] | undefined): void + applyToSelected(properties?: TokenProperty[]): void ``` Applies this token to the currently selected shapes. @@ -21586,7 +22048,7 @@ TokenTypography: Parameters - * properties: TokenProperty[] | undefined + * properties: TokenProperty[] Returns void TokenCatalog: @@ -21607,7 +22069,7 @@ TokenCatalog: themes: TokenTheme[]; sets: TokenSet[]; addTheme(group: { group: string; name: string }): TokenTheme; - addSet(name: { name: string }): TokenSet; + addSet(name: { name: string; active?: boolean }): TokenSet; getThemeById(id: string): TokenTheme | undefined; getSetById(id: string): TokenSet | undefined; } @@ -21628,7 +22090,7 @@ TokenCatalog: ``` The list of sets in this catalog, in the order defined - by the user. The order is important because then same token name + by the user. The order is important because when the same token name exists in several active sets, the latter has precedence. Methods: addTheme: |- @@ -21649,14 +22111,19 @@ TokenCatalog: Returns the created TokenTheme. addSet: |- ``` - addSet(name: { name: string }): TokenSet + addSet(name: { name: string; active?: boolean }): TokenSet ``` Creates a new TokenSet and adds it to the catalog. + Newly created sets are **inactive** by default: only active sets + affect shapes and reference resolution. Pass `active: true` to create + an already-active set, or activate it later via `set.active = true` / + `set.toggleActive()`. + Parameters - * name: { name: string } + * name: { name: string; active?: boolean } The name of the set (required). It may contain a group path, separated by `/`. @@ -21796,7 +22263,7 @@ TokenSet: * type: { type: TokenType; name: string; value: TokenValueString } - Thetype of token. + The type of the token. Returns Token @@ -21829,9 +22296,9 @@ TokenTheme: sets that are *not* in this theme, because they may have been activated by other themes. - Themes may be gruped. At any time only one of the themes in a group + Themes may be grouped. At any time only one of the themes in a group may be active. But there may be active themes in other groups. This - allows to define multiple "axis" for theming (e.g. color scheme, + allows defining multiple "axis" for theming (e.g. color scheme, density or brand). When a TokenSet is activated or deactivated directly, all themes @@ -21847,8 +22314,8 @@ TokenTheme: active: boolean; toggleActive(): void; activeSets: TokenSet[]; - addSet(tokenSet: TokenSet): void; - removeSet(tokenSet: TokenSet): void; + addSet(tokenSet: string | TokenSet): void; + removeSet(tokenSet: string | TokenSet): void; duplicate(): TokenTheme; remove(): void; } @@ -21869,14 +22336,14 @@ TokenTheme: readonly externalId: string | undefined ``` - Optional identifier that may exists if the theme was imported from an + Optional identifier that may exist if the theme was imported from an external tool that uses ids in the json file. group: |- ``` group: string ``` - The group name of the theme. Can be empt string. + The group name of the theme. Can be an empty string. name: |- ``` name: string @@ -21891,7 +22358,7 @@ TokenTheme: Indicates if the theme is currently active. activeSets: |- ``` - activeSets: TokenSet[] + readonly activeSets: TokenSet[] ``` The sets that will be activated if this theme is activated. @@ -21906,26 +22373,30 @@ TokenTheme: Returns void addSet: |- ``` - addSet(tokenSet: TokenSet): void + addSet(tokenSet: string | TokenSet): void ``` Adds a set to the list of the theme. Parameters - * tokenSet: TokenSet + * tokenSet: string | TokenSet + + a `TokenSet` or the id of a token set. Returns void removeSet: |- ``` - removeSet(tokenSet: TokenSet): void + removeSet(tokenSet: string | TokenSet): void ``` Removes a set from the list of the theme. Parameters - * tokenSet: TokenSet + * tokenSet: string | TokenSet + + a `TokenSet` or the id of a token set. Returns void duplicate: |- @@ -22029,7 +22500,10 @@ Variants: Interface Variants ================== - TODO + Represents a Variant in Penpot: the grouping of all the VariantComponents that + belong to the same VariantContainer, together with their shared properties. + This interface provides attributes and actions that affect the Variant as a + whole (not a single VariantComponent). ``` interface Variants { @@ -22063,7 +22537,7 @@ Variants: The unique identifier of the library to which the variant belongs. properties: |- ``` - properties: string[] + readonly properties: string[] ``` A list with the names of the properties of the Variant @@ -22073,7 +22547,7 @@ Variants: currentValues(property: string): string[] ``` - A list of all the values of a property along all the variantComponents of this Variant + A list of all the values of a property across all the VariantComponents of this Variant Parameters @@ -22281,25 +22755,25 @@ Bounds: Properties: x: |- ``` - x: number + readonly x: number ``` Top-left x position of the rectangular area defined y: |- ``` - y: number + readonly y: number ``` Top-left y position of the rectangular area defined width: |- ``` - width: number + readonly width: number ``` Width of the represented area height: |- ``` - height: number + readonly height: number ``` Height of the represented area @@ -22415,37 +22889,37 @@ ImageData: Properties: name: |- ``` - name?: string + readonly name?: string ``` The optional name of the image. width: |- ``` - width: number + readonly width: number ``` The width of the image. height: |- ``` - height: number + readonly height: number ``` The height of the image. mtype: |- ``` - mtype?: string + readonly mtype?: string ``` The optional media type of the image (e.g., 'image/png', 'image/jpeg'). id: |- ``` - id: string + readonly id: string ``` The unique identifier for the image. keepAspectRatio: |- ``` - keepAspectRatio?: boolean + readonly keepAspectRatio?: boolean ``` Whether to keep the aspect ratio of the image when resizing. @@ -22456,7 +22930,7 @@ ImageData: data(): Promise<Uint8Array<ArrayBufferLike>> ``` - Returns the imaged data as a byte array. + Returns the image data as a byte array. Returns Promise<Uint8Array<ArrayBufferLike>> LibraryContext: @@ -22557,11 +23031,11 @@ Point: Properties: x: |- ``` - x: number + readonly x: number ``` y: |- ``` - y: number + readonly y: number ``` RulerGuideOrientation: overview: |- @@ -22572,6 +23046,8 @@ RulerGuideOrientation: RulerGuideOrientation: "horizontal" | "vertical" ``` + The possible orientations for a ruler guide: 'horizontal' or 'vertical'. + Referenced by: Board, Page, RulerGuide, VariantContainer members: {} Shape: @@ -22679,10 +23155,16 @@ TokenValueString: | TokenTypographyValueString | string | string[] + | number ``` Any possible type of value field in a token. + Token values are always stored as strings, including for numeric token + types such as `spacing`, `dimension` or `borderRadius` (e.g. `"16"` or + `"16px"`). A plain `number` is also accepted on input and coerced to its + string representation. + Referenced by: TokenSet members: {} Token: diff --git a/plugins/libs/plugin-types/package.json b/plugins/libs/plugin-types/package.json index e44dd187b9..f79cbe7d85 100644 --- a/plugins/libs/plugin-types/package.json +++ b/plugins/libs/plugin-types/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/plugin-types", - "version": "1.4.2", + "version": "1.5.0", "typings": "./index.d.ts", "type": "module", "scripts": { diff --git a/plugins/libs/plugins-runtime/package.json b/plugins/libs/plugins-runtime/package.json index cac1e949d5..bd56f9de75 100644 --- a/plugins/libs/plugins-runtime/package.json +++ b/plugins/libs/plugins-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/plugins-runtime", - "version": "1.4.2", + "version": "1.5.0", "dependencies": { "@penpot/plugin-types": "workspace:^", "ses": "^2.1.0", diff --git a/plugins/libs/plugins-styles/package.json b/plugins/libs/plugins-styles/package.json index f71775d820..2ba002b6f5 100644 --- a/plugins/libs/plugins-styles/package.json +++ b/plugins/libs/plugins-styles/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/plugin-styles", - "version": "1.4.2", + "version": "1.5.0", "dependencies": {}, "scripts": { "build": "node ../../tools/scripts/build-css.mjs", diff --git a/plugins/tools/scripts/build-css.mjs b/plugins/tools/scripts/build-css.mjs index 36434e482b..73be37f276 100644 --- a/plugins/tools/scripts/build-css.mjs +++ b/plugins/tools/scripts/build-css.mjs @@ -1,16 +1,19 @@ import esbuild from 'esbuild'; import { copy } from 'fs-extra'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; -const source = 'libs/plugins-styles'; -const dist = 'dist/plugins-styles'; +const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const source = resolve(root, 'libs/plugins-styles'); +const dist = resolve(root, 'dist/plugins-styles'); const handleErr = (err) => { console.error(err); process.exit(1); }; -esbuild - .build({ +Promise.all([ + esbuild.build({ entryPoints: [`${source}/src/lib/styles.css`], bundle: true, outfile: `${dist}/styles.css`, @@ -18,9 +21,8 @@ esbuild loader: { '.svg': 'dataurl', }, - }) - .catch(handleErr); - -copy(`${source}/package.json`, `${dist}/package.json`).catch(handleErr); -copy(`${source}/README.md`, `${dist}/README.md`).catch(handleErr); -copy(`LICENSE`, `${dist}/LICENSE`).catch(handleErr); + }), + copy(`${source}/package.json`, `${dist}/package.json`), + copy(`${source}/README.md`, `${dist}/README.md`), + copy(resolve(root, 'LICENSE'), `${dist}/LICENSE`), +]).catch(handleErr); diff --git a/plugins/tools/scripts/build-types.mjs b/plugins/tools/scripts/build-types.mjs index 999852cca1..3d5d739cc5 100644 --- a/plugins/tools/scripts/build-types.mjs +++ b/plugins/tools/scripts/build-types.mjs @@ -1,14 +1,19 @@ import { copy } from 'fs-extra'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; -const source = 'libs/plugin-types'; -const dist = 'dist/plugin-types'; +const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const source = resolve(root, 'libs/plugin-types'); +const dist = resolve(root, 'dist/plugin-types'); const handleErr = (err) => { console.error(err); process.exit(1); }; -copy(`${source}/package.json`, `${dist}/package.json`).catch(handleErr); -copy(`${source}/README.md`, `${dist}/README.md`).catch(handleErr); -copy(`${source}/index.d.ts`, `${dist}/index.d.ts`).catch(handleErr); -copy(`LICENSE`, `${dist}/LICENSE`).catch(handleErr); +Promise.all([ + copy(`${source}/package.json`, `${dist}/package.json`), + copy(`${source}/README.md`, `${dist}/README.md`), + copy(`${source}/index.d.ts`, `${dist}/index.d.ts`), + copy(resolve(root, 'LICENSE'), `${dist}/LICENSE`), +]).catch(handleErr); diff --git a/plugins/tools/scripts/publish.ts b/plugins/tools/scripts/publish.ts index 855ee3f13e..64125436fe 100644 --- a/plugins/tools/scripts/publish.ts +++ b/plugins/tools/scripts/publish.ts @@ -1,5 +1,5 @@ import { execSync } from 'child_process'; -import { readFileSync, writeFileSync } from 'fs'; +import { cpSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; @@ -66,6 +66,21 @@ const writePackageJson = (packagePath: string, content: PackageJson): void => { writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n'); }; +const getPublishPath = (packagePath: string): string => { + return join('dist', packagePath.split('/').pop()!); +}; + +const prepareRuntimePackage = (): void => { + const source = 'libs/plugins-runtime'; + const dist = getPublishPath(source); + + mkdirSync(dist, { recursive: true }); + cpSync(join(source, 'package.json'), join(dist, 'package.json')); + cpSync(join(source, 'README.md'), join(dist, 'README.md')); + cpSync('LICENSE', join(dist, 'LICENSE')); + cpSync(join(source, 'dist'), join(dist, 'dist'), { recursive: true }); +}; + const incrementVersion = ( currentVersion: string, specifier: string, @@ -164,6 +179,7 @@ const log = (message: string, verbose: boolean, forceLog = false): void => { stdio: 'inherit', }, ); + prepareRuntimePackage(); } else { console.log(' [DRY RUN] Skipping build\n'); } @@ -176,7 +192,7 @@ const log = (message: string, verbose: boolean, forceLog = false): void => { for (const packagePath of PACKAGES) { const pkg = readPackageJson(packagePath); - const distPath = join('dist', packagePath.split('/').pop()!); + const distPath = getPublishPath(packagePath); if (args.dryRun) { console.log( @@ -188,7 +204,7 @@ const log = (message: string, verbose: boolean, forceLog = false): void => { `pnpm publish --tag ${tag} --access public --no-git-checks`, { cwd: join(process.cwd(), distPath), - stdio: args.verbose ? 'inherit' : 'pipe', + stdio: 'inherit', }, ); console.log(` ✅ Published ${pkg.name}@${newVersion}`); diff --git a/tools/db-schema b/tools/db-schema new file mode 100755 index 0000000000..6cf2a8c710 --- /dev/null +++ b/tools/db-schema @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${PENPOT_DB_HOST:-postgres}" +USER="${PENPOT_DB_USER:-penpot}" +PASSWORD="${PENPOT_DB_PASSWORD:-penpot}" +DB="${PENPOT_DB_NAME:-penpot}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --test) + DB="penpot_test" + shift + ;; + --host|-h) + HOST="$2" + shift 2 + ;; + --user|-U) + USER="$2" + shift 2 + ;; + --db|-d) + DB="$2" + shift 2 + ;; + *) + break + ;; + esac +done + +export PGPASSWORD="$PASSWORD" +exec pg_dump -h "$HOST" -U "$USER" -d "$DB" --schema-only --no-owner --no-privileges "$@" diff --git a/tools/psql b/tools/psql new file mode 100755 index 0000000000..03dee3be19 --- /dev/null +++ b/tools/psql @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${PENPOT_DB_HOST:-postgres}" +USER="${PENPOT_DB_USER:-penpot}" +PASSWORD="${PENPOT_DB_PASSWORD:-penpot}" +DB="${PENPOT_DB_NAME:-penpot}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --test) + DB="penpot_test" + shift + ;; + --host|-h) + HOST="$2" + shift 2 + ;; + --user|-U) + USER="$2" + shift 2 + ;; + --db|-d) + DB="$2" + shift 2 + ;; + *) + break + ;; + esac +done + +export PGPASSWORD="$PASSWORD" +exec psql -h "$HOST" -U "$USER" -d "$DB" "$@"