mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 12:19:58 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2afbbb5cdd |
No files matched your search
@@ -1,91 +0,0 @@
|
||||
# Agent skills
|
||||
|
||||
This folder is the single home for the skills our coding agents use.
|
||||
Each skill is a folder with a `SKILL.md` inside — a short instruction
|
||||
manual that an agent loads only when it needs it.
|
||||
|
||||
One copy serves every tool:
|
||||
|
||||
- **opencode** reads this folder directly.
|
||||
- **Claude Code** reads it through the `.claude/skills` symlink.
|
||||
- **Codex** reads it directly.
|
||||
|
||||
To change how the agents behave, edit the `SKILL.md` here. There is no
|
||||
second copy to keep in sync.
|
||||
|
||||
## How the skills are organized
|
||||
|
||||
**Flows** are the six skills you invoke by name. Each one covers one step
|
||||
in the life of a change: plan it, review the plan, implement it, review
|
||||
the code, open the pull request.
|
||||
|
||||
**References** hold the quality standards. A flow's reviewer loads them;
|
||||
you rarely touch them directly.
|
||||
|
||||
**Procedures** define how one concrete step is done — a plan document, an
|
||||
issue, a commit. Flows call them, but they also work on their own.
|
||||
|
||||
**Utilities** are small helpers for everyday work: search, file lookup,
|
||||
JSON, REPL access, and so on.
|
||||
|
||||
## Flows
|
||||
|
||||
| Skill | What it does | When you would say |
|
||||
|---|---|---|
|
||||
| [`make-a-plan`](skills/make-a-plan/SKILL.md) | Researches the task, writes an implementation plan, asks you the open questions in plain language, and saves the plan to `.agents/plans/`. | "make a plan for the token refresh bug" |
|
||||
| [`review-plan`](skills/review-plan/SKILL.md) | Evaluates a plan before anyone writes code: completeness, ordering, risks. Approves it or asks for changes. | "review this plan before we start" |
|
||||
| [`implement-plan`](skills/implement-plan/SKILL.md) | Shows you the full flow first — the issue and branch it will create (or the branch it continues on), the execution style, and the task checklist — and, after your go-ahead, executes a ready plan. Default: every task, one commit. On request ("step by step"): one task, one commit, your confirmation between tasks. On request ("direct"): no issue and no branch, commits on the current branch. | "implement the plan" · "step by step, one commit per task" · "direct, no branch" |
|
||||
| [`review-code`](skills/review-code/SKILL.md) | Reviews a diff, branch, or PR and returns findings ranked by impact. | "review my changes before I push" |
|
||||
| [`create-pr`](skills/create-pr/SKILL.md) | Opens a pull request for the current branch — with checks on base branch, commits, issue, and push state — or updates an existing PR's title and description. | "open a PR for this branch" |
|
||||
| [`resolve-git-conflicts`](skills/resolve-git-conflicts/SKILL.md) | Untangles merge or rebase conflicts: explains both sides, proposes a resolution, applies it after you approve. Never runs `git rebase --continue`. | "resolve these conflicts" |
|
||||
|
||||
## References
|
||||
|
||||
| Skill | What it holds |
|
||||
|---|---|
|
||||
| [`plan-review-criteria`](skills/plan-review-criteria/SKILL.md) | The plan review rubric: six axes, severity levels, approval standard, output format. The `review-plan` reviewer loads it. |
|
||||
| [`code-review-criteria`](skills/code-review-criteria/SKILL.md) | The code review rubric: five axes, core principles (DRY, KISS, YAGNI), severity format, verdict. The `review-code` reviewer loads it. |
|
||||
|
||||
## Procedures
|
||||
|
||||
| Skill | What it does |
|
||||
|---|---|
|
||||
| [`planner`](skills/planner/SKILL.md) | The spec of a good plan: context, architecture decisions, tasks with acceptance criteria, checkpoints. Used by `make-a-plan`. |
|
||||
| [`create-issue`](skills/create-issue/SKILL.md) | Creates a GitHub issue that follows Penpot conventions. Used by `implement-plan`; also works on its own. |
|
||||
| [`create-commit`](skills/create-commit/SKILL.md) | Makes a commit the Penpot way: emoji subject, clear body, `AI-assisted-by` trailer. Used by `implement-plan`; also works alone when you say "commit this". |
|
||||
|
||||
## Utilities
|
||||
|
||||
| Skill | What it does |
|
||||
|---|---|
|
||||
| [`bat-cat`](skills/bat-cat/SKILL.md) | Read files in the terminal with syntax highlighting and line numbers. |
|
||||
| [`fd-find`](skills/fd-find/SKILL.md) | Find files by name or pattern, respecting `.gitignore`. |
|
||||
| [`ripgrep`](skills/ripgrep/SKILL.md) | Fast content search with regular expressions. |
|
||||
| [`jq-json-processor`](skills/jq-json-processor/SKILL.md) | Slice, filter, and reshape JSON output. |
|
||||
| [`nrepl-eval`](skills/nrepl-eval/SKILL.md) | Run Clojure or ClojureScript code in the live REPL sessions (backend and frontend). |
|
||||
| [`taiga`](skills/taiga/SKILL.md) | Look up Penpot issues, user stories, and tasks in Taiga. |
|
||||
| [`testing`](skills/testing/SKILL.md) | The repo's testing rules and TDD workflow, loaded before writing tests. |
|
||||
| [`local-ci`](skills/local-ci/SKILL.md) | Run CI-style lint, test, and format checks for the modules you touched with `scripts/ci`, and read the logs when they fail. |
|
||||
| [`security-and-hardening`](skills/security-and-hardening/SKILL.md) | Security checks for code that handles user input, auth, or external services. |
|
||||
| [`ste`](skills/ste/SKILL.md) | Rewrites prose in Simplified Technical English. Loads only when you name it. |
|
||||
| [`refine-prompt`](skills/refine-prompt/SKILL.md) | Rewrites a rough prompt into a clearer one. Never runs the prompt. |
|
||||
| [`update-changelog`](skills/update-changelog/SKILL.md) | Regenerates `CHANGES.md` from a GitHub milestone. |
|
||||
|
||||
## A typical round
|
||||
|
||||
1. `/make-a-plan` — you get a plan and a saved file in `.agents/plans/`.
|
||||
2. `/review-plan` — a second opinion; approve or request changes.
|
||||
3. `/implement-plan` — the code gets written and committed. Starting from a base branch, it also opens the GitHub issue and the `issue-NNNN` branch; the plans that follow continue on that same branch.
|
||||
4. `/review-code` — a reviewer checks the commit.
|
||||
5. `/create-pr` — the branch goes up as a pull request.
|
||||
|
||||
Every step also works on its own, and you can always say what you want
|
||||
in plain words — the agents pick the right skill from what you say.
|
||||
|
||||
## Adding or changing a skill
|
||||
|
||||
Create a folder here with a `SKILL.md` inside. The file needs `name` and
|
||||
`description` in its frontmatter, and a clear "When to use" section so
|
||||
agents know when to reach for it. Keep one job per skill, and keep the
|
||||
two families apart: flows are named with a verb first; reference skills
|
||||
end in `-criteria`.
|
||||
@@ -1,105 +0,0 @@
|
||||
---
|
||||
name: create-pr
|
||||
description: PR flow — open a new PR for the current task branch (validates base branch, commits, issue and push state) or update an existing PR's title or description to match Penpot conventions. Use it when the user asks to open or create a PR, in any phrasing.
|
||||
---
|
||||
|
||||
# Create PR
|
||||
|
||||
Two modes. **Open mode** takes the current task branch to a new, validated
|
||||
PR. **Update mode** rewrites an existing PR's title or description. Gather
|
||||
information, validate, and act in one pass. If validation fails, STOP with a
|
||||
single coherent message that lists every problem and states exactly what
|
||||
information is missing — never fix or work around problems silently.
|
||||
|
||||
Both modes require an authenticated `gh` CLI (`gh auth status`) and never
|
||||
push — the user pushes from their own shell.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to open or create a NEW PR for the current task branch, in
|
||||
any phrasing ("open a PR", "create the pull request", "put this up for
|
||||
review") — or runs `/create-pr`. → **Open mode**.
|
||||
- The user asks to fix or update an EXISTING PR's title or description to
|
||||
match conventions. → **Update mode**.
|
||||
|
||||
If the running agent cannot write (for example, the plan agent), say so and
|
||||
stop — this skill needs the build agent.
|
||||
|
||||
## Open mode
|
||||
|
||||
### 1. Gather context (read-only)
|
||||
|
||||
- Current branch: `git rev-parse --abbrev-ref HEAD`.
|
||||
- Target base branch: run `./scripts/detect-target-branch` from the repo root.
|
||||
It prints the nearest ancestor branch of HEAD (exit 0) or fails (exit 1).
|
||||
- Commits: `git log --oneline <base>..HEAD`.
|
||||
- Push state (local): `git rev-parse --verify origin/<branch>` and compare
|
||||
with HEAD. It reads the local remote-tracking ref — no network, no SSH. It
|
||||
reflects the last push or fetch this clone knows about.
|
||||
- Issue: from the session context, or from the branch name — `issue-NNNN`
|
||||
maps to issue NNNN; recover its title and body with `gh issue view NNNN`.
|
||||
|
||||
### 2. Validate — stop with one message if anything fails
|
||||
|
||||
Run all checks before reporting, then report every failure together:
|
||||
|
||||
1. **Base branch not usable.** If the script fails (exit 1), or its output —
|
||||
after stripping an optional `remotes/origin/` prefix — is not one of the
|
||||
canonical branches (`develop`, `staging`, `main`), stop and ask the user
|
||||
to re-run with more context — for example, passing the base branch
|
||||
explicitly in their invocation. An explicit base given by the user
|
||||
overrides the script's output.
|
||||
2. **On a base branch.** There is no task branch to merge — say so and stop.
|
||||
3. **No commits.** The branch has no commits ahead of the base — say so and
|
||||
stop.
|
||||
4. **No clear issue.** There is no issue in the session context, and the
|
||||
branch name has no `issue-NNNN` pattern (or `gh issue view` finds nothing)
|
||||
— say so and stop. Exception: the user's invocation says `no issue` /
|
||||
`without issue` — then continue without an issue reference.
|
||||
5. **Branch not pushed.** The remote-tracking ref `origin/<branch>` is
|
||||
missing, or `git rev-parse origin/<branch>` differs from HEAD — the
|
||||
branch was never pushed, or has commits the remote does not have. Never
|
||||
push yourself; ask the user to push and to run `/create-pr` again
|
||||
afterwards, then stop.
|
||||
|
||||
### 3. Already-open PR
|
||||
|
||||
Check whether a PR already exists for this branch (`gh pr list --head
|
||||
<branch>`). If one exists, report its URL and stop — do not create a second
|
||||
one. Title or description fixes belong to Update mode.
|
||||
|
||||
### 4. Write and create the PR
|
||||
|
||||
Write the title and body following `mem:workflow/creating-prs` (title format,
|
||||
description structure, writing principles) and `mem:workflow/creating-commits`
|
||||
(commit type emojis). Derive the title and body from the commits and, when
|
||||
there is one, from the issue body. Reference the issue with `Closes #NNNN`.
|
||||
|
||||
```bash
|
||||
gh pr create --repo penpot/penpot --title "<TITLE>" --body-file /tmp/pr-body.md
|
||||
```
|
||||
|
||||
### 5. Report
|
||||
|
||||
Report the PR URL and stop.
|
||||
|
||||
## Update mode
|
||||
|
||||
1. Identify the PR: the number given by the user, or `gh pr list --head
|
||||
<branch>`.
|
||||
2. Write the new title and/or body following `mem:workflow/creating-prs`.
|
||||
3. Apply and verify:
|
||||
|
||||
```bash
|
||||
gh pr edit <NUMBER> --repo penpot/penpot --title "<TITLE>" --body-file /tmp/pr-body.md
|
||||
gh pr view <NUMBER> --repo penpot/penpot --json title,body
|
||||
```
|
||||
|
||||
4. Report and stop.
|
||||
|
||||
## User context
|
||||
|
||||
Extra context in the user's invocation (the message that triggered this skill)
|
||||
plays the role command arguments play elsewhere: overrides such as `no issue` /
|
||||
`without issue`, an explicit base branch (`from origin/staging`), a PR number
|
||||
for Update mode, and so on.
|
||||
@@ -1,144 +0,0 @@
|
||||
---
|
||||
name: implement-plan
|
||||
description: Implementation flow — execute a ready plan from the session context: read the plan, detect the flow, then present the full picture (issue and branch to create or the branch to continue on, execution style, task checklist) and wait for confirmation. Default is every task with one final commit; on request ("step by step"), one task and one commit at a time with a pause after each; on request ("direct"), no issue and no branch — the commit lands on the current branch. Use it when the user asks to implement or execute a plan, in any phrasing.
|
||||
---
|
||||
|
||||
# Implement Plan
|
||||
|
||||
This flow is run once a plan is ready (for example, from plan mode). Execute
|
||||
the plan already prepared in the current session context. It never pushes —
|
||||
the user pushes.
|
||||
|
||||
By default it ends with exactly one commit. When the user asks for it
|
||||
("step by step"), it commits once per task instead and waits for the
|
||||
user's confirmation after each one (see *Execution modes*).
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to implement or execute a plan, in any phrasing:
|
||||
"implement the plan", "execute it", "go build it" — or runs
|
||||
`/implement-plan`.
|
||||
- A ready, reviewed plan is in the session context or a plan file path
|
||||
was given (typically after `/make-a-plan` or `/review-plan`).
|
||||
|
||||
Do not use it to produce plans — that is the `make-a-plan` flow.
|
||||
|
||||
## 1. Read the plan first
|
||||
|
||||
Identify the plan to execute — from the file path the user gave, the
|
||||
arguments, or the session context. Read it completely. Read the required
|
||||
memories before writing any code: `mem:critical-info` and the core memory
|
||||
of every module the plan touches, plus the deeper memories they reference
|
||||
(AGENTS.md governs this).
|
||||
|
||||
## 2. Detect the flow (no questions)
|
||||
|
||||
Inspect the current branch with `git rev-parse --abbrev-ref HEAD`, pick the
|
||||
mode, and announce it in one line before presenting anything. Detection is
|
||||
read-only: nothing is created until the user confirms (step 3).
|
||||
|
||||
- **On a base branch** (`main`, `develop`, `staging`) → **standalone mode**:
|
||||
a new GitHub issue and a branch `issue-NNNN` will be created after the
|
||||
user's confirmation.
|
||||
- **On any other branch** (a feature branch, typically `issue-NNNN`) →
|
||||
**continue mode**: the implementation continues on the current branch.
|
||||
No issue or branch is created. The branch name provides the issue
|
||||
reference when it follows the `issue-NNNN` pattern.
|
||||
|
||||
Arguments override detection: `standalone`, `continue`, `direct`
|
||||
(`no branch` / `direct commit`), `no issue` / `without issue`, or an
|
||||
explicit base such as `from origin/develop`.
|
||||
|
||||
**Direct mode** (`direct`, `no branch`, `direct commit`): no issue and
|
||||
no branch — the implementation and the commit land on the current branch
|
||||
as it is, even when it is a base branch. Best for small or tooling-only
|
||||
changes the user wants committed in place.
|
||||
|
||||
**Standalone while already on a feature branch:** stop and explain that this
|
||||
would stack branches. Ask the user to re-run with an explicit base, for
|
||||
example `from origin/develop` — then branch from that base instead of HEAD.
|
||||
|
||||
## 3. Present the checklist and wait
|
||||
|
||||
Before touching the repository, show the user the full picture:
|
||||
|
||||
- **The flow**: whether the GitHub issue and the branch will be created
|
||||
(standalone mode — give the planned branch name, `issue-NNNN` or
|
||||
`plan-<slug>`), whether you continue on the current branch
|
||||
(continue mode — name it), or whether everything lands on the current
|
||||
branch as it is (direct mode — name it, and say so when it is a base
|
||||
branch).
|
||||
- **The execution style**: batch or step-by-step (see *Execution modes*).
|
||||
- A checklist (todolist) of the plan's tasks, in order.
|
||||
|
||||
Then WAIT for the user's explicit confirmation. Do not start until you
|
||||
have it. If the plan has no discrete tasks, ask the user how to split
|
||||
it, or propose running it as a single change.
|
||||
|
||||
## 4. Execute the plan
|
||||
|
||||
**Standalone setup, after the confirmation:** create the issue with the
|
||||
**`create-issue`** skill, following the *Creating Issues from Draft Body*
|
||||
flow in `mem:workflow/creating-issues`. Derive the issue title and body
|
||||
from the plan, capture the new issue's number — call it **NNNN** — and
|
||||
create the branch from the current HEAD:
|
||||
|
||||
```
|
||||
git checkout -b issue-NNNN
|
||||
```
|
||||
|
||||
If the arguments say `no issue` / `without issue`, skip the issue and
|
||||
create a branch named `plan-<slug>` instead, where `<slug>` is the plan
|
||||
title, lowercase and hyphen-separated.
|
||||
|
||||
If the arguments say `direct` / `no branch` / `direct commit`, skip the
|
||||
issue and the branch: implement and commit on the current branch as it
|
||||
is. If it is a base branch, the checklist presentation already said so —
|
||||
no further confirmation is needed.
|
||||
|
||||
### Batch mode (default)
|
||||
|
||||
Implement every task in one go. Work methodically, keeping changes
|
||||
focused on what the issue requires. Respect the plan's proposed
|
||||
parallelization when it applies.
|
||||
|
||||
When the implementation is complete, load the **`create-commit`** skill
|
||||
and follow its workflow to commit the changes. Provide a brief summary
|
||||
of what was implemented and why, the issue reference (`issue-NNNN`) when
|
||||
there is one, and the model name you are running as so the
|
||||
`AI-assisted-by` trailer is set correctly.
|
||||
|
||||
### Step-by-step mode (on request)
|
||||
|
||||
When the user asks for it — "step by step", "task by task", "one commit
|
||||
per task" — loop one task at a time:
|
||||
|
||||
- Execute exactly ONE task.
|
||||
- Commit it now: load the **`create-commit`** skill and follow it —
|
||||
one commit per task, never two tasks in one commit. Same inputs as
|
||||
always: what and why, the issue reference, your model name.
|
||||
- Show the user the result (what changed, files touched, how it was
|
||||
verified).
|
||||
- WAIT for the user's confirmation before starting the next task.
|
||||
|
||||
Never batch in this mode: no two tasks in one commit, and no new task
|
||||
before the user confirms. If a task turns out much bigger than planned,
|
||||
stop and ask the user before splitting it.
|
||||
|
||||
## When you are done
|
||||
|
||||
End by suggesting the next steps (suggestions, not a required pipeline — any
|
||||
instruction from me overrides them):
|
||||
|
||||
- `/review-code` — to review the changes just committed; it routes to
|
||||
`/make-a-plan` by itself if the findings need one.
|
||||
- `/create-pr` — when the task is done and the branch is ready to merge.
|
||||
|
||||
## User context
|
||||
|
||||
Extra context in the user's invocation (the message that triggered this
|
||||
skill) plays the role command arguments play elsewhere: `standalone`,
|
||||
`continue`, `direct` (`no branch` / `direct commit`), `no issue` /
|
||||
`without issue`, an explicit base such as `from origin/develop`, or
|
||||
`step by step` / `one commit per task` for the step-by-step execution
|
||||
mode. Modes combine freely, for example "standalone step by step".
|
||||
@@ -1,95 +0,0 @@
|
||||
---
|
||||
name: local-ci
|
||||
description: Run local CI-style checks with ./scripts/ci (lint, tests, format) per monorepo module. Use when verifying changes before declaring work done, running lint or tests locally, fixing formatting, or repairing Clojure delimiter errors.
|
||||
---
|
||||
|
||||
# Local CI
|
||||
|
||||
Run the same checks CI runs, locally, for the modules you touched, with
|
||||
`scripts/ci`. Each task writes a log file; the final summary says what
|
||||
passed and what failed.
|
||||
|
||||
Full details: `mem:scripts/ci` (file: `.serena/memories/scripts/ci.md`)
|
||||
|
||||
## When to use
|
||||
|
||||
- After implementing or fixing code — verify every module you touched
|
||||
before declaring the work done.
|
||||
- When the user asks to run CI, lint, tests, or format checks locally.
|
||||
- When you changed `common/` — validate its consumers too.
|
||||
|
||||
**Skip:** while exploring, planning, or reading code.
|
||||
|
||||
## Command reference
|
||||
|
||||
Run from the repo root:
|
||||
|
||||
```bash
|
||||
./scripts/ci [OPTIONS] [MODULES...]
|
||||
```
|
||||
|
||||
Modules: `frontend` `backend` `common` `render-wasm` `exporter` `mcp`
|
||||
`plugins` `library`, or `--all` for every module.
|
||||
|
||||
With no task flags it runs three tasks per module, in order: **lint**,
|
||||
**test**, **fmt** (format check; `--fix` formats files instead).
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--all` | Run every module |
|
||||
| `--exclude MOD` | Skip one module (repeatable) |
|
||||
| `--lint` / `--no-lint` | Run only lint / drop lint |
|
||||
| `--test` / `--no-test` | Run only tests / drop tests |
|
||||
| `--fmt` / `--no-fmt` | Run only format check / drop it |
|
||||
| `--fix` | Format files instead of checking (other tasks unaffected) |
|
||||
| `--paren-repair` | Fix delimiter errors in Clojure/CLJS files |
|
||||
| `--fail-fast` | Stop at the first failure |
|
||||
| `--quiet` | Suppress failure output |
|
||||
| `--dry-run` | Show what would run, execute nothing |
|
||||
| `--clean` | Delete the `.ci-logs/` directory |
|
||||
|
||||
## Reading failures
|
||||
|
||||
Every task writes its full output to `.ci-logs/<module>-<task>.log`. On
|
||||
failure the script prints only the last 30 lines. To diagnose a failure,
|
||||
**read the log file** — never re-run the command piped through filters
|
||||
(repo rule: redirect to a file first, then read it). The exit code is 1
|
||||
when any task failed; the summary lists each failed `module:task` and its
|
||||
log path.
|
||||
|
||||
## Typical workflows
|
||||
|
||||
```bash
|
||||
# Verify a module you changed: lint + tests + format check
|
||||
./scripts/ci frontend
|
||||
|
||||
# Fast pass while iterating: lint only
|
||||
./scripts/ci --lint frontend
|
||||
|
||||
# Lint + format check, skip the long test suite
|
||||
./scripts/ci --no-test frontend
|
||||
|
||||
# Format the module without running the test suite
|
||||
./scripts/ci --fix --no-test frontend
|
||||
|
||||
# Broke delimiters in Clojure/CLJS files: repair first, then lint
|
||||
./scripts/ci --paren-repair frontend
|
||||
./scripts/ci --lint frontend
|
||||
|
||||
# Changed common/ — validate its consumers too
|
||||
./scripts/ci frontend backend exporter
|
||||
|
||||
# Preview what would run, without running it
|
||||
./scripts/ci --dry-run --all
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Run from the repo root.
|
||||
- Test tasks are long-running (backend runs `clojure -M:dev:test`); give
|
||||
the bash call a generous timeout (10–20 minutes) instead of letting it
|
||||
time out mid-run.
|
||||
- `mcp` has no lint task — it shows as skipped, not failed.
|
||||
- `--paren-repair` only fixes delimiters; run lint afterwards to catch
|
||||
what remains. See `mem:scripts/paren-repair`.
|
||||
- What to run and how to read test results: `mem:testing`.
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
name: make-a-plan
|
||||
description: Planning flow — research the subject of this session, produce an implementation plan with the planner skill, resolve open questions with the user in plain language, and save the final plan to .agents/plans/. Use it when the user asks to plan, design, or break down a task, in any phrasing.
|
||||
---
|
||||
|
||||
# Make a Plan
|
||||
|
||||
Act as a senior software engineer: research the subject of this session in depth and
|
||||
produce a well-grounded, actionable implementation plan.
|
||||
|
||||
If the running agent cannot write (for example, the plan agent), say so and
|
||||
stop — this skill needs the build agent to save the plan.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to plan, design, or break down a task, in any phrasing:
|
||||
"make a plan", "how would we build X", "design an approach for Y" —
|
||||
or runs `/make-a-plan`.
|
||||
- The user asks to rework or extend an existing plan (for example, after
|
||||
review findings) — revise the saved plan file in place.
|
||||
|
||||
Do not use it to execute a plan — that is the `implement-plan` flow.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. **Produce the plan** with the `planner` skill. By default, research the
|
||||
subject of this session and draft the plan yourself. If I ask for it (for
|
||||
example, `delegated` in the user context), delegate to the `general` subagent
|
||||
instead — the delegate must also follow the `planner` skill and receive all
|
||||
the relevant session context (a review, user feedback, and so on).
|
||||
2. Before asking me to decide anything, explain the plan and every open question in
|
||||
plain language. Assume I know only the high-level project goal, not the codebase,
|
||||
architecture, implementation terms, or the problem this task solves.
|
||||
3. Once all decisions are answered and the plan is final, save it verbatim to the
|
||||
announced path under `.agents/plans/` (create the directory if it does not
|
||||
exist). This step is the flow's explicit authorization to write the plan
|
||||
file — the only write allowed here. If I later ask for changes, update the
|
||||
saved file directly.
|
||||
4. Present me with a clear, self-contained summary of the plan's most relevant points
|
||||
only after all required decisions have been answered. Write it for someone who knows
|
||||
only the project's high-level goal and may not know the plan's low-level context.
|
||||
Explain necessary technical language in plain terms, include the problem being
|
||||
solved and the proposed outcome, and do not assume that listing technical task names
|
||||
is enough.
|
||||
|
||||
### Hard rule — read-only while planning
|
||||
|
||||
While this flow runs, act read-only: research with read-only tools only.
|
||||
Never edit source files, never run builds, tests, linters, or any command that
|
||||
modifies state, and never commit. The single allowed write is the plan file in
|
||||
step 3. This rule expires when I approve the plan or move on to another task;
|
||||
then you act as a normal build agent again.
|
||||
|
||||
When the plan contains open questions, do not show them as bare technical questions or
|
||||
assume that I understand the technical language or technical words used in the plan.
|
||||
For each question, first explain:
|
||||
|
||||
- What part of the user problem the decision affects.
|
||||
- The relevant concept from the beginning, with a small concrete example.
|
||||
- What each available option would make the system do.
|
||||
- The practical benefits, costs, risks, and user-visible consequences of each option.
|
||||
- Which option the planner recommends and why.
|
||||
|
||||
Only after that explanation, use the `question` tool to ask the decision with clear,
|
||||
non-technical option labels. Put the recommended option first and mark it as
|
||||
`(Recommended)`. Group related questions when their context is shared, but do not ask a
|
||||
question whose meaning has not already been explained.
|
||||
|
||||
If I say that I do not understand a question or its choices, do not treat my previous
|
||||
answer as valid. Explain the concepts again from the high-level project goal, use a more
|
||||
concrete example, explain the implications, and ask the question again with the
|
||||
`question` tool. Repeat this until I can make an informed choice. If one answer creates
|
||||
new design consequences or additional decisions, explain those consequences before
|
||||
asking any new question.
|
||||
|
||||
Distinguish clearly between requirements already fixed by the roadmap or existing
|
||||
architecture and choices that actually require my input. Do not ask me to choose an
|
||||
implementation detail when the plan can resolve it safely without changing the public
|
||||
behavior. If there are no decisions that require my input, say so and present the
|
||||
summary.
|
||||
|
||||
IMPORTANT: **Under no circumstances execute the plan. Wait for the user to review it
|
||||
after all possible questions have been answered.** The final summary must explain the
|
||||
problem being solved, the proposed behavior, the main user-visible workflow, important
|
||||
constraints and risks, what is deliberately out of scope, and the path where the plan
|
||||
is saved. Never assume that a short list of task names is enough context. End
|
||||
the final response by suggesting the next steps, in this order:
|
||||
|
||||
1. `/review-plan` — to get a second opinion on the plan before executing it.
|
||||
2. `/implement-plan` — to execute the plan from the current session context.
|
||||
|
||||
These are suggestions, not a required pipeline — any instruction from me
|
||||
overrides them (for example, asking you to implement the plan directly).
|
||||
|
||||
## User context
|
||||
|
||||
Extra context in the user's invocation (the message that triggered this skill)
|
||||
plays the role command arguments play elsewhere: for example, `delegated` to
|
||||
hand the research and drafting to the `general` subagent, or corrections and
|
||||
feedback about a previous plan.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: resolve-git-conflicts
|
||||
description: Conflict resolution flow — understand the local git conflicts, present a resolution plan, and resolve them after the user approves it. Never continues the rebase. Use it when the repo has unresolved conflicts (rebase, merge, cherry-pick) or the user asks to resolve them.
|
||||
---
|
||||
|
||||
# Resolve Git Conflicts
|
||||
|
||||
Resolve conflicts in the local repository. The user handles finishing the
|
||||
rebase themselves — you must **never** run `git rebase --continue`,
|
||||
`git rebase --skip`, `git merge --continue`, or anything similar.
|
||||
|
||||
## When to use
|
||||
|
||||
- The repository has unresolved conflicts — during a rebase, merge, or
|
||||
cherry-pick — whether the user asks about them or not.
|
||||
- The user asks to resolve conflicts, in any phrasing: "fix the merge
|
||||
conflicts", "resolve these", "what's conflicting here?".
|
||||
|
||||
## Phase 1 — Understand the problem (read-only)
|
||||
|
||||
1. Run `git status` to detect the conflict state (rebase, merge, cherry-pick, etc.) and list conflicted files.
|
||||
2. For each conflicted (unmerged) file, understand the situation **without modifying anything**:
|
||||
- Read the file and identify the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`).
|
||||
- Inspect both sides — `git show <ours>:<file>` and `git show <theirs>:<file>` — plus `git log`/`git show` on the commits involved to understand intent.
|
||||
- Identify what each side changed and why, and how they should be combined.
|
||||
|
||||
## Phase 2 — Present the resolution plan
|
||||
|
||||
3. **Present a clear plan to the user before touching any file.** For each conflicted file, state:
|
||||
- What each side changed and why.
|
||||
- Your proposed resolution and the reasoning behind it.
|
||||
- How the two sides are combined (both additive → merge; both modify the same code → keep the semantically correct version, merging intent from both sides when clear from code and context).
|
||||
4. **Ask the user only when genuinely unclear.** Do not ask about anything you can determine yourself from the code, commit messages, or context. Only decisions that are not determinable and change the outcome (e.g. conflicting product decisions, which side to discard) warrant a question. **Collect all such questions together in an "Open Questions" section at the end of the plan**, so the user has full context to answer them properly.
|
||||
5. **Wait for the user to accept the plan** (and answer any open questions) before editing, staging, or otherwise modifying anything.
|
||||
|
||||
## Phase 3 — Execute
|
||||
|
||||
6. Resolve each conflicted file by editing the file to the agreed merged content and removing all conflict markers.
|
||||
|
||||
## Phase 4 — Stage and verify
|
||||
|
||||
7. **Stage every resolved file** with `git add <file>`. Do not stage unrelated untracked files unless clearly part of the resolution.
|
||||
8. Verify no conflict markers remain (search for `<<<<<<<` / `>>>>>>>` in resolved files) and that `git status` shows no unmerged paths.
|
||||
|
||||
## Phase 5 — Report
|
||||
|
||||
9. Briefly report the conflict state, how each conflicted file was resolved (and any answers received to open questions), and stop — do **not** run `git rebase --continue` or any other continuation command.
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
name: review-code
|
||||
description: Code review flow — review a diff, PR, or code change, delegating the review to a subagent that follows the code-review-criteria skill. Use it when the user asks to review code or a PR, in any phrasing.
|
||||
---
|
||||
|
||||
# Review Code
|
||||
|
||||
Act as a senior software engineer and perform a thorough code review.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to review code, in any phrasing: "review this diff",
|
||||
"review the PR", "check my changes", "code review" — or runs
|
||||
`/review-code`.
|
||||
- A commit, branch, PR, or diff is ready and the user wants it assessed
|
||||
before merge.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. **Determine what is being reviewed** from the user context: a working-tree
|
||||
diff, a commit range, a branch, a PR (number or URL), or specific files. If
|
||||
the target is ambiguous, ask before reviewing.
|
||||
2. Delegate the review to the `general` subagent (via the task tool), unless the
|
||||
user specifies another agent. Include in the prompt the
|
||||
**`code-review-criteria`** skill name and all user context.
|
||||
3. When the subagent returns, output the review to the user verbatim. Do not
|
||||
summarize it and do not act on its findings.
|
||||
4. Right after the review, suggest how to proceed based on the findings. These
|
||||
are suggestions — the user decides:
|
||||
- **Approve (no required changes):** say so — there is nothing to address.
|
||||
- **Minor findings (nits):** applying them directly as-is is fine once the
|
||||
review is done — no plan needed.
|
||||
- **Substantive findings:** suggest `/make-a-plan` to make a plan to address
|
||||
them.
|
||||
|
||||
### Hard rule — read-only while reviewing
|
||||
|
||||
This flow is read-only **for the duration of the review**: from the moment it
|
||||
starts until the user considers the review finished (including any feedback,
|
||||
questions, or clarifications about it). During that period, never fix,
|
||||
implement, edit files or create commits — not even "obvious" fixes derived from
|
||||
the findings. Once the user explicitly states the review is done (or moves on to
|
||||
a different task), this rule no longer applies and you act as a normal build
|
||||
agent again.
|
||||
|
||||
## Instructions for the subagent
|
||||
|
||||
1. Load the **`code-review-criteria`** skill and follow its process and output
|
||||
format.
|
||||
2. Read `AGENTS.md` (if present) and follow its instructions for finding and
|
||||
reading all related testing documentation from memories before reviewing.
|
||||
3. Return in your final message the COMPLETE review, verbatim, exactly as the
|
||||
skill instructs it to be produced. Do not summarize it — include the full
|
||||
structured review.
|
||||
|
||||
### Strong rules for the subagent
|
||||
|
||||
1. Do not invent problems. Every finding must be real and actionable.
|
||||
2. Read-only: do not modify any file and do not create a commit — reviewing
|
||||
never writes.
|
||||
3. Be specific and constructive. "This could be better" is not helpful — explain
|
||||
why and how.
|
||||
4. Prioritize by impact. One structural issue outweighs ten nits.
|
||||
5. Missing tests are an issue, not a suggestion. Report as a severity-tagged
|
||||
finding — never as a recommendation.
|
||||
6. Skip generated files, lockfile-only changes, and unrelated modifications
|
||||
unless they introduce security risks.
|
||||
|
||||
## User context
|
||||
|
||||
Extra context in the user's invocation (the message that triggered this skill)
|
||||
plays the role command arguments play elsewhere: for example, a PR number or
|
||||
URL, a commit range, specific files, or a different agent to run the review.
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
name: review-plan
|
||||
description: Plan review flow — evaluate an implementation plan before it is executed, delegating the review to a subagent that follows the plan-review-criteria skill. Use it when the user asks to review a plan, in any phrasing.
|
||||
---
|
||||
|
||||
# Review Plan
|
||||
|
||||
Act as a senior software engineer and perform a thorough review of an
|
||||
implementation plan.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to review a plan, in any phrasing: "review this plan",
|
||||
"does this plan look right?", "second opinion on the plan" — or runs
|
||||
`/review-plan`.
|
||||
- A plan was just produced (typically by `/make-a-plan`) and the user
|
||||
wants it evaluated before executing it.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. **Determine the plan under review** from the session context (for example, a
|
||||
plan just produced by `/make-a-plan`) or from a plan file path given by the
|
||||
user (typically under `.agents/plans/`). If a file path is given, read the
|
||||
file first so the complete plan is in context.
|
||||
2. Delegate the review to the `general` subagent (via the task tool), unless the
|
||||
user specifies another agent. Include in the prompt the
|
||||
**`plan-review-criteria`** skill name and all user context.
|
||||
3. When the subagent returns, output the review to the user verbatim. Do not
|
||||
summarize it and do not act on its findings.
|
||||
4. Right after the review, suggest the next step based on the verdict. These
|
||||
are suggestions — the user decides, and any instruction overrides them:
|
||||
- **Approve** → suggest `/implement-plan` to execute it.
|
||||
- **Request changes** → suggest `/make-a-plan` to make a plan to address the
|
||||
findings.
|
||||
|
||||
### Hard rule — read-only while reviewing
|
||||
|
||||
This flow is read-only **for the duration of the review**: from the moment it
|
||||
starts until the user considers the review finished (including any feedback,
|
||||
questions, or clarifications about it). During that period, never fix,
|
||||
implement, edit files or create commits — not even "obvious" fixes derived from
|
||||
the findings. Once the user explicitly states the review is done (or moves on to
|
||||
a different task), this rule no longer applies and you act as a normal build
|
||||
agent again.
|
||||
|
||||
## Instructions for the subagent
|
||||
|
||||
1. Load the **`plan-review-criteria`** skill and follow its process and output
|
||||
format.
|
||||
2. Read `AGENTS.md` (if present) and follow its instructions for finding and
|
||||
reading all related documentation and testing memories before reviewing.
|
||||
3. Return in your final message the COMPLETE review, verbatim, exactly as the
|
||||
skill instructs it to be produced. Do not summarize it — include the full
|
||||
structured review.
|
||||
|
||||
### Strong rules for the subagent
|
||||
|
||||
1. Do not invent problems. Every finding must be real and actionable.
|
||||
2. Read-only: do not modify any file and do not create a commit — reviewing
|
||||
never writes.
|
||||
3. Be specific and constructive. "This could be better" is not helpful — explain
|
||||
why and how.
|
||||
4. Prioritize by impact. One structural issue outweighs ten nits.
|
||||
5. Judge the plan as the implementer would: every task executable without
|
||||
guessing, ordering follows the dependency graph, risks named.
|
||||
|
||||
## User context
|
||||
|
||||
Extra context in the user's invocation (the message that triggered this skill)
|
||||
plays the role command arguments play elsewhere: for example, a plan file path
|
||||
to review, or a different agent to run the review.
|
||||
@@ -1 +0,0 @@
|
||||
../.agents/skills
|
||||
+1
-1
@@ -101,7 +101,7 @@ opencode.json
|
||||
/.playwright-mcp
|
||||
/.devenv/mcp/
|
||||
/opencode.json
|
||||
/.agents/plans
|
||||
/.opencode/plans
|
||||
/.opencode/reports
|
||||
/.opencode/prompts
|
||||
/.ci-logs
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
description: Create a PR for the current task branch or update an existing one — loads and follows the create-pr skill
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`create-pr`** skill and follow it as your only instruction.
|
||||
|
||||
## User input, overrides and additional context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -1,10 +1,39 @@
|
||||
---
|
||||
description: Execute a ready plan — task checklist, your confirmation, then all tasks with one commit (default) or step by step with a commit and a pause per task; creates issue + branch when on a base branch, or commits on the current branch with "direct" — loads and follows the implement-plan skill
|
||||
description: Execute a ready plan end-to-end — create a GitHub issue, branch issue-NNNN, implement the plan, then commit via the create-commit skill
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`implement-plan`** skill and follow it as your only instruction.
|
||||
This command is run once a plan is ready (for example, from plan mode). Execute
|
||||
the plan already prepared in the current session context. Follow these steps in order.
|
||||
|
||||
## User input, overrides and additional context
|
||||
## 1. Create the issue
|
||||
|
||||
$ARGUMENTS
|
||||
Use the **`create-issue`** skill, following the *Creating Issues from Draft Body*
|
||||
flow in `mem:workflow/creating-issues`. Derive the issue title and body from the
|
||||
plan. Capture the new issue's number — call it **NNNN** (needed for the branch
|
||||
name and the commit reference).
|
||||
|
||||
## 2. Create the branch
|
||||
|
||||
Create and switch to a branch named after the issue:
|
||||
|
||||
```
|
||||
git checkout -b issue-NNNN
|
||||
```
|
||||
|
||||
(Replace NNNN with the issue number from step 1.)
|
||||
|
||||
## 3. Execute the plan
|
||||
|
||||
Implement the prepared plan from the session context. Work methodically, keeping
|
||||
changes focused on what the issue requires. Do not commit — the commit happens in
|
||||
step 4.
|
||||
|
||||
## 4. Commit with the create-commit skill
|
||||
|
||||
After the implementation is complete, load the **`create-commit`** skill and
|
||||
follow its workflow to commit the changes. Provide a brief summary of what was
|
||||
implemented and why, the issue reference (`issue-NNNN`), and the model name you
|
||||
are running as so the `AI-assisted-by` trailer is set correctly.
|
||||
|
||||
Do not push. Pushing is handled separately by the user.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
description: Investigate the chosen task, produce an implementation plan, and save it — loads and follows the make-a-plan skill
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`make-a-plan`** skill and follow it as your only instruction.
|
||||
|
||||
## User input, overrides and additional context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -1,6 +1,40 @@
|
||||
---
|
||||
description: Resolve local git conflicts and stage the resolved files; never continues the rebase — loads and follows the resolve-git-conflicts skill
|
||||
description: Resolve local git conflicts and stage the resolved files with git add — never continues the rebase
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`resolve-git-conflicts`** skill and follow it as your only instruction.
|
||||
# Fix Git Conflicts
|
||||
|
||||
Resolve conflicts in the local repository. The user handles finishing the
|
||||
rebase themselves — you must **never** run `git rebase --continue`,
|
||||
`git rebase --skip`, `git merge --continue`, or anything similar.
|
||||
|
||||
## Phase 1 — Understand the problem (read-only)
|
||||
|
||||
1. Run `git status` to detect the conflict state (rebase, merge, cherry-pick, etc.) and list conflicted files.
|
||||
2. For each conflicted (unmerged) file, understand the situation **without modifying anything**:
|
||||
- Read the file and identify the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`).
|
||||
- Inspect both sides — `git show <ours>:<file>` and `git show <theirs>:<file>` — plus `git log`/`git show` on the commits involved to understand intent.
|
||||
- Identify what each side changed and why, and how they should be combined.
|
||||
|
||||
## Phase 2 — Present the resolution plan
|
||||
|
||||
3. **Present a clear plan to the user before touching any file.** For each conflicted file, state:
|
||||
- What each side changed and why.
|
||||
- Your proposed resolution and the reasoning behind it.
|
||||
- How the two sides are combined (both additive → merge; both modify the same code → keep the semantically correct version, merging intent from both sides when clear from code and context).
|
||||
4. **Ask the user only when genuinely unclear.** Do not ask about anything you can determine yourself from the code, commit messages, or context. Only decisions that are not determinable and change the outcome (e.g. conflicting product decisions, which side to discard) warrant a question. **Collect all such questions together in an "Open Questions" section at the end of the plan**, so the user has full context to answer them properly.
|
||||
5. **Wait for the user to accept the plan** (and answer any open questions) before editing, staging, or otherwise modifying anything.
|
||||
|
||||
## Phase 3 — Execute
|
||||
|
||||
6. Resolve each conflicted file by editing the file to the agreed merged content and removing all conflict markers.
|
||||
|
||||
## Phase 4 — Stage and verify
|
||||
|
||||
7. **Stage every resolved file** with `git add <file>`. Do not stage unrelated untracked files unless clearly part of the resolution.
|
||||
8. Verify no conflict markers remain (search for `<<<<<<<` / `>>>>>>>` in resolved files) and that `git status` shows no unmerged paths.
|
||||
|
||||
## Phase 5 — Report
|
||||
|
||||
9. Briefly report the conflict state, how each conflicted file was resolved (and any answers received to open questions), and stop — do **not** run `git rebase --continue` or any other continuation command.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
description: Code review — review a diff, PR, or code change — loads and follows the review-code skill
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`review-code`** skill and follow it as your only instruction.
|
||||
|
||||
## User input, overrides and additional context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
description: Plan review — evaluate an implementation plan before executing it — loads and follows the review-plan skill
|
||||
agent: build
|
||||
---
|
||||
|
||||
Load the **`review-plan`** skill and follow it as your only instruction.
|
||||
|
||||
## User input, overrides and additional context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -9,11 +9,6 @@ metadata: {"clawdbot":{"emoji":"🦇","requires":{"bins":["bat"]},"install":[{"i
|
||||
|
||||
`cat` with syntax highlighting, line numbers, and Git integration.
|
||||
|
||||
## When to use
|
||||
|
||||
- Reading or displaying a file in the terminal — prefer it over plain
|
||||
`cat`: syntax highlighting, line numbers, git-side indicators.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic usage
|
||||
+8
-7
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: code-review-criteria
|
||||
description: Code review criteria — the five review axes, core principles, severity format, and verdict for reviewing code changes. Loaded by the reviewer subagent of the review-code flow. Not a user-facing flow — to review code, use the review-code flow.
|
||||
name: code-review
|
||||
description: Conducts multi-axis code review. Use before merging any change. Use when reviewing code written by yourself, another agent, or a human. Use when you need to assess code quality across multiple dimensions before it enters the main branch.
|
||||
---
|
||||
|
||||
# Code Review Criteria and Quality
|
||||
# Code Review and Quality
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -13,10 +13,11 @@ Multi-dimensional code review with quality gates. Every change gets reviewed bef
|
||||
|
||||
## When to Use
|
||||
|
||||
- The reviewer subagent of the `review-code` flow loads this skill to perform
|
||||
the review of a code change.
|
||||
- To review code, always go through the `review-code` flow — never load this
|
||||
skill directly for that. This is the criteria reference, not the flow.
|
||||
- Before merging any PR or change
|
||||
- After completing a feature implementation
|
||||
- When another agent or model produced code you need to evaluate
|
||||
- When refactoring existing code
|
||||
- After any bug fix (review both the fix and the regression test)
|
||||
|
||||
## Core Principles
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: create-pr
|
||||
description: Create or update a GitHub PR following Penpot conventions.
|
||||
---
|
||||
|
||||
# Skill: create-pr
|
||||
|
||||
Create or update a GitHub PR. Read and follow:
|
||||
- `mem:workflow/creating-prs` — title format, description structure, writing principles
|
||||
- `mem:workflow/creating-commits` — commit type emojis
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating a new PR from a feature branch
|
||||
- Updating an existing PR's title or description to match conventions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `gh` CLI authenticated (`gh auth status`)
|
||||
|
||||
## Commands
|
||||
|
||||
**Create:**
|
||||
|
||||
```bash
|
||||
gh pr create --repo penpot/penpot --title "<TITLE>" --body-file /tmp/pr-body.md
|
||||
```
|
||||
|
||||
**Update:**
|
||||
|
||||
```bash
|
||||
gh pr edit <NUMBER> --repo penpot/penpot --title "<TITLE>" --body-file /tmp/pr-body.md
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
|
||||
```bash
|
||||
gh pr view <NUMBER> --repo penpot/penpot --json title,body
|
||||
```
|
||||
@@ -9,11 +9,6 @@ metadata: {"clawdbot":{"emoji":"📂","requires":{"bins":["fd"]},"install":[{"id
|
||||
|
||||
User-friendly alternative to `find` with smart defaults.
|
||||
|
||||
## When to use
|
||||
|
||||
- Locating files or directories by name or pattern — prefer it over
|
||||
plain `find`: simpler syntax, smart defaults, respects `.gitignore`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic search
|
||||
-5
@@ -9,11 +9,6 @@ metadata: {"clawdbot":{"emoji":"🔍","requires":{"bins":["jq"]},"install":[{"id
|
||||
|
||||
Process, filter, and transform JSON data with jq.
|
||||
|
||||
## When to use
|
||||
|
||||
- Parsing, filtering, or transforming JSON from commands, files, or API
|
||||
responses — slicing, reshaping, or validating JSON output.
|
||||
|
||||
## Quick Examples
|
||||
|
||||
### Basic filtering
|
||||
@@ -10,12 +10,6 @@ Evaluate Clojure (or ClojureScript) code via a running nREPL server using
|
||||
|
||||
Full documentation: `mem:scripts/nrepl-eval` (file: `.serena/memories/scripts/nrepl-eval.md`)
|
||||
|
||||
## When to use
|
||||
|
||||
- Evaluating Clojure or ClojureScript code against the running nREPL
|
||||
sessions (backend 6064, frontend 3447) — live inspection, patching, or
|
||||
debugging.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: plan-review-criteria
|
||||
description: Plan review criteria — the six review axes, severity rubric, approval standard, and output format for reviewing implementation plans. Loaded by the reviewer subagent of the review-plan flow. Not a user-facing flow — to review a plan, use the review-plan flow.
|
||||
name: plan-review
|
||||
description: Reviews implementation plans for quality, completeness, and actionability. Use after a plan is produced by the planner skill, before starting implementation. Use when evaluating a plan written by yourself, another agent, or a human.
|
||||
---
|
||||
|
||||
# Plan Review Criteria
|
||||
# Plan Review
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -13,10 +13,10 @@ Multi-dimensional plan review with quality gates. Every plan gets reviewed befor
|
||||
|
||||
## When to Use
|
||||
|
||||
- The reviewer subagent of the `review-plan` flow loads this skill to perform
|
||||
the review of a plan.
|
||||
- To review a plan, always go through the `review-plan` flow — never load this
|
||||
skill directly for that. This is the criteria reference, not the flow.
|
||||
- After the planner skill produces a plan
|
||||
- Before starting implementation on any non-trivial task
|
||||
- When reviewing a plan written by another agent or a human
|
||||
- When a plan feels too large, vague, or risky to start
|
||||
|
||||
**Do NOT use for:** Single-file changes with obvious scope, or when the task is trivial enough to just do.
|
||||
|
||||
@@ -87,7 +87,7 @@ Can an implementer actually execute this?
|
||||
|
||||
### 6. Proposed Code Quality *(when the plan includes implementation details)*
|
||||
|
||||
If the plan proposes code shapes, function signatures, data structures, or API designs, evaluate those proposals against `code-review-criteria`:
|
||||
If the plan proposes code shapes, function signatures, data structures, or API designs, evaluate those proposals against `code-review` criteria:
|
||||
|
||||
- **Correctness:** Do the proposed types/signatures handle edge cases (null, empty, boundaries)?
|
||||
- **Readability:** Are proposed names descriptive and consistent with project conventions?
|
||||
@@ -215,7 +215,7 @@ Check that the plan can actually confirm it worked:
|
||||
If the plan includes code snippets, types, or API designs:
|
||||
|
||||
```
|
||||
- Load code-review-criteria skill for criteria
|
||||
- Load code-review skill for criteria
|
||||
- Check proposed signatures for edge cases
|
||||
- Verify naming follows project conventions
|
||||
- Confirm abstractions follow existing patterns
|
||||
@@ -310,6 +310,6 @@ If the plan includes code snippets, types, or API designs:
|
||||
## See Also
|
||||
|
||||
- For producing plans, use the `planner` skill
|
||||
- For reviewing implemented code, use `code-review-criteria` — also the criteria source for axis 6
|
||||
- For reviewing implemented code, use `code-review` — also the criteria source for axis 6
|
||||
- For security-specific concerns, see `security-and-hardening`
|
||||
- For testing strategy guidance, see `testing`
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: planner
|
||||
description: Read-only planning and architecture analysis for Penpot — produce a structured implementation plan with task breakdown, acceptance criteria, sizing, and checkpoints. Always output to the user with the plan's save path (saved or suggested) and the next steps.
|
||||
description: Read-only planning and architecture analysis for Penpot — produce a structured implementation plan with task breakdown, acceptance criteria, sizing, and checkpoints. Always output to the user and save to .opencode/plans/YYYY-MM-DD-<title>.md.
|
||||
---
|
||||
|
||||
# Planner
|
||||
@@ -215,9 +215,9 @@ Add explicit checkpoints with the relevant module commands:
|
||||
|
||||
## Constraints
|
||||
|
||||
- You are **analysis-only** — never create, edit, or delete source code. The
|
||||
only file you may write is the plan itself, and only when the command or
|
||||
user explicitly instructs you to save it.
|
||||
- You are **analysis-only** — never create, edit, or delete source code.
|
||||
- The only file write you may attempt is the plan itself, saved to
|
||||
`.opencode/plans/`.
|
||||
- You do **not** run builds, tests, linters, or any commands that modify state.
|
||||
- You do **not** create git commits or interact with version control.
|
||||
- You do **not** execute shell commands beyond read-only searches (`rg`, `ls`,
|
||||
@@ -228,23 +228,22 @@ Add explicit checkpoints with the relevant module commands:
|
||||
## Output Format
|
||||
|
||||
The plan is always delivered in the response so the user sees it regardless
|
||||
of which agent is running the skill. By default you never write the plan file;
|
||||
announce the path instead. Write the file only when the command or user
|
||||
explicitly instructs you to save it — and then only that file.
|
||||
of which agent is running the skill.
|
||||
|
||||
Announce the suggested save path:
|
||||
Additionally, save the plan to:
|
||||
|
||||
```
|
||||
.agents/plans/YYYY-MM-DD-<plan-one-line-title>.md
|
||||
.opencode/plans/YYYY-MM-DD-<plan-one-line-title>.md
|
||||
```
|
||||
|
||||
Use today's date in the user's local timezone. The `<plan-one-line-title>`
|
||||
slug is lowercase, hyphen-separated, and a short summary of the task
|
||||
(e.g. `add-batch-get-profiles-for-file-comments`). If the user explicitly
|
||||
provides a target file path, announce that path instead of the default.
|
||||
(e.g. `add-batch-get-profiles-for-file-comments`). Create the
|
||||
`.opencode/plans/` directory if it does not exist.
|
||||
|
||||
End the response by suggesting the next steps: `/review-plan` to get a second
|
||||
opinion on the plan and `/implement-plan` to execute it.
|
||||
IMPORTANT: The plan agent has write permission specifically for
|
||||
`.opencode/plans/` — always attempt the write. If the user explicitly provides
|
||||
a target file path, use that path instead of the default.
|
||||
|
||||
### Plan Document Template
|
||||
|
||||
@@ -375,6 +374,4 @@ Before delivering the plan, confirm:
|
||||
- [ ] Task dependencies are identified and ordered correctly
|
||||
- [ ] No task is XL or larger — break it down instead
|
||||
- [ ] Checkpoints exist after every 2-3 tasks
|
||||
- [ ] The response states the plan's path (saved or suggested) and suggests
|
||||
`/review-plan` and `/implement-plan`
|
||||
- [ ] The plan is ready for human review
|
||||
File renamed without changes.
@@ -9,11 +9,6 @@ metadata: {"clawdbot":{"emoji":"🔎","requires":{"bins":["rg"]},"install":[{"id
|
||||
|
||||
Fast, smart recursive search. Respects `.gitignore` by default.
|
||||
|
||||
## When to use
|
||||
|
||||
- Searching file contents across the repo for regex patterns — the
|
||||
default code search, respects `.gitignore`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic search
|
||||
File renamed without changes.
@@ -9,13 +9,6 @@ Apply the ASD-STE100 standard to all prose you produce in this task. Do not anno
|
||||
|
||||
Compliance note (for you, not for output): the official specification and its dictionary are copyright ASD. This skill encodes paraphrased rules and a publicly sourced word list. For certified aerospace/defense deliverables, tell the user that full compliance requires the free official specification (asd-ste100.org) and a human sign-off. Never claim certified compliance.
|
||||
|
||||
## When to use
|
||||
|
||||
Only when the user explicitly invokes it: they type `/ste`, or say "use
|
||||
the ste skill" / "apply ASD-STE100". Requests like "simplify this",
|
||||
"make it clearer", or "shorter sentences" do NOT invoke it — respond
|
||||
normally unless it is named.
|
||||
|
||||
## Step 0 — Classify the text
|
||||
|
||||
Before writing a single sentence, decide: is this **procedural** text (instructions someone follows) or **descriptive** text (explanation, background, description)? Every limit below depends on this. Mixed documents get classified section by section.
|
||||
File renamed without changes.
File renamed without changes.
@@ -11,12 +11,6 @@ Fetch information from Taiga public API for the **Penpot** project
|
||||
|
||||
**No authentication required** — only public project data is accessed.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks about Penpot issues, user stories, or tasks tracked in
|
||||
Taiga — fetch them via the public API (project id 345963), no
|
||||
authentication needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `python3` — the `scripts/taiga.py` CLI script is self-contained (stdlib only)
|
||||
File renamed without changes.
File renamed without changes.
@@ -14,11 +14,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo.
|
||||
- Before `git commit` → `mem:workflow/creating-commits` (subject format, body, `AI-assisted-by: model-name` trailer)
|
||||
- Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, labels, Issue Type)
|
||||
- Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, "Note:" line)
|
||||
- Before a repo-wide pnpm version update → `mem:workflow/updating-pnpm` (workspace
|
||||
layout, `corepack use` sweep order, the stamp-missing-field and
|
||||
ignored-builds gotchas, verification steps)
|
||||
- **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell; if a push is required, say so and wait. Never amend a commit that the user has already pushed unless explicitly asked.
|
||||
- **Never edit `CHANGES.md` by hand.** The changelog is generated from GitHub milestones during the release process; update it only via the `update-changelog` skill flow or on explicit user request.
|
||||
- You have access to the GitHub CLI `gh` or corresponding MCP tools.
|
||||
- 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.
|
||||
@@ -74,14 +70,6 @@ module. You can read it from `mem:<MODULE>/core`
|
||||
- `scripts/error-reports.mjs` — Query error reports via RPC API with token
|
||||
authentication. Supports list/get operations with filtering and pagination.
|
||||
See `mem:scripts/error-reports`.
|
||||
- `scripts/clean-node-modules` — Remove stale `node_modules` from all pnpm
|
||||
workspaces (root, modules, member packages). Keeps the shared pnpm store
|
||||
at `<repo>/.pnpm-store` unless `--store`; ignores `external/` and
|
||||
`.opencode/`. Usage and reinstall steps: `mem:workflow/updating-pnpm`.
|
||||
- `scripts/ci` — CI orchestration script: runs lint, tests, and format
|
||||
checks per module (`frontend backend common render-wasm exporter mcp
|
||||
plugins library`). Logs go to `.ci-logs/`; read the log file on failure.
|
||||
See `mem:scripts/ci`.
|
||||
|
||||
# Dependency graph
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# CI (scripts/ci)
|
||||
|
||||
`scripts/ci` runs CI-style checks — lint, tests, format — for one or more
|
||||
monorepo modules and prints a per-task summary. It is the local equivalent
|
||||
of CI; use it to verify changes before declaring work done.
|
||||
|
||||
## When to use
|
||||
|
||||
- After implementing or fixing code in a module: run its checks before
|
||||
finishing (AGENTS.md: run the applicable lint and format checks).
|
||||
- When `common/` changed: validate its consumers too (frontend, backend,
|
||||
exporter; see the dependency graph in `mem:critical-info`).
|
||||
- To fix formatting across a module (`--fix`) or repair delimiters
|
||||
(`--paren-repair`) before linting.
|
||||
|
||||
## How to use (CLI)
|
||||
|
||||
Run from the repo root:
|
||||
|
||||
```bash
|
||||
./scripts/ci MODULE... # lint + test + fmt per module
|
||||
./scripts/ci --all --no-test # lint + fmt on all modules
|
||||
./scripts/ci --lint frontend # lint only
|
||||
./scripts/ci --fix --no-test frontend # format files, skip tests
|
||||
./scripts/ci --paren-repair --all # fix delimiters in all Clojure modules
|
||||
./scripts/ci --dry-run --all # preview what would run
|
||||
```
|
||||
|
||||
Modules: `frontend backend common render-wasm exporter mcp plugins library`.
|
||||
|
||||
Flags:
|
||||
|
||||
- Default tasks: `lint`, `test`, `fmt` (format check; `--fix` formats
|
||||
instead).
|
||||
- `--lint` / `--test` / `--fmt` run one task only; `--no-lint` /
|
||||
`--no-test` / `--no-fmt` drop one task from the default set.
|
||||
- `--paren-repair` runs only the delimiter repair — it wraps
|
||||
`scripts/paren-repair` over each module's Clojure/CLJS sources; see
|
||||
`mem:scripts/paren-repair`.
|
||||
- `--all` selects every module; `--exclude MOD` drops one (repeatable).
|
||||
- `--fail-fast` stops at the first failure; `--quiet` suppresses failure
|
||||
output; `--dry-run` prints commands without running; `--clean` removes
|
||||
the log directory.
|
||||
|
||||
## Logs and exit codes
|
||||
|
||||
- Full output of every task: `.ci-logs/<module>-<task>.log`.
|
||||
- On failure the script prints the last 30 lines; the final summary lists
|
||||
every failed `module:task` with its log path.
|
||||
- Exit code 0 when all selected tasks passed, 1 otherwise.
|
||||
- Diagnose failures by reading the log file — never pipe test output
|
||||
through filters (AGENTS.md hard rule).
|
||||
|
||||
## Notes
|
||||
|
||||
- `mcp` has no lint task (shows as skipped). `render-wasm` uses `./lint`,
|
||||
`./test`, and `cargo fmt`.
|
||||
- Test tasks are long-running (backend: `clojure -M:dev:test`); use a
|
||||
generous timeout when calling it from an agent shell.
|
||||
- Skill entry point: `.agents/skills/local-ci/SKILL.md`.
|
||||
- Testing principles and output discipline: `mem:testing`.
|
||||
@@ -351,5 +351,5 @@ gh issue view <NUMBER> --repo penpot/penpot --json title
|
||||
## See Also
|
||||
|
||||
- End-to-end orchestration entry point: the `create-issue` skill at
|
||||
`.agents/skills/create-issue/SKILL.md`. The skill is a thin entry
|
||||
`.opencode/skills/create-issue/SKILL.md`. The skill is a thin entry
|
||||
point; this memory is the canonical home for all issue-creation rules.
|
||||
@@ -1,91 +0,0 @@
|
||||
# Updating pnpm Across All Workspaces
|
||||
|
||||
Canonical procedure. Run it from the repo root with the log redirected to a
|
||||
file (never pipe tool output through filters).
|
||||
|
||||
## Layout facts
|
||||
|
||||
- The repo has 11 pnpm workspaces, each with its own `pnpm-workspace.yaml`
|
||||
and `pnpm-lock.yaml`: the repo root plus `backend`, `common`, `docs`,
|
||||
`exporter`, `frontend`, `library`, `mcp`, `media-processor`, `plugins`,
|
||||
and `render-wasm`.
|
||||
- Every package inside a module workspace (for example all `plugins/apps/*`
|
||||
and `plugins/libs/*` packages) is a plain member of that module's
|
||||
workspace. Members must not carry their own `pnpm-workspace.yaml` or
|
||||
`pnpm-lock.yaml`; their dependencies resolve through the parent
|
||||
workspace's lockfile.
|
||||
- One shared pnpm store for the whole repo: `<repo>/.pnpm-store`. Every
|
||||
workspace yaml sets it explicitly: `storeDir: .pnpm-store` at the root,
|
||||
`storeDir: ../.pnpm-store` in each module. pnpm resolves the value
|
||||
against the workspace root, so all workspaces land on the same store.
|
||||
Do not remove these lines: nested workspaces do not inherit settings,
|
||||
and without them each workspace may resolve a different store.
|
||||
- The store survives `node_modules` cleans. It is content-addressed and
|
||||
integrity-verified, so it cannot go stale; staleness lives in
|
||||
node_modules. Only `scripts/clean-node-modules --store` removes it.
|
||||
- Every `package.json` (about 35 of them) must carry a `packageManager` field
|
||||
with the identical `pnpm@<version>+sha512.<hash>` value. Do not let them drift.
|
||||
- CI pins no pnpm version; workflows rely on corepack reading
|
||||
`packageManager`. Fixing the fields fixes CI.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Resolve the target tag first and note the version. Example:
|
||||
`npm view pnpm dist-tags --json` for `next-12` (latest 12.x). The tag
|
||||
moves over time; always re-check.
|
||||
2. List every directory with a `package.json`, excluding `node_modules`
|
||||
(`fd -H -t f package.json -E node_modules`). This list is the work set;
|
||||
do not maintain a hand-written list.
|
||||
3. Run `corepack use pnpm@<tag>` in workspace roots first, then members.
|
||||
`corepack use` stamps `packageManager` in the nearest package.json and
|
||||
runs an install. Member runs repeat the workspace install; after the root
|
||||
run they are quick no-ops.
|
||||
4. If a run fails, fix the cause (see gotchas) and re-run that directory.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `corepack use` only updates an existing `packageManager` field. If a
|
||||
package.json lacks the field, corepack walks up to the nearest ancestor
|
||||
that has one and stamps that file instead; the member stays unstamped.
|
||||
After the sweep, assert every package.json carries the field. For a
|
||||
missing one, insert the identical `pnpm@<version>+sha512.<hash>` string,
|
||||
then re-run `corepack use pnpm@<tag>` in that directory.
|
||||
- A workspace may fail with `ERR_PNPM_IGNORED_BUILDS`, and pnpm then writes
|
||||
a placeholder scaffold into its `pnpm-workspace.yaml`:
|
||||
`allowBuilds: esbuild: set this to true or false`. Current pnpm writes
|
||||
only the `allowBuilds` placeholder; any legacy key still present
|
||||
(`ignoredBuiltDependencies`, `onlyBuiltDependencies`,
|
||||
`neverBuiltDependencies`) is ignored since pnpm 11. Repo convention is
|
||||
`allowBuilds: esbuild: true`. Replace the placeholder and drop the
|
||||
legacy entry, then re-run.
|
||||
- `plugins/apps/composable-test-suite` once had its own
|
||||
`pnpm-workspace.yaml` and acted as a nested workspace root. That state is
|
||||
gone on purpose: pnpm picks the nearest `pnpm-workspace.yaml` walking up,
|
||||
so a nested one silently forks install and lockfile behavior. Do not
|
||||
reintroduce it.
|
||||
- Expect metadata-only lockfile diffs when only the pnpm version moves:
|
||||
the pnpm self-reference entries, plus a new `packageManagerDependencies`
|
||||
section in lockfiles last written by older pnpm. Large diffs mean
|
||||
re-resolution; inspect them before accepting.
|
||||
|
||||
## Verification
|
||||
|
||||
- Every `packageManager` field is byte-identical (same version and hash).
|
||||
- `pnpm --version` in each workspace prints the target version.
|
||||
- `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces.
|
||||
- `git diff` on lockfiles matches the expectations above.
|
||||
|
||||
## Cleaning stale node_modules
|
||||
|
||||
- `scripts/clean-node-modules` removes every workspace `node_modules`: the
|
||||
repo root, all module workspaces, and all member packages. Use it when
|
||||
installs misbehave after dependency changes: clean, reinstall, done.
|
||||
- Flags: `-n/--dry-run` lists without deleting; `--store` also removes the
|
||||
shared pnpm store at `<repo>/.pnpm-store` (the next install re-downloads
|
||||
what it held). `external/` (vendored dependency trees with their own
|
||||
lifecycles) and `.opencode/` are always ignored.
|
||||
- The script never touches the pnpm store by default, so the reinstall
|
||||
after cleaning reuses cached packages (zero downloads).
|
||||
- After cleaning, run `pnpm install` in each workspace root to restore the
|
||||
development environment; `frontend` postinstall also reinstalls and
|
||||
builds `plugins-runtime`.
|
||||
@@ -8,9 +8,6 @@
|
||||
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.
|
||||
- **Never edit `CHANGES.md` by hand** in commits or PRs. The changelog is
|
||||
generated from GitHub milestones during the release process; update it only
|
||||
via the `update-changelog` skill flow or on explicit user request.
|
||||
- **Never pipe test output directly to filters** (`| head`, `| tail`, `| grep`, etc.).
|
||||
Always redirect to a file first: `command > /tmp/output.txt 2>&1`, then read/grep the file.
|
||||
This prevents hiding test failures. See `mem:testing` for details.
|
||||
@@ -145,6 +142,6 @@ precision while maintaining a strong focus on maintainability and performance.
|
||||
- `scripts/nrepl-eval.mjs` — Evaluate Clojure code via nREPL (backend + frontend).
|
||||
- `scripts/check-commit` — Validate commit messages against Penpot's commit guidelines.
|
||||
- `scripts/check-fmt-clj` — Check Clojure formatting without modifying files.
|
||||
- `scripts/ci` — CI orchestration script for running lint, tests, and format checks across modules. See `mem:scripts/ci`.
|
||||
- `scripts/ci` — CI orchestration script for running lint, tests, and format checks across modules. See `scripts/ci --help`.
|
||||
- `scripts/gh.py` — Multi-purpose GitHub CLI helper. Subcommands: `issues` (list issues in a milestone), `prs` (fetch PR details), `advisories` (list/inspect security advisories). See `python3 scripts/gh.py --help`.
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
## 2.18.0 (Unreleased)
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
- Group toolbar drawing tools into shape and free-draw flyouts [#9316](https://github.com/penpot/penpot/issues/9316) (PR: [#9480](https://github.com/penpot/penpot/pull/9480), [#10354](https://github.com/penpot/penpot/pull/10354))
|
||||
- Add dedicated Line and Arrow drawing tools (by @davidv399) [#9145](https://github.com/penpot/penpot/issues/9145) (PR: [#9146](https://github.com/penpot/penpot/pull/9146))
|
||||
- Show and manage comments while designing in the workspace [#10239](https://github.com/penpot/penpot/issues/10239) (PR: [#10275](https://github.com/penpot/penpot/pull/10275))
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix MCP integration hanging when the Penpot tab is backgrounded or frozen by the browser [#10323](https://github.com/penpot/penpot/issues/10323) (PR: [#10392](https://github.com/penpot/penpot/pull/10392))
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Read and follow the instructions in `AGENTS.md`.
|
||||
|
||||
Treat `AGENTS.md` as the canonical project instruction file.
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
Generated
-101
@@ -1,104 +1,3 @@
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- brace-expansion@5.0.8 || 5.0.9
|
||||
@@ -68,8 +68,7 @@
|
||||
"x-profile-id" (str profile-id)}
|
||||
:uri uri
|
||||
:version :http1.1}
|
||||
(= method :post) (assoc :body (json/encode request-params :key-fn json/write-camel-key)))
|
||||
{:skip-ssrf-check? true})))
|
||||
(= method :post) (assoc :body (json/encode request-params :key-fn json/write-camel-key))))))
|
||||
|
||||
(defn- with-retries
|
||||
[handler max-retries]
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
[app.rpc.notifications :as notifications]
|
||||
[app.storage :as sto]
|
||||
[app.util.services :as sv]
|
||||
[app.util.ssrf :as ssrf]
|
||||
[app.worker :as wrk]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@@ -961,18 +960,13 @@ RETURNING id, deleted_at;")
|
||||
(sv/defmethod ::check-organization-sso
|
||||
"Validate an organization SSO configuration by generating a login redirect URL.
|
||||
Nitrate calls this while configuring SSO to verify client credentials and OIDC
|
||||
discovery before saving the settings. The issuer URL is nitrate-supplied
|
||||
(customer-configured), so it is checked against the SSRF blocklist before
|
||||
any outbound request is attempted."
|
||||
discovery before saving the settings."
|
||||
{::doc/added "2.18"
|
||||
::sm/params cto/schema:nitrate-sso
|
||||
::sm/result schema:check-organization-sso-result
|
||||
::rpc/auth false}
|
||||
[cfg params]
|
||||
(let [issuer (oidc/organization-sso-discovery-uri params)]
|
||||
{:valid (boolean (and issuer
|
||||
(ssrf/safe-url? issuer)
|
||||
(oidc/is-organization-sso-config-valid? cfg params)))}))
|
||||
{:valid (oidc/is-organization-sso-config-valid? cfg params)})
|
||||
|
||||
;; ---- API: notify-organization-sso-change
|
||||
(sv/defmethod ::notify-organization-sso-change
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
[app.http.client :as http]
|
||||
[app.main :as-alias main]
|
||||
[app.setup :as-alias setup]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.json :as json]
|
||||
[integrant.core :as ig]
|
||||
[promesa.exec :as px]))
|
||||
@@ -247,16 +248,20 @@
|
||||
:props (or (some-> props db/decode-transit-pgobject) {})
|
||||
:context (or (some-> context db/decode-transit-pgobject) {})}))
|
||||
|
||||
(defn- encode-batch
|
||||
"Encode a sequence of event maps into a fressian+zstd base64 string
|
||||
suitable for JSON transport."
|
||||
^String [events]
|
||||
(blob/encode-str events {:version 4}))
|
||||
|
||||
(defn send-event-batch
|
||||
"Send a single batch of events to the telemetry endpoint. Returns
|
||||
true on success. The events are sent as a plain vector of event
|
||||
maps; the JSON encoder handles UUID and temporal types natively and
|
||||
the receiver coerces them back to proper types."
|
||||
true on success."
|
||||
[{:keys [::setup/props] :as cfg} batch]
|
||||
(let [payload {:type :telemetry-events
|
||||
:version (:full cf/version)
|
||||
:instance-id (:instance-id props)
|
||||
:events (vec batch)}
|
||||
:events (encode-batch batch)}
|
||||
request {:method :post
|
||||
:uri (cf/get :telemetry-uri)
|
||||
:headers {"content-type" "application/json"}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.nitrate-ssrf-test
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.http.client :as http]
|
||||
[app.nitrate :as nitrate]
|
||||
[app.setup :as-alias setup]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t]
|
||||
[integrant.core :as ig]
|
||||
[java-http-clj.core :as jhttp]))
|
||||
|
||||
(def ^:private private-admin-uri "http://127.0.0.1:9090")
|
||||
|
||||
(defn- mk-cfg
|
||||
"Minimal nitrate cfg with a real HttpClient and nitrate client methods."
|
||||
[]
|
||||
(let [http-client (jhttp/build-client {})
|
||||
base {::http/client http-client
|
||||
::setup/shared-keys {:admin-console "test-shared-key"}}]
|
||||
(assoc base ::nitrate/client (ig/init-key ::nitrate/client base))))
|
||||
|
||||
(defn- with-admin-console-uri
|
||||
"Run `f` with :admin-console enabled and the given admin-console URI / allowlist."
|
||||
[admin-uri allowed-hosts f]
|
||||
(let [original-get cf/get]
|
||||
(with-redefs [cf/flags #{:admin-console}
|
||||
cf/get (fn [key & args]
|
||||
(case key
|
||||
:admin-console-uri admin-uri
|
||||
:ssrf-allowed-hosts allowed-hosts
|
||||
(apply original-get key args)))]
|
||||
(f))))
|
||||
|
||||
(t/deftest nitrate-blocks-private-admin-console-uri
|
||||
(let [sent? (atom false)]
|
||||
(with-admin-console-uri
|
||||
private-admin-uri
|
||||
#{}
|
||||
(fn []
|
||||
(with-redefs [jhttp/send (fn [_req _opts]
|
||||
(reset! sent? true)
|
||||
{:status 200 :body "{\"licenses\":true}"})]
|
||||
(try
|
||||
(nitrate/call (mk-cfg) :connectivity {})
|
||||
(t/is false "should have raised :nitrate-unavailable")
|
||||
(catch Exception e
|
||||
(t/is (= :nitrate-unavailable (:type (ex-data e))))
|
||||
(t/is (false? @sent?)
|
||||
"SSRF must stop the request before it reaches the network"))))))))
|
||||
|
||||
(t/deftest nitrate-proceeds-when-admin-console-host-allowlisted
|
||||
(let [captured (atom nil)]
|
||||
(with-admin-console-uri
|
||||
private-admin-uri
|
||||
#{"127.0.0.1"}
|
||||
(fn []
|
||||
(with-redefs [jhttp/send (fn [req _opts]
|
||||
(reset! captured req)
|
||||
{:status 200
|
||||
:body "{\"licenses\":true}"})]
|
||||
(let [result (nitrate/call (mk-cfg) :connectivity {})]
|
||||
(t/is (= {:licenses true} result))
|
||||
(t/is (some? @captured))
|
||||
(t/is (str/starts-with? (str (:uri @captured)) private-admin-uri))))))))
|
||||
@@ -17,7 +17,6 @@
|
||||
[app.msgbus :as mbus]
|
||||
[app.nitrate :as nitrate]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.util.ssrf :as ssrf]
|
||||
[app.worker :as wrk]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.set :as set]
|
||||
@@ -1807,14 +1806,13 @@
|
||||
|
||||
(t/deftest check-organization-sso-returns-valid-true
|
||||
(let [organization-id (uuid/random)
|
||||
out (with-redefs [ssrf/safe-url? (constantly true)
|
||||
oidc/is-organization-sso-config-valid? (constantly true)]
|
||||
(th/management-command!
|
||||
{::th/type :check-organization-sso
|
||||
:organization-id organization-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:issuer "https://idp.example.com"}))]
|
||||
out (with-redefs [oidc/is-organization-sso-config-valid? (constantly true)]
|
||||
(th/management-command!
|
||||
{::th/type :check-organization-sso
|
||||
:organization-id organization-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:issuer "https://idp.example.com"}))]
|
||||
(t/is (th/success? out))
|
||||
(t/is (true? (-> out :result :valid)))))
|
||||
|
||||
@@ -1829,36 +1827,19 @@
|
||||
|
||||
(t/deftest check-organization-sso-passes-issuer-to-validation
|
||||
(let [organization-id (uuid/random)
|
||||
out (with-redefs [ssrf/safe-url? (constantly true)
|
||||
oidc/is-organization-sso-config-valid?
|
||||
(fn [_cfg sso]
|
||||
(and (= "test-client" (:client-id sso))
|
||||
(= "https://idp.example.com/" (:issuer sso))))]
|
||||
(th/management-command!
|
||||
{::th/type :check-organization-sso
|
||||
:organization-id organization-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:issuer "https://idp.example.com/"}))]
|
||||
out (with-redefs [oidc/is-organization-sso-config-valid?
|
||||
(fn [_cfg sso]
|
||||
(and (= "test-client" (:client-id sso))
|
||||
(= "https://idp.example.com/" (:issuer sso))))]
|
||||
(th/management-command!
|
||||
{::th/type :check-organization-sso
|
||||
:organization-id organization-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:issuer "https://idp.example.com/"}))]
|
||||
(t/is (th/success? out))
|
||||
(t/is (true? (-> out :result :valid)))))
|
||||
|
||||
(t/deftest check-organization-sso-returns-valid-false-on-ssrf-blocked-issuer
|
||||
(t/testing "an SSRF-blocked issuer must not reach the OIDC validation flow"
|
||||
(let [called? (atom false)
|
||||
out (with-redefs [oidc/is-organization-sso-config-valid?
|
||||
(fn [_cfg _sso] (reset! called? true) true)]
|
||||
(th/management-command!
|
||||
{::th/type :check-organization-sso
|
||||
:organization-id (uuid/random)
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:issuer "http://127.0.0.1/idp"}))]
|
||||
(t/is (th/success? out))
|
||||
(t/is (false? (-> out :result :valid)))
|
||||
(t/is (false? @called?)
|
||||
"OIDC validation should not run when the issuer is SSRF-blocked"))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PUSH AUDIT EVENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.db :as db]
|
||||
[app.loggers.audit :as audit]
|
||||
[app.tasks.telemetry :as telemetry]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.json :as json]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.test :as t]
|
||||
@@ -58,6 +59,11 @@
|
||||
:cnt
|
||||
long))
|
||||
|
||||
(defn- decode-event-batch
|
||||
"Decode the base64+fressian+zstd event-batch sent to the mock."
|
||||
[b64-str]
|
||||
(blob/decode-str b64-str))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; STATS / REPORT STRUCTURE TESTS (existing behaviour, extended)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -239,19 +245,21 @@
|
||||
(t/is (not (contains? ev :ip-addr)))))))))
|
||||
|
||||
(t/deftest test-batch-encoding-is-decodable
|
||||
;; Events are sent as a plain vector of raw event maps (no blob
|
||||
;; encoding): every batch must JSON round-trip unchanged, because
|
||||
;; the receiver coerces types from the plain JSON representation.
|
||||
;; Verify that encode-batch produces a blob that round-trips back
|
||||
;; through blob/decode to the original data.
|
||||
(let [events [{:name "navigate" :type "action" :source "telemetry"
|
||||
:tracked-at (ct/now)}
|
||||
{:name "create-file" :type "action" :source "telemetry"
|
||||
:tracked-at (ct/now)}]
|
||||
encoded (json/encode-str {:events (vec events)})
|
||||
decoded (json/decode encoded)]
|
||||
(t/is (vector? (:events decoded)))
|
||||
(t/is (= (count events) (count (:events decoded))))
|
||||
(t/is (= "navigate" (:name (first (:events decoded)))))
|
||||
(t/is (= "create-file" (:name (second (:events decoded)))))))
|
||||
;; Call the private fn through the ns-mapped var
|
||||
encode (ns-resolve 'app.tasks.telemetry 'encode-batch)
|
||||
encoded (encode events)
|
||||
decoded (decode-event-batch encoded)]
|
||||
(t/is (string? encoded))
|
||||
(t/is (seq decoded))
|
||||
(t/is (= (count events) (count decoded)))
|
||||
(t/is (= "navigate" (:name (first decoded))))
|
||||
(t/is (= "create-file" (:name (second decoded))))))
|
||||
|
||||
(t/deftest test-multiple-batches-when-many-events
|
||||
;; Lower batch-size to 1 so that 3 events produce 3 separate
|
||||
@@ -779,13 +787,9 @@
|
||||
(t/is (= "telemetry-events" (name (:type body))))
|
||||
(t/is (string? (:version body)))
|
||||
(t/is (some? (:instance-id body)))
|
||||
;; :events is a plain vector of raw event maps
|
||||
(t/is (vector? (:events body)))
|
||||
(t/is (pos? (count (:events body))))
|
||||
(doseq [ev (:events body)]
|
||||
(t/is (string? (:name ev)))
|
||||
(t/is (string? (:source ev)))
|
||||
(t/is (string? (:tracked-at ev))))))))))
|
||||
;; :events is a base64-encoded blob
|
||||
(t/is (string? (:events body)))
|
||||
(t/is (pos? (count (:events body))))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TASK BRANCH COVERAGE
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Generated
+37
-37
@@ -7,96 +7,96 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
minimumReleaseAge: 0
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
+1
-1
@@ -39,5 +39,5 @@
|
||||
"markdown-it-anchor": "^9.2.1",
|
||||
"markdown-it-plantuml": "^1.4.1"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67"
|
||||
}
|
||||
Generated
+37
-37
@@ -7,96 +7,96 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- undici@7.28.0 || 7.29.0
|
||||
- js-yaml@3.15.0 || 4.3.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Comments
|
||||
order: 6
|
||||
order: 4
|
||||
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
|
||||
---
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Enterprise plan
|
||||
order: 2
|
||||
desc: Learn how the Enterprise plan works in Penpot. Discover its features and how to use it within your organization.
|
||||
---
|
||||
|
||||
<h1 id="enterprise">Enterprise plan</h1>
|
||||
<p class="main-paragraph">Penpot Enterprise gives organizations the tools to govern how design work happens across their teams: from creating a structured org and managing members, to applying fine-grained permissions and configuration through the Admin Console.</p>
|
||||
|
||||
<h3 id="what-is-penpot-enterprise">What is Penpot Enterprise?
|
||||
</h3>
|
||||
<p>Penpot Enterprise is the plan that unlocks organizational governance features. While Penpot remains free and unlimited as an open-source platform, Enterprise adds a layer of control on top: the ability to create Organizations, manage teams under it, and apply configuration settings that define what members can and cannot do.</p>
|
||||
<p>The organization owner is the user who creates the organization. They have exclusive access to the Admin Console and are responsible for configuring Modules.</p>
|
||||
<p>The key concepts you'll work with:</p>
|
||||
<ul>
|
||||
<li><strong><a href="/user-guide/account-teams/organizations/">Organization</a>:</strong> the top-level structure that groups one or more Teams under a shared governance layer.</li>
|
||||
<li><strong><a href="/user-guide/account-teams/organizations/#the-admin-console">Admin Console</a>:</strong> the back-office interface where the org owner manages settings, teams and members.</li>
|
||||
<li><strong><a href="/user-guide/account-teams/organizations/#modules-and-controls">Modules</a>:</strong> the paid, configurable capabilities applied to an organization. Each Module consists of individual Controls (specific settings or restrictions).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="subscribing-to-enterprise">Subscribing to Enterprise
|
||||
<a class="direct-link" href="#subscribing-to-enterprise">#</a>
|
||||
</h3>
|
||||
<p>To create an organization, you first need to upgrade to the <a href="https://penpot.app/pricing" target="_blank">Enterprise plan</a>. Click "Create Organization" to begin. Once the subscription process is complete, you'll be redirected to the Admin Console to finish creating your organization.</p>
|
||||
|
||||
<h3 id="frequently-asked-questions"> Frequently asked questions
|
||||
<a class="direct-link" href="#frequently-asked-questions">#</a>
|
||||
</h3>
|
||||
|
||||
<h4>Can I have multiple organizations?</h4>
|
||||
<p>Yes. You can create more than one organization under a single Enterprise subscription and manage them from the Admin Console.</p>
|
||||
|
||||
<h4>What happens if I cancel my Enterprise subscription?</h4>
|
||||
<p>Your organizations are deleted but not their teams. Governance settings no longer apply to any of them.</p>
|
||||
|
||||
@@ -13,19 +13,7 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<p>Access your account settings and manage personal access tokens</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/enterprise-plan/">
|
||||
<h2>Enterprise plan →</h2>
|
||||
<p>Learn how the Enterprise plan works and what it includes</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/organizations/">
|
||||
<h2>Organizations →</h2>
|
||||
<p>Create and manage organizations, the Admin Console, and SSO</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/teams">
|
||||
<h2>Teams →</h2>
|
||||
<p>Create and manage your teams</p>
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
---
|
||||
title: Organizations
|
||||
order: 3
|
||||
desc: Learn how Organizations work in Penpot Enterprise, creating one, the Admin Console, membership, Modules and Controls, and Single Sign-On (SSO).
|
||||
---
|
||||
|
||||
<h1 id="organizations">Organizations</h1>
|
||||
<p class="main-paragraph">An organization is the governance layer Penpot Enterprise adds on top of your teams. This section covers how to create and manage one: the Admin Console, membership, and the Modules and Controls used to configure it, including Single Sign-On (SSO).</p>
|
||||
|
||||
<h3 id="creating-an-organization">Creating an organization
|
||||
<a class="direct-link" href="#creating-an-organization">#</a>
|
||||
</h3>
|
||||
<p>Creating an organization is as easy as giving it a name.</p>
|
||||
<p>Once the organization is created, you'll be taken to the Admin Console. At this point, the organization has one member: you, the owner.</p>
|
||||
|
||||
<h3 id="the-admin-console">The Admin Console
|
||||
<a class="direct-link" href="#the-admin-console">#</a>
|
||||
</h3>
|
||||
<p>The Admin Console is the admin interface for your organization. Only the organization owner can access it.</p>
|
||||
<p>You can reach the Admin Console directly at <code>/admin-console</code>, or from any team dashboard in Penpot by opening the organization navigation menu and clicking the Admin Console link. If you own more than one organization, you can switch between them from within the Admin Console.</p>
|
||||
|
||||
<figure>
|
||||
<a href="/img/enterprise/enterprise-access-admin-console.webp" target="_blank">
|
||||
<img alt="Accessing the Admin Console" src="/img/enterprise/enterprise-access-admin-console.webp"/>
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<p>The Admin Console includes:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Switch organization menu: </strong> To navigate between the organizations you own.</li>
|
||||
<li><strong>Organization settings: </strong> To change basic settings, such as renaming it or deleting it.</li>
|
||||
<li><strong>A "Go to files" button </strong> that returns you to a team dashboard.</li>
|
||||
<li><strong>Your user avatar</strong>, that expands into a full user menu.</li>
|
||||
<li><strong>Module: </strong> The control itself to configure.</li>
|
||||
</ol>
|
||||
|
||||
<figure>
|
||||
<a href="/img/enterprise/enterprise-admin-console.webp" target="_blank">
|
||||
<img alt="Admin Console" src="/img/enterprise/enterprise-admin-console.webp"/>
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<h3 id="organizations-and-teams">Organizations and teams
|
||||
<a class="direct-link" href="#organizations-and-teams">#</a>
|
||||
</h3>
|
||||
<p>An organization groups one or more Teams under a shared governance structure. Teams continue to work just as they do in standard Penpot, with the added layer that the org owner can apply configuration that affects all members across the organization's teams.</p>
|
||||
<h4>How teams relate to organizations</h4>
|
||||
|
||||
<p>Teams inside an organization inherit the governance settings applied at the org level via the Admin Console. The structure is:</p>
|
||||
|
||||
<figure>
|
||||
<img alt="organization-hierarchy" src="/img/enterprise/enterprise-organization-hierarchy-w.webp"/>
|
||||
</figure>
|
||||
|
||||
<p>Team members work within projects and files as usual. What changes under Enterprise is the org owner's ability to restrict or govern that work from the Admin Console.</p>
|
||||
<p>Within each team, the <a href="https://help.penpot.app/user-guide/account-teams/teams/#teams-members" target="_blank">standard Penpot roles</a> apply.</p>
|
||||
|
||||
<h3 id="managing-organization-membership">Managing organization membership
|
||||
<a class="direct-link" href="#managing-organization-membership">#</a>
|
||||
</h3>
|
||||
<p>At launch, an organization has a single member: the owner. Additional members are brought in by being part of a team added to the organization, being invited to teams within the organization, or being directly invited to the organization by the owner.</p>
|
||||
<p>The Admin Console provides a unified view of all members across teams within the organization.</p>
|
||||
|
||||
<figure>
|
||||
<a href="/img/enterprise/enterprise-membership-w.webp" target="_blank">
|
||||
<img alt="Admin-Console Members page" src="/img/enterprise/enterprise-membership-w.webp"/>
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<h3 id="modules-and-controls">Modules and Controls
|
||||
<a class="direct-link" href="#modules-and-controls">#</a>
|
||||
</h3>
|
||||
|
||||
<p>Modules are the configurable governance capabilities available to Enterprise organizations. Each Module is made up of one or more Controls: the individual settings that define who can do what, and where.</p>
|
||||
<p>Modules are configured from the Admin Console and apply organization-wide. </p>
|
||||
|
||||
<table cellspacing="0" cellpadding="1" border="1" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th valign="top" class="cellrowborder">Modules</th>
|
||||
<th valign="top" class="cellrowborder" style="text-align: center;">What it does</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Single Sign-On (SSO)</td>
|
||||
<td>Requires all org members to authenticate through your corporate identity provider before accessing the organization's teams and files.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Advanced permissions</td>
|
||||
<td>Defines who can create, view, edit, administer, or share teams, projects, and files. Also controls who can invite new members to teams.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="module-advanced-permissions">Module: Advanced permissions
|
||||
<a class="direct-link" href="#module-advanced-permissions">#</a>
|
||||
</h3>
|
||||
|
||||
<p>Advanced Permissions gives the organization owner fine-grained control over what members can do across all teams in the organization. Rather than relying on the default Penpot team roles alone, this module lets you restrict or open up specific actions at the organization level.</p>
|
||||
|
||||
<h4>What it controls</h4>
|
||||
|
||||
<p>The Advanced Permissions module is made up of individual Controls. Each Control governs a specific action, and each has a set of options to choose from. The selected option becomes the rule for the entire organization.</p>
|
||||
|
||||
<h4>How to configure it</h4>
|
||||
|
||||
<p>Advanced Permissions is configured from the Admin Console. Changes apply to all teams within the organization immediately.</p>
|
||||
<ol>
|
||||
<li>Open the Admin Console.</li>
|
||||
<li>Select Advanced Permissions from the left sidebar.</li>
|
||||
<li>For each Control, select the option that fits your governance policy.</li>
|
||||
<li>Changes take effect right away. There is no publish or save step.</li>
|
||||
</ol>
|
||||
|
||||
<h4>How it relates to team roles</h4>
|
||||
|
||||
<p>Advanced Permissions works on top of the standard Penpot team roles (Viewer, Editor, Admin, Owner). It does not replace them. Think of it as a ceiling: even if a member's team role would normally allow an action, an Advanced Permissions Control can prevent it organization-wide.</p>
|
||||
<p>For example, if “New team members” is set to "Organization members only," a team owner who would normally be able to invite anyone will find that option restricted to people who are already part of the organization.</p>
|
||||
|
||||
<h4>Default behavior</h4>
|
||||
|
||||
<p>When an organization is first created, all controls are configured with the most permissive setting; the same setting is used by all teams that are not part of any organization. No behavior changes until you actively configure a Control.</p>
|
||||
|
||||
<h3 id="module-single-sign-on">Module: Single Sign-On (SSO)
|
||||
<a class="direct-link" href="#module-single-sign-on">#</a>
|
||||
</h3>
|
||||
|
||||
<p>Single Sign-On lets you require all members of your organization to authenticate through your corporate identity provider (IdP) before accessing any of the organization's teams and files.</p>
|
||||
<p>SSO applies to teams and files only. The Admin Console is always accessible without SSO, so you can always reach your configuration to adjust or deactivate it, even if your own directory entry changes.</p>
|
||||
|
||||
<h4>Configuring your identity provider</h4>
|
||||
<p>Before setting up SSO in Penpot, you need to register Penpot as an application in your identity provider. The steps vary by provider, but you will always need to set the following <strong>callback URL</strong> in your IdP configuration:</p>
|
||||
<p><strong><code>https://<your-penpot-domain>/api/auth/oidc/callback</code></strong></p>
|
||||
<p>Your IdP will then give you a <strong>Client ID</strong> and <strong>Client Secret</strong> to use in Penpot. Once you have those:</p>
|
||||
<ol>
|
||||
<li>Open the Admin Console.</li>
|
||||
<li>Select <strong>SSO Config</strong>.</li>
|
||||
<li>Choose your provider and fill in the fields.</li>
|
||||
</ol>
|
||||
|
||||
<figure>
|
||||
<a href="/img/enterprise/enterprise-module-sso.webp" target="_blank">
|
||||
<img alt="SSO configuration" src="/img/enterprise/enterprise-module-sso.webp"/>
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<h5>Generic authentication (OpenID Connect)</h5>
|
||||
<p>Use this option for any identity provider that supports the OIDC protocol, such as Okta, Keycloak, or Auth0.</p>
|
||||
<ul>
|
||||
<li><strong>Issuer / Authority URL:</strong> base URL of your OIDC provider, used to autodiscover endpoints</li>
|
||||
<li><strong>Client ID:</strong> client identifier assigned by your provider</li>
|
||||
<li><strong>Client Secret:</strong> client secret assigned by your provider</li>
|
||||
</ul>
|
||||
|
||||
<h5>Azure Active Directory (OpenID Connect)</h5>
|
||||
<ul>
|
||||
<li><strong>Issuer / Authority URL:</strong> <code>https://login.microsoftonline.com/<your-tenant-id>/v2.0/</code></li>
|
||||
<li><strong>Client ID:</strong> Application (client) ID from your Azure app registration</li>
|
||||
<li><strong>Client Secret:</strong> client secret value from your Azure app registration</li>
|
||||
</ul>
|
||||
|
||||
<h5>Google (OAuth)</h5>
|
||||
<ul>
|
||||
<li><strong>Client ID:</strong> client identifier from your Google Cloud OAuth 2.0 credentials</li>
|
||||
<li><strong>Client Secret:</strong> client secret from your Google Cloud OAuth 2.0 credentials</li>
|
||||
</ul>
|
||||
|
||||
<p>When all fields are filled, click <strong>Activate SSO</strong>. Penpot will run a test connection against your IdP. If the connection fails, your draft is kept and no changes are applied.</p>
|
||||
<p>If the test passes, a confirmation dialog will appear. It will warn you that members not in your directory will lose access to the organization's teams once SSO is active. Review your member list if needed, then confirm. SSO becomes active immediately.</p>
|
||||
|
||||
<h4>What happens to existing sessions</h4>
|
||||
<p>When SSO is activated, any member who is currently inside one of the organization's teams is cut off immediately and sent through the SSO login. This does not log them out of Penpot entirely. They can still reach teams that do not belong to your organization without re-authenticating.</p>
|
||||
<p>SSO sessions last 4 hours. When a session expires, members are routed through the SSO login again. If they are still in the directory, they are signed back in immediately.</p>
|
||||
|
||||
<h4>Editing an active configuration</h4>
|
||||
<p>While SSO is active, you can edit any field. As soon as you make a change, <strong>Apply changes</strong> and <strong>Discard changes</strong> buttons appear. Discarding restores every field to the current live configuration. Applying runs the same test connection as the initial setup, without a confirmation dialog. If the connection fails, your live configuration is not touched. Changes may take up to 5 minutes to apply for members who are currently working in a file.</p>
|
||||
|
||||
<h4>Deactivating SSO</h4>
|
||||
<p>Click <strong>Deactivate SSO</strong> and confirm. The configuration is preserved as a draft so you can reactivate it later without re-entering your credentials. Members are notified by email the first time SSO is activated. If you deactivate and reactivate within 24 hours, the notification is not re-sent.</p>
|
||||
|
||||
<h4>For your members</h4>
|
||||
<p>Members do not need to do anything to prepare. When they next try to access the organization's teams, they will be asked to authenticate through your IdP. If they are already signed in through that provider, the step is skipped automatically.</p>
|
||||
<p>Org membership still requires an invitation from you. Being in the directory alone does not grant access to Penpot or to your organization.</p>
|
||||
<p>If a member is not in your directory, they remain an org member but cannot enter the organization's teams until they are added. A single email is sent to all current members and pending invitees when SSO is first activated, explaining what changed and who to contact if they cannot get in.</p>
|
||||
|
||||
<h3 id="frequently-asked-questions"> Frequently asked questions
|
||||
<a class="direct-link" href="#frequently-asked-questions">#</a>
|
||||
</h3>
|
||||
|
||||
<h4>Can a team belong to more than one organization?</h4>
|
||||
<p>No. A team belongs to a single organization.</p>
|
||||
|
||||
<h4>Can non-owners access the Admin Console?</h4>
|
||||
<p>No. Access to the Admin Console is currently exclusive to the organization owner.</p>
|
||||
|
||||
<h4>What is the organization name used for?</h4>
|
||||
<p>It's the human-readable name used to identify your organization in the UI, in emails, and in URLs. It is not your official billing name. You can change it at any time without affecting navigation or functionality.</p>
|
||||
|
||||
<h4>Do Advanced Permissions replace the standard Penpot team roles?</h4>
|
||||
<p>No. Advanced Permissions work on top of the existing roles (Viewer, Editor, Admin, Owner). They add an organization-wide ceiling on what any role can do, but they do not change how roles work within a team.</p>
|
||||
|
||||
<h4>What is the default behavior when I first create an organization?</h4>
|
||||
<p>All Controls are set to their most permissive option. Nothing changes until you actively configure a Control.</p>
|
||||
|
||||
<h4>What happens to existing team admins if I change a Control that restricts their permissions?</h4>
|
||||
<p>The restriction applies immediately. An admin who could previously perform an action will no longer be able to do so as soon as the Control is changed, with no grace period.</p>
|
||||
|
||||
<h4>Is this the same as the authentication providers in Penpot's self-hosting configuration?</h4>
|
||||
<p>No. Penpot's self-hosted configuration lets server administrators enable login methods (Google, GitHub, GitLab, OIDC) at the instance level, so users can sign in to Penpot itself with those providers. That is a server-level setting managed by whoever runs the infrastructure.</p>
|
||||
<p>The SSO module in Enterprise is different in scope and purpose. It is configured by you, the organization owner, from the Admin Console, and it governs access to your organization's teams and files specifically. It does not change how users log in to Penpot as a platform, only whether they need to pass through your corporate identity provider to reach your organization's content.</p>
|
||||
|
||||
<h4>Does SSO affect the Admin Console?</h4>
|
||||
<p>No. The Admin Console is always accessible without SSO, regardless of your configuration. This ensures you can always reach your settings to adjust or deactivate SSO, even if something changes on the directory side.</p>
|
||||
|
||||
<h4>What happens if my identity provider goes down while SSO is active?</h4>
|
||||
<p>Current sessions continue until they expire. The next time a member tries to authenticate through your IdP and the provider is unreachable, the login will fail. There is no automatic bypass. Since the Admin Console is outside SSO, you can still reach your configuration to deactivate SSO if needed.</p>
|
||||
|
||||
<h4>Can I use the same identity provider for more than one organization?</h4>
|
||||
<p>Yes. Two different organizations, and the Penpot instance itself, can share the same IdP. A successful SSO login never grants org membership on its own, so there is no risk of cross-organization access. Membership always requires an explicit invitation.</p>
|
||||
|
||||
<h4>What if a member is not in my directory?</h4>
|
||||
<p>They remain an org member and keep their Penpot account, but they cannot enter the organization's teams until they are added to the directory. We send them an email when SSO is first activated explaining the situation and telling them to contact you.</p>
|
||||
|
||||
<h4>Does accepting an invitation automatically give someone access to my org's teams?</h4>
|
||||
<p>Only if they are also in your directory. An invitee can register and accept the invitation, but if they are not in the directory, they become an org member without being able to enter the teams. Acceptance is never blocked on that basis.</p>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Projects and Files
|
||||
order: 5
|
||||
order: 3
|
||||
desc: Learn how to organize your work in Penpot. Create, manage and organize projects and files, work with drafts, and handle deleted items.
|
||||
---
|
||||
|
||||
@@ -56,10 +56,6 @@ desc: Learn how to organize your work in Penpot. Create, manage and organize pro
|
||||
</figure>
|
||||
<p>When creating a file, you'll be asked to give it a name. The file will open in the workspace where you can start designing immediately.</p>
|
||||
|
||||
<h3 id="file-view">Grid and list view</h3>
|
||||
<p>In the dashboard header you can switch how files are shown: a thumbnail <strong>Grid view</strong> or a compact <strong>List view</strong>. The choice is kept in the browser and applies both to the team files view and inside a project.</p>
|
||||
<p>List view shows the file name, whether the file is a shared library, the last modification time, and the same options menu as the file cards.</p>
|
||||
|
||||
<h3 id="edit-file">Edit a file</h3>
|
||||
<p>To rename a file, right-click on the file card in the dashboard and select <strong>Rename</strong>, or click on the three-dot menu on the file card. Enter the new name and confirm the change. You can also access file settings and other options from the file's context menu.</p>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Teams
|
||||
order: 4
|
||||
order: 2
|
||||
desc: Manage teams and roles with Penpot's collaboration features! Learn how to manage teams, roles (Viewer, Editor, Admin, Owner), send invites and use webhooks.
|
||||
---
|
||||
|
||||
|
||||
@@ -122,14 +122,8 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
<img alt="Multiple strokes" src="/img/styling/stroke-multiple.webp"/>
|
||||
</figure>
|
||||
|
||||
<h2 id="stroke-to-path">Stroke to path</h2>
|
||||
<p>A path is the underlying geometry: a sequence of points and segments (straight lines or Bezier curves). A stroke is the visible line drawn along that path, with properties such as width, color, opacity, dashes, caps, and joins.</p>
|
||||
<p><strong>Stroke to path</strong> turns the visible outline of a stroke into a separate, editable path. You can then reshape or style that outline independently of the original path. The new shape is usually closed. That matters for SVG, boolean operations, and editing the outline as geometry.</p>
|
||||
<p>To convert a stroke, select a layer that has one, open the layer menu, and choose <strong>Stroke to path</strong>. Penpot creates a new path from each stroke (named with " (stroke)") and removes the strokes from the original layer.</p>
|
||||
<p class="advice">Stroke to path is currently available when <a href="/user-guide/first-steps/troubleshooting-webgl/">WebGL rendering</a> is enabled.</p>
|
||||
|
||||
<h2 id="stroke-caps">Stroke Caps</h2>
|
||||
<p>Ever needed an arrow to point to something? You can style the ends of any open paths selecting different styles for each end of an open path. You can also start from the <a href="/user-guide/designing/layers/#lines-arrows">Arrow tool</a>, which already applies a triangle cap.</p>
|
||||
<p>Ever needed an arrow to point something? You can style the ends of any open paths selecting different styles for each end of an open path.</p>
|
||||
<figure>
|
||||
<video title="Stroke cap" muted="" playsinline="" controls="" width="100%" poster="/img/styling/stroke-cap.webp" height="auto">
|
||||
<source src="/img/styling/stroke-cap.mp4" type="video/mp4">
|
||||
|
||||
@@ -140,7 +140,7 @@ Penpot allows you to decide if the fill of an artboard will be shown in exports,
|
||||
<h3 id="rectangles-ellipses">Rectangles and ellipses</h3>
|
||||
<p>Rectangle and ellipses are two basic “primitive” geometric shapes that are useful when starting
|
||||
a design.</p>
|
||||
<p>The shortcut keys are <kbd>E</kbd> for ellipses and <kbd>R</kbd> for rectangles. Both tools live in the shapes flyout of the toolbar, together with line and arrow.</p>
|
||||
<p>The shortcut keys are <kbd>E</kbd> for ellipses and <kbd>R</kbd> for rectangles.</p>
|
||||
<p>To find out more about how to edit and modify these shapes go to <a href="/user-guide/designing/layers/#layer-actions">Layer basics</a>.</p>
|
||||
<figure>
|
||||
<video title="Rectangles and ellipses" muted="" playsinline="" controls="" width="auto" poster="/img/objects/rectangles-ellipses.webp" height="auto">
|
||||
@@ -151,24 +151,13 @@ a design.</p>
|
||||
<h3 id="text">Text</h3>
|
||||
<p>Text layers are how you add copy to your designs in Penpot. If you want to go deeper into fonts, typography and advanced text options, check the dedicated <a href="/user-guide/designing/text-typo/">Text & Typography</a> section.</p>
|
||||
|
||||
<h3 id="lines-arrows">Lines and arrows</h3>
|
||||
<p>Use the Line and Arrow tools to draw a two-point path in one drag. They live in the shapes flyout of the toolbar, together with rectangle and ellipse.</p>
|
||||
<ul>
|
||||
<li><strong>Line</strong> (<kbd>L</kbd>): click and drag. Hold <kbd>Shift/⇧</kbd> to snap the angle in 15 degree increments.</li>
|
||||
<li><strong>Arrow</strong>: the same gesture, with a triangle arrow cap on the end. Pick it from the shapes flyout. You can change or remove the cap from the <a href="/user-guide/designing/color-stroke/#stroke-caps">stroke caps</a>.</li>
|
||||
</ul>
|
||||
<p>The result is a regular path, so you can edit its nodes and stroke like any other path.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/line-arrow-tools.webp" alt="Line and Arrow tools in the shapes flyout of the toolbar" />
|
||||
</figure>
|
||||
|
||||
<h3 id="curves">Curves (freehand)</h3>
|
||||
<p>The curve tool allows a path to be created directly in a freehand mode.
|
||||
Select the curve tool from the free-draw flyout in the toolbar or press <kbd>Shift/⇧</kbd> + <kbd>C</kbd>.</p>
|
||||
Select the curve tool by clicking on the icon at the toolbar or pressing <kbd>Shift/⇧</kbd> + <kbd>c</kbd>.
|
||||
<p>The path created will contain a lot of points, but it is edited the same way as any other curve.</p>
|
||||
|
||||
<h3 id="paths">Paths (bezier)</h3>
|
||||
<p>A path is composed of two or more nodes and the line segments between them, which may also be curved. To draw a new path, select the path tool from the free-draw flyout in the toolbar or press <kbd>P</kbd>. Then you have two ways to create the path:</p>
|
||||
<p>A path is composed of two or more nodes and the line segments between them, which may also be curved. To draw a new path you have to select the path tool by clicking on the icon at the toolbar or pressing <kbd>P</kbd>. Then you have two ways to create the path:</p>
|
||||
<ol>
|
||||
<li><strong>Click</strong> to create a new corner node.</li>
|
||||
<li><strong>Click and drag</strong> to create a curved node.</li>
|
||||
@@ -222,7 +211,7 @@ You can choose to edit individual nodes or create new ones. Press <kbd>Esc</kbd>
|
||||
<h2 id="layer-actions">Layer actions</h2>
|
||||
|
||||
<h3 id="creating-layers">Create</h3>
|
||||
<p>To create a layer, pick a tool from the toolbar. Board, text, and image stay on the toolbar. Rectangle, ellipse, line, and arrow are in the shapes flyout. Path and curve are in the free-draw flyout. Then you usually click and drag on the viewport.</p>
|
||||
<p>To create a layer you have to select the type of layer by clicking the selected tool (board, rectangle, ellipse, text, image, path or curve) at the toolbar. Then you usually have to click and drag your mouse on the viewport. </p>
|
||||
<p>Hold <kbd>Shift/⇧</kbd> while creating an ellipse or a rectangle to maintain equal width and height.</p>
|
||||
<figure>
|
||||
<video title="Layers create" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-create.webp" height="auto">
|
||||
|
||||
@@ -32,7 +32,7 @@ desc: Penpot's guide on custom fonts! Upload, manage, and use custom fonts in Pe
|
||||
<img src="/img/objects/text-options.webp" alt="text options">
|
||||
</figure>
|
||||
<ol>
|
||||
<li><strong>Font family.</strong> Penpot includes by default the <a href="https://fonts.google.com/" target=”_blank”>Google Fonts</a> cataloge. You can also <a href="/user-guide/designing/text-typo/#custom-fonts">install your own fonts</a>. In the font selector, each family name is shown in its own typeface so you can compare fonts before applying one. The name is the preview. While a font loads, you may briefly see the interface font instead. The preview uses a default variant, not every weight or style of the family.</li>
|
||||
<li><strong>Font family.</strong> Penpot includes by default the <a href="https://fonts.google.com/" target=”_blank”>Google Fonts</a> cataloge. You can also <a href="/user-guide/designing/text-typo/#custom-fonts">install your own fonts</a>.</li>
|
||||
<li><strong>Font size.</strong></li>
|
||||
<li><strong>Font type.</strong></li>
|
||||
<li><strong>Line height</strong> (in pixels).</li>
|
||||
|
||||
@@ -43,15 +43,6 @@ desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools,
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4 id="select-delete-pages">Select and delete several pages</h4>
|
||||
<p>In the Pages panel you can select more than one page and delete them in a single action.</p>
|
||||
<ul>
|
||||
<li>Click a page to open it. This selects only that page.</li>
|
||||
<li><kbd>Shift/⇧</kbd> + click to select a range of pages. This does not change the page you are viewing.</li>
|
||||
<li><kbd>Ctrl/⌘</kbd> + click to add or remove one page from the selection. This does not navigate.</li>
|
||||
</ul>
|
||||
<p>Right-click a selected page and choose <strong>Delete pages</strong>. Confirm to remove all selected pages in one step. A file always keeps at least one page. Page separators cannot be included in the selection.</p>
|
||||
|
||||
<h4 id="page-separators">Page separators</h4>
|
||||
<p>You can add visual dividers in the page list to group pages without extra structure. Create an empty page, then rename it to <code>---</code>. The page appears as a horizontal line in the list.</p>
|
||||
<p>A page only becomes a separator if its name is <code>---</code> and the page has no content on the canvas. A page with content that is named <code>---</code> stays a normal page. Separators cannot be opened or selected; you can reorder or delete them like other pages.</p>
|
||||
|
||||
@@ -681,8 +681,8 @@ desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Curve</td>
|
||||
<td style="text-align: center;"><kbd>Shift</kbd><kbd>C</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⇧</kbd><kbd>C</kbd></td>
|
||||
<td style="text-align: center;"><kbd>Ctrl</kbd><kbd>C</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌘</kbd><kbd>C</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ellipse</td>
|
||||
@@ -694,11 +694,6 @@ desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface
|
||||
<td style="text-align: center;"><kbd>Shift</kbd><kbd>K</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⇧</kbd><kbd>K</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Line</td>
|
||||
<td style="text-align: center;"><kbd>L</kbd></td>
|
||||
<td style="text-align: center;"><kbd>L</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td style="text-align: center;"><kbd>P</kbd></td>
|
||||
|
||||
@@ -41,9 +41,9 @@ desc: Discover Penpot's free user guide! Learn the interface, workspace basics,
|
||||
|
||||
<ol>
|
||||
<li><strong>Viewport:</strong> An infinite canvas where you can design without limits.</li>
|
||||
<li><strong>Toolbar: </strong> Tools to create layers. Board, text, and image stay on the toolbar. Shapes (rectangle, ellipse, line, and arrow) and free-draw tools (path and curve) are grouped in flyouts. Open a flyout to pick a tool, or use its shortcut. <a href="/user-guide/designing/layers/">Learn more about layers.</a></li>
|
||||
<li><strong>Toolbar: </strong> This is where you’ll find all the tools to quickly and easily create different types of layers: board, rectangle, ellipse, text, graphic, path, and free drawing. <a href="/user-guide/designing/layers/">Learn more about layers.</a></li>
|
||||
<li><strong>Main menu:</strong> From the main menu, you can customize your workspace. Manage the visibility of grids, rulers, and panels. Enable or disable snapping and dynamic alignment. Add or remove the file as a Shared Library. You’ll also find help resources here.</li>
|
||||
<li><strong>Pages:</strong> A file can contain as many pages as you need. Each page has its own viewport (the almost infinite area where you design) and its own layers. You can create, delete, or reorder pages, and select several pages at once to delete them. <a href="/user-guide/designing/workspace-basics/#layer-basics">More about pages.</a></li>
|
||||
<li><strong>Pages:</strong> A file can contain as many pages as you need. Each page has its own viewport (the almost infinite area where you design) and its own layers. You can create, delete, or reorder pages as needed.</li>
|
||||
<li><strong>Layers:</strong> Layers are the different objects you can place in the design viewport. <a href="/user-guide/designing/workspace-basics/#layer-basics">More about Layers panel.</a></li>
|
||||
<li><strong>Rulers:</strong> Rulers provide coordinates to help you design. You can also drag guides from them.</li>
|
||||
<li><strong>Color palette:</strong> The color palette gives you quick access to a visible library of colors. Use the menu to easily switch between libraries. <a href="/user-guide/designing/color-stroke/#color-palette">Learn more about the color palette.</a>.</li>
|
||||
@@ -137,7 +137,7 @@ desc: Discover Penpot's free user guide! Learn the interface, workspace basics,
|
||||
<li><strong>User area:</strong> This must be you! Access your <a href="/user-guide/account-teams/your-account"> profile settings</a>, Penpot tutorials, the Penpot Community and more. You can also find here a way to leave us feedback. We’d love to read your thoughts :). </li>
|
||||
<li><strong>Comments notifications:</strong> Here you will be able to see if you have unread comments inside the files of the team. There's also a button to mark all notifications as read.</li>
|
||||
<li><strong>Create project:</strong> Create as many projects as you need to organize your designs.</li>
|
||||
<li><strong>File card:</strong> Basic information about a file at plain sight. A preview, update info or if it’s added as a Shared Library. From there you can perform several actions over the file (rename, duplicate, move, download, delete). You can also switch the files area between a thumbnail grid and a compact list. <a href="/user-guide/account-teams/projects-files/#file-view">More about grid and list view.</a></li>
|
||||
<li><strong>File card:</strong> Basic information about a file at plain sight. A preview, update info or if it’s added as a Shared Library. From there you can perform several actions over the file (rename, duplicate, move, download, delete).</li>
|
||||
<li><strong>Libraries & Templates module:</strong> A curated selection of Libraries & Templates files ready to import.</li>
|
||||
</ol>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
Generated
+37
-37
@@ -7,96 +7,96 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
allowBuilds:
|
||||
core-js-pure: false
|
||||
minimumReleaseAgeExclude:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { setProjectAnnotations } from '@storybook/react-vite';
|
||||
import * as projectAnnotations from './preview';
|
||||
|
||||
// This is an important step to apply the right configuration when testing your stories.
|
||||
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||
setProjectAnnotations([projectAnnotations]);
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Penpot Draft-JS Wrapper",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Simple library for handling keyboard shortcuts",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"author": "Craig Campbell",
|
||||
"license": "Apache-2.0 WITH LLVM-exception"
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"version": "0.0.1",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js"
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"fdata/path-data",
|
||||
"plugins/runtime",
|
||||
"design-tokens/v1",
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type",
|
||||
"text-editor/v2"
|
||||
]
|
||||
},
|
||||
"~:team-id": "~u9e6e22b2-db76-81d6-8006-75d7cdbb8bad",
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "Fixed size text",
|
||||
"~:revn": 3,
|
||||
"~:modified-at": "~m1753957736516",
|
||||
"~:vern": 0,
|
||||
"~:id": "~u238a17e0-75ff-8075-8006-934586ea2230",
|
||||
"~:is-shared": false,
|
||||
"~:migrations": {
|
||||
"~#ordered-set": [
|
||||
"legacy-2",
|
||||
"legacy-3",
|
||||
"legacy-5",
|
||||
"legacy-6",
|
||||
"legacy-7",
|
||||
"legacy-8",
|
||||
"legacy-9",
|
||||
"legacy-10",
|
||||
"legacy-11",
|
||||
"legacy-12",
|
||||
"legacy-13",
|
||||
"legacy-14",
|
||||
"legacy-16",
|
||||
"legacy-17",
|
||||
"legacy-18",
|
||||
"legacy-19",
|
||||
"legacy-25",
|
||||
"legacy-26",
|
||||
"legacy-27",
|
||||
"legacy-28",
|
||||
"legacy-29",
|
||||
"legacy-31",
|
||||
"legacy-32",
|
||||
"legacy-33",
|
||||
"legacy-34",
|
||||
"legacy-36",
|
||||
"legacy-37",
|
||||
"legacy-38",
|
||||
"legacy-39",
|
||||
"legacy-40",
|
||||
"legacy-41",
|
||||
"legacy-42",
|
||||
"legacy-43",
|
||||
"legacy-44",
|
||||
"legacy-45",
|
||||
"legacy-46",
|
||||
"legacy-47",
|
||||
"legacy-48",
|
||||
"legacy-49",
|
||||
"legacy-50",
|
||||
"legacy-51",
|
||||
"legacy-52",
|
||||
"legacy-53",
|
||||
"legacy-54",
|
||||
"legacy-55",
|
||||
"legacy-56",
|
||||
"legacy-57",
|
||||
"legacy-59",
|
||||
"legacy-62",
|
||||
"legacy-65",
|
||||
"legacy-66",
|
||||
"legacy-67",
|
||||
"0001-remove-tokens-from-groups",
|
||||
"0002-normalize-bool-content-v2",
|
||||
"0002-clean-shape-interactions",
|
||||
"0003-fix-root-shape",
|
||||
"0003-convert-path-content-v2",
|
||||
"0004-clean-shadow-color",
|
||||
"0005-deprecate-image-type",
|
||||
"0006-fix-old-texts-fills",
|
||||
"0007-clear-invalid-strokes-and-fills-v2",
|
||||
"0008-fix-library-colors-v4",
|
||||
"0009-clean-library-colors",
|
||||
"0009-add-partial-text-touched-flags"
|
||||
]
|
||||
},
|
||||
"~:version": 67,
|
||||
"~:project-id": "~u9e6e22b2-db76-81d6-8006-75d7cdc30669",
|
||||
"~:created-at": "~m1753957644225",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~u238a17e0-75ff-8075-8006-934586ea2231"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~u238a17e0-75ff-8075-8006-934586ea2231": {
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:name": "Root Frame",
|
||||
"~:width": 0.01,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.0,
|
||||
"~:y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.0,
|
||||
"~:y": 0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:r3": 0,
|
||||
"~:r1": 0,
|
||||
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 0,
|
||||
"~:proportion": 1.0,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 0.01,
|
||||
"~:height": 0.01,
|
||||
"~:x1": 0,
|
||||
"~:y1": 0,
|
||||
"~:x2": 0.01,
|
||||
"~:y2": 0.01
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 0.01,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": [
|
||||
"~ucc6f0580-449c-8019-8006-9345db077fa0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~ucc6f0580-449c-8019-8006-9345db077fa0": {
|
||||
"~#shape": {
|
||||
"~:y": 150,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:grow-type": "~:fixed",
|
||||
"~:content": {
|
||||
"~:type": "root",
|
||||
"~:key": "1s4am1jl24s",
|
||||
"~:children": [
|
||||
{
|
||||
"~:type": "paragraph-set",
|
||||
"~:children": [
|
||||
{
|
||||
"~:line-height": "1.2",
|
||||
"~:font-style": "normal",
|
||||
"~:children": [
|
||||
{
|
||||
"~:line-height": "1.2",
|
||||
"~:font-style": "normal",
|
||||
"~:typography-ref-id": null,
|
||||
"~:text-transform": "none",
|
||||
"~:font-id": "sourcesanspro",
|
||||
"~:key": "13p0zwl2yhc",
|
||||
"~:font-size": "14",
|
||||
"~:font-weight": "400",
|
||||
"~:typography-ref-file": null,
|
||||
"~:font-variant-id": "regular",
|
||||
"~:text-decoration": "none",
|
||||
"~:letter-spacing": "0",
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#000000",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:font-family": "sourcesanspro",
|
||||
"~:text": "Lorem ipsum"
|
||||
}
|
||||
],
|
||||
"~:typography-ref-id": null,
|
||||
"~:text-transform": "none",
|
||||
"~:text-align": "left",
|
||||
"~:font-id": "sourcesanspro",
|
||||
"~:key": "20hf3kmyoub",
|
||||
"~:font-size": "14",
|
||||
"~:font-weight": "400",
|
||||
"~:typography-ref-file": null,
|
||||
"~:text-direction": "ltr",
|
||||
"~:type": "paragraph",
|
||||
"~:font-variant-id": "regular",
|
||||
"~:text-decoration": "none",
|
||||
"~:letter-spacing": "0",
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#000000",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:font-family": "sourcesanspro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"~:vertical-align": "top"
|
||||
},
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Fixed text",
|
||||
"~:width": 300,
|
||||
"~:type": "~:text",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 200,
|
||||
"~:y": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 500,
|
||||
"~:y": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 500,
|
||||
"~:y": 350
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 200,
|
||||
"~:y": 350
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:id": "~ucc6f0580-449c-8019-8006-9345db077fa0",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:x": 200,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 200,
|
||||
"~:y": 150,
|
||||
"~:width": 300,
|
||||
"~:height": 200,
|
||||
"~:x1": 200,
|
||||
"~:y1": 150,
|
||||
"~:x2": 500,
|
||||
"~:y2": 350
|
||||
}
|
||||
},
|
||||
"~:flip-x": null,
|
||||
"~:height": 200,
|
||||
"~:flip-y": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id": "~u238a17e0-75ff-8075-8006-934586ea2231",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
},
|
||||
"~:id": "~u238a17e0-75ff-8075-8006-934586ea2230",
|
||||
"~:options": {
|
||||
"~:components-v2": true,
|
||||
"~:base-font-size": "16px"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ const FILE = {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WasmWorkspacePage.init(page);
|
||||
// WASM_FLAGS already enables render-wasm; add the WASM text editor on top.
|
||||
await WasmWorkspacePage.mockConfigFlags(page, [
|
||||
"enable-feature-text-editor-wasm",
|
||||
]);
|
||||
await WasmWorkspacePage.mockConfigFlags(page, ["enable-feature-text-editor-wasm"]);
|
||||
});
|
||||
|
||||
async function openEditorAndSelectAll(workspace) {
|
||||
@@ -24,12 +22,12 @@ async function openEditorAndSelectAll(workspace) {
|
||||
}
|
||||
|
||||
test.describe("BUG 10502 - Mixed families and variants", () => {
|
||||
test("Multiple variants of the same font family", async ({ page }) => {
|
||||
test("Multiple variants of the same font family", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page, { textEditor: true });
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile(
|
||||
"text-editor/get-file-10502-mixed-variants.json",
|
||||
);
|
||||
await workspace.mockGetFile("text-editor/get-file-10502-mixed-variants.json");
|
||||
|
||||
await workspace.goToWorkspace(FILE);
|
||||
await workspace.waitForFirstRender();
|
||||
@@ -49,14 +47,10 @@ test.describe("BUG 10502 - Mixed families and variants", () => {
|
||||
await expect(fontVariant).toHaveText("--");
|
||||
});
|
||||
|
||||
test("Mixed font families appear as such in the dropdown", async ({
|
||||
page,
|
||||
}) => {
|
||||
test("Mixed font families appear as such in the dropdown", async ({ page }) => {
|
||||
const workspace = new WasmWorkspacePage(page, { textEditor: true });
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile(
|
||||
"text-editor/get-file-10502-mixed-families.json",
|
||||
);
|
||||
await workspace.mockGetFile("text-editor/get-file-10502-mixed-families.json");
|
||||
// Serve a stand-in TTF for Sora so the render doesn't wait on a real fetch.
|
||||
// Glyphs are irrelevant here: the assertion only inspects the sidebar.
|
||||
await workspace.mockGoogleFont("sora", "render-wasm/assets/ebgaramond.ttf");
|
||||
@@ -153,94 +147,9 @@ test("BUG 10531 - Entering the editor auto-selects the whole text", async ({
|
||||
await workspace.copy("keyboard");
|
||||
|
||||
// Assert the text was copied correctly
|
||||
const copiedText = await page.evaluate(() => navigator.clipboard.readText());
|
||||
const copiedText = await page.evaluate(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
expect(copiedText).toBe("Lorem ipsum");
|
||||
});
|
||||
|
||||
test.describe("BUG 10934 - Double-clicking a text side handle sets auto-size", () => {
|
||||
// Sets up the workspace and loads a text shape whose size is larger than its text
|
||||
async function setupFixedSizeText(page) {
|
||||
const workspace = new WasmWorkspacePage(page, { textEditor: true });
|
||||
// Enable token inputs so they use the new component with accessible DOM
|
||||
await workspace.mockConfigFlags(["enable-feature-token-input"]);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("text-editor/get-file-fixed-size-text.json");
|
||||
await workspace.goToWorkspace();
|
||||
await workspace.waitForFirstRender();
|
||||
|
||||
// Select the text and zoom to fit, so it is fully visible in the viewport
|
||||
await workspace.clickLeafLayer("Fixed text");
|
||||
await page.keyboard.press("Shift+1");
|
||||
await workspace.waitForIdle();
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
async function doubleClickSideHandle(workspace, position) {
|
||||
const handle = workspace.viewport.getByTestId(
|
||||
`resize-side-handler-${position}`,
|
||||
);
|
||||
await handle.waitFor();
|
||||
const box = await handle.boundingBox();
|
||||
await workspace.page.mouse.dblclick(
|
||||
box.x + box.width / 2,
|
||||
box.y + box.height / 2,
|
||||
);
|
||||
}
|
||||
|
||||
function measureInput(workspace, name) {
|
||||
return workspace.rightSidebar
|
||||
.getByRole("region", { name: "shape-measures-section" })
|
||||
.getByRole("textbox", { name, exact: true });
|
||||
}
|
||||
|
||||
test("Double-clicking the right handle switches to auto-width", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await setupFixedSizeText(page);
|
||||
|
||||
const widthInput = workspace.rightSidebar
|
||||
.getByRole("region", { name: "shape-measures-section" })
|
||||
.getByRole("textbox", { name: "Width", exact: true });
|
||||
const initialWidth = Number(await widthInput.inputValue());
|
||||
|
||||
await doubleClickSideHandle(workspace, "right");
|
||||
|
||||
// Assert auto-width is selected and that the width has shrunk. The resize
|
||||
// is debounced, so poll the value (auto-retrying) rather than reading once.
|
||||
await expect(
|
||||
workspace.rightSidebar.getByRole("button", {
|
||||
name: "Auto width",
|
||||
pressed: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => Number(await widthInput.inputValue()))
|
||||
.toBeLessThan(initialWidth);
|
||||
});
|
||||
|
||||
test("Double-clicking the bottom handle switches to auto-height", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await setupFixedSizeText(page);
|
||||
|
||||
const heightInput = workspace.rightSidebar
|
||||
.getByRole("region", { name: "shape-measures-section" })
|
||||
.getByRole("textbox", { name: "Height", exact: true });
|
||||
const initialHeight = Number(await heightInput.inputValue());
|
||||
|
||||
await doubleClickSideHandle(workspace, "bottom");
|
||||
|
||||
// Assert auto-height is selected and that the height has shrunk. The resize
|
||||
// is debounced, so poll the value (auto-retrying) rather than reading once.
|
||||
await expect(
|
||||
workspace.rightSidebar.getByRole("button", {
|
||||
name: "Auto height",
|
||||
pressed: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => Number(await heightInput.inputValue()))
|
||||
.toBeLessThan(initialHeight);
|
||||
});
|
||||
});
|
||||
Generated
+37
-37
@@ -7,96 +7,96 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
packages:
|
||||
- "packages/draft-js"
|
||||
- "packages/mousetrap"
|
||||
|
||||
@@ -123,8 +123,7 @@
|
||||
(defn- await-font-faces
|
||||
"Waits for missing WASM faces, then resizes the affected texts."
|
||||
[stream face-keys ids]
|
||||
(let [resize-opts {:stack-undo? true :undo-transation? false}
|
||||
resize-stream (->> (rx/from ids) (rx/map #(dwwt/resize-wasm-text % resize-opts)))]
|
||||
(let [resize-stream (->> (rx/from ids) (rx/map dwwt/resize-wasm-text))]
|
||||
(if (empty? face-keys)
|
||||
resize-stream
|
||||
(->> (rx/merge wasm.fonts/font-stored-stream
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.thumbnails :as thc]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
@@ -24,7 +23,6 @@
|
||||
[app.main.render :as render]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.queue :as q]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -291,60 +289,13 @@
|
||||
(mapcat get-frame-ids-cached))
|
||||
changes))))
|
||||
|
||||
;; Board thumbnails used to render text shapes without position-data as
|
||||
;; nothing instead of falling back to the foreignObject renderer (see
|
||||
;; frame-imposter in app.main.render), so any board thumbnail cached before
|
||||
;; that fix may be missing its text. The backend doesn't tell the client
|
||||
;; when a fetched thumbnail was generated, so we can't tell stale apart from
|
||||
;; fresh by inspecting it; instead each board thumbnail with text content is
|
||||
;; regenerated at most once per browser, tracked via local-storage so repeat
|
||||
;; visits (once healed) don't keep re-rendering it.
|
||||
(def ^:private healed-storage-key ::healed-text-thumbnails)
|
||||
|
||||
(defn- frame-has-text?
|
||||
[objects frame-id]
|
||||
(->> (cfh/get-children-with-self objects frame-id)
|
||||
(some cfh/text-shape?)
|
||||
(some?)))
|
||||
|
||||
(defn- unhealed-text-thumbnail?
|
||||
[state object-id]
|
||||
(and (some? (dm/get-in state [:thumbnails object-id :uri]))
|
||||
(not (contains? (get @storage/global healed-storage-key) object-id))))
|
||||
|
||||
(defn- mark-thumbnail-healed!
|
||||
[object-id]
|
||||
(swap! storage/global update healed-storage-key (fnil conj #{}) object-id))
|
||||
|
||||
(defn- heal-stale-text-thumbnails
|
||||
"Emits an `update-thumbnail` for every board on the page that has text
|
||||
content and hasn't already been healed (see `healed-storage-key`) in this
|
||||
browser."
|
||||
[state file-id page-id]
|
||||
(let [objects (-> (dsh/lookup-file-data state file-id)
|
||||
(dsh/get-page page-id)
|
||||
:objects)
|
||||
frame-ids (ctt/get-root-frames-ids objects)
|
||||
xf (comp
|
||||
(filter #(frame-has-text? objects %))
|
||||
(keep
|
||||
(fn [frame-id]
|
||||
(let [object-id (thc/fmt-object-id file-id page-id frame-id "frame")]
|
||||
(when (unhealed-text-thumbnail? state object-id)
|
||||
[frame-id object-id])))))]
|
||||
(->> (rx/from (eduction xf frame-ids))
|
||||
(rx/map
|
||||
(fn [[frame-id object-id]]
|
||||
(mark-thumbnail-healed! object-id)
|
||||
(update-thumbnail file-id page-id frame-id "frame" "heal-stale-text-thumbnails"))))))
|
||||
|
||||
(defn watch-state-changes
|
||||
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
||||
of the frame data so the thumbnail can be updated."
|
||||
[file-id page-id]
|
||||
(ptk/reify ::watch-state-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(watch [_ _ stream]
|
||||
(let [stopper-s (rx/filter
|
||||
(fn [event]
|
||||
(as-> (ptk/type event) type
|
||||
@@ -379,10 +330,6 @@
|
||||
(rx/tap #(l/trc :hint "buffer initialized")))]
|
||||
|
||||
(->> (rx/merge
|
||||
;; Heal boards with text whose cached thumbnail may predate the
|
||||
;; text-position fix (see heal-stale-text-thumbnails).
|
||||
(heal-stale-text-thumbnails state file-id page-id)
|
||||
|
||||
;; Perform instant thumbnail cleaning of affected frames
|
||||
;; and interrupt any ongoing update-thumbnail process
|
||||
;; related to current frame-id
|
||||
|
||||
@@ -79,26 +79,20 @@
|
||||
|
||||
(defn resize-wasm-text
|
||||
"Resize a single text shape (auto-width/auto-height) by id.
|
||||
No-op if the id is not a text shape or is :fixed.
|
||||
`opts` are forwarded to `apply-wasm-modifiers`, so a caller whose undo
|
||||
transaction is already closed when the resize lands can still get the
|
||||
geometry into the right undo entry."
|
||||
([id]
|
||||
(resize-wasm-text id nil))
|
||||
([id opts]
|
||||
(ptk/reify ::resize-wasm-text
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shape (get objects id)
|
||||
apply-opts (or opts {})
|
||||
resize-stream
|
||||
(if (and (some? shape)
|
||||
(cfh/text-shape? shape)
|
||||
(not= :fixed (:grow-type shape)))
|
||||
(rx/of (dwm/apply-wasm-modifiers (resize-wasm-text-modifiers shape) apply-opts))
|
||||
(rx/empty))]
|
||||
(wrf/with-pending :text-resize [id] resize-stream))))))
|
||||
No-op if the id is not a text shape or is :fixed."
|
||||
[id]
|
||||
(ptk/reify ::resize-wasm-text
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shape (get objects id)
|
||||
resize-stream
|
||||
(if (and (some? shape)
|
||||
(cfh/text-shape? shape)
|
||||
(not= :fixed (:grow-type shape)))
|
||||
(rx/of (dwm/apply-wasm-modifiers (resize-wasm-text-modifiers shape)))
|
||||
(rx/empty))]
|
||||
(wrf/with-pending :text-resize [id] resize-stream)))))
|
||||
|
||||
(defn resize-wasm-text-debounce-commit
|
||||
([]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.string :as str]
|
||||
[okulary.core :as l]))
|
||||
|
||||
;; ---- Global refs
|
||||
@@ -583,13 +584,21 @@
|
||||
(dm/get-in state [:viewer-local :zoom-type]))
|
||||
st/state))
|
||||
|
||||
(defn- resolved-uri?
|
||||
"Returns true if the uri is already a fully resolved URI (blob or data)."
|
||||
[uri]
|
||||
(or (str/starts-with? uri "blob:")
|
||||
(str/starts-with? uri "data:")))
|
||||
|
||||
(defn workspace-thumbnail-by-id
|
||||
[object-id]
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(when-let [entry (dm/get-in state [:thumbnails object-id])]
|
||||
(cond-> entry
|
||||
(:uri entry) (update :uri cf/resolve-media))))
|
||||
(and (:uri entry)
|
||||
(not (resolved-uri? (:uri entry))))
|
||||
(update :uri cf/resolve-media))))
|
||||
st/state))
|
||||
|
||||
(def workspace-text-modifier
|
||||
|
||||
@@ -266,17 +266,16 @@
|
||||
[{:keys [objects frame vbox x y width height background]}]
|
||||
(let [shape-wrapper (shape-wrapper-factory objects)]
|
||||
[:& (mf/provider muc/render-thumbnails) {:value false}
|
||||
[:& (mf/provider muc/is-render?) {:value true}
|
||||
[:svg {:view-box vbox
|
||||
:width (ust/format-precision width viewbox-decimal-precision)
|
||||
:height (ust/format-precision height viewbox-decimal-precision)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:fill "none"}
|
||||
(when (some? background)
|
||||
[:rect {:x x :y y :width width :height height :fill background}])
|
||||
[:& shape-wrapper {:shape frame}]]]]))
|
||||
[:svg {:view-box vbox
|
||||
:width (ust/format-precision width viewbox-decimal-precision)
|
||||
:height (ust/format-precision height viewbox-decimal-precision)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:fill "none"}
|
||||
(when (some? background)
|
||||
[:rect {:x x :y y :width width :height height :fill background}])
|
||||
[:& shape-wrapper {:shape frame}]]]))
|
||||
|
||||
;; Component that serves for render frame thumbnails, mainly used in
|
||||
;; the viewer and inspector
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.data.workspace.wasm-text :as dwwt]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
@@ -29,7 +26,6 @@
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.object :as obj]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def rotation-handler-size 20)
|
||||
@@ -299,23 +295,13 @@
|
||||
on-double-click
|
||||
(mf/use-fn
|
||||
(mf/deps shape-id position shape-type)
|
||||
(fn [event]
|
||||
(fn [_event]
|
||||
(when (= shape-type :text)
|
||||
;; Prevent the viewport double-click handler from entering text editor
|
||||
(dom/stop-propagation event)
|
||||
(let [grow-type (case position
|
||||
:right :auto-width
|
||||
:bottom :auto-height
|
||||
nil)]
|
||||
(when (some? grow-type)
|
||||
(let [uid (js/Symbol)]
|
||||
(st/emit! (dwu/start-undo-transaction uid)
|
||||
(dwsh/update-shapes [shape-id] #(assoc % :grow-type grow-type)))
|
||||
;; The WASM renderer needs an explicit reflow after the grow-type change
|
||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||
(st/emit! (dwwt/resize-wasm-text-all [shape-id] {:undo-id uid})
|
||||
(ptk/data-event :layout/update {:ids [shape-id]}))
|
||||
(st/emit! (dwu/commit-undo-transaction uid)))))))))]
|
||||
(cond
|
||||
(= position :right)
|
||||
(st/emit! (dwsh/update-shapes [shape-id] #(assoc % :grow-type :auto-width)))
|
||||
(= position :bottom)
|
||||
(st/emit! (dwsh/update-shapes [shape-id] #(assoc % :grow-type :auto-height)))))))]
|
||||
|
||||
[:g.resize-handler
|
||||
(when ^boolean show-handler
|
||||
@@ -335,7 +321,6 @@
|
||||
:height height
|
||||
:class cursor
|
||||
:data-position (name position)
|
||||
:data-testid (dm/str "resize-side-handler-" (name position))
|
||||
:transform transform-str
|
||||
:on-pointer-down on-resize
|
||||
:on-double-click on-double-click
|
||||
|
||||
@@ -658,9 +658,7 @@
|
||||
|
||||
(defn use-shape
|
||||
[id]
|
||||
;; Use `wasm/live?` (not `initialized?`) so context-restore reload can
|
||||
;; select shapes while `reloading?` still blocks external app callers.
|
||||
(when (wasm/live?)
|
||||
(when (initialized?)
|
||||
(let [buffer (uuid/get-u32 id)]
|
||||
(h/call wasm/internal-module "_use_shape"
|
||||
(aget buffer 0)
|
||||
@@ -670,7 +668,7 @@
|
||||
|
||||
(defn has-shape
|
||||
[id]
|
||||
(when (wasm/live?)
|
||||
(when (initialized?)
|
||||
(let [buffer (uuid/get-u32 id)
|
||||
|
||||
result
|
||||
@@ -688,11 +686,9 @@
|
||||
;; Cache content for text editor sync
|
||||
(text-editor/cache-shape-text-content! shape-id content)
|
||||
|
||||
;; Skip when the GL/WASM context is not usable. Use `wasm/live?` (not
|
||||
;; `initialized?`) so context-restore reload can re-upload text while
|
||||
;; `reloading?` still blocks external app callers. Geometry shapes already
|
||||
;; use `live?` via `set-shape-base-props`; text must match that path.
|
||||
(when (wasm/live?)
|
||||
;; The WASM design state may not be ready (e.g. while switching renderer
|
||||
;; with a text shape being edited). Skip the WASM layout calls in that case.
|
||||
(when (initialized?)
|
||||
(h/call wasm/internal-module "_clear_shape_text")
|
||||
|
||||
(set-shape-vertical-align (get content :vertical-align))
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"vite": "^8.2.0",
|
||||
"vitest": "^4.1.10"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67"
|
||||
}
|
||||
@@ -7,8 +7,12 @@ import { playwright } from '@vitest/browser-playwright'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
||||
const dirname = import.meta.dirname;
|
||||
const dirname =
|
||||
typeof __dirname !== "undefined"
|
||||
? __dirname
|
||||
: path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
||||
export default defineConfig({
|
||||
@@ -41,14 +45,15 @@ export default defineConfig({
|
||||
{browser: "chromium"},
|
||||
],
|
||||
},
|
||||
setupFiles: [".storybook/vitest.setup.ts"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@target": resolve(dirname, "./target/storybook"),
|
||||
"@public": resolve(dirname, "./resources/public/js/"),
|
||||
"@target": resolve(__dirname, "./target/storybook"),
|
||||
"@public": resolve(__dirname, "./resources/public/js/"),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.2.0-RC1",
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Generated
+37
-37
@@ -7,96 +7,96 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.linux-x64-musl@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.win32-arm64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
storeDir: ../.pnpm-store
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- brace-expansion@5.0.8 || 5.0.9
|
||||
patchedDependencies:
|
||||
|
||||
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user