@penpair/web 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -240
- package/docs/approvals.md +19 -0
- package/docs/artifacts.md +13 -0
- package/docs/audits.md +13 -0
- package/docs/backup.md +54 -0
- package/docs/branch-review.md +59 -0
- package/docs/build-kit.md +57 -0
- package/docs/command-contract.md +27 -0
- package/docs/compatibility.md +46 -0
- package/docs/cookbook.md +25 -0
- package/docs/coverage.md +50 -0
- package/docs/credentials.md +27 -0
- package/docs/design-workflow.md +11 -0
- package/docs/extensions-trust.md +13 -0
- package/docs/formats.md +20 -0
- package/docs/getting-started.md +10 -2
- package/docs/guide.md +83 -9
- package/docs/handoff.md +32 -0
- package/docs/improvements.md +1217 -0
- package/docs/inspection.md +38 -0
- package/docs/layout-settling.md +13 -0
- package/docs/marwes-components.md +34 -0
- package/docs/marwes-tokens.md +46 -2
- package/docs/marwes.md +7 -0
- package/docs/mcp.md +40 -0
- package/docs/mode-verification.md +35 -0
- package/docs/pr-improvements.md +57 -0
- package/docs/recovery.md +25 -0
- package/docs/releasing.md +142 -22
- package/docs/run-file.md +71 -0
- package/docs/shots.md +15 -0
- package/docs/skill-installation.md +16 -0
- package/docs/snapshots.md +20 -0
- package/docs/status.md +7 -0
- package/docs/team.md +17 -0
- package/docs/token-editing.md +22 -0
- package/docs/tradient-followups.md +77 -0
- package/docs/trials/report-template.json +76 -0
- package/docs/trials.md +41 -0
- package/docs/troubleshooting.md +26 -0
- package/docs/upstream/marwes-1.11.0-evidence.json +15 -0
- package/docs/upstream/marwes-requests.md +35 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,258 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="https://raw.githubusercontent.com/niklas-westman/penpair/main/docs/assets/logo.png" alt="penpair" width="120" height="120">
|
|
3
|
-
</p>
|
|
1
|
+
# penpair
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
**Design together. Build together.** Connect people and agents to the same Penpot design, with pinned targets, recoverable writes and independent readback.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Inspect structure and tokens, compose reviewed recipes, capture references and hand design evidence to the implementation. Penpot holds the design; Git holds declarations, snapshots and review records.
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
One command connects your AI coding assistant to your <a href="https://penpot.app">Penpot</a> design file,<br>
|
|
11
|
-
so people and agents work on the same design, safely and repeatably.
|
|
12
|
-
</p>
|
|
7
|
+
## Choose your path
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
<a href="https://www.npmjs.com/package/@penpair/web"><img alt="npm" src="https://img.shields.io/npm/v/@penpair/web?color=0A5CFF&label=%40penpair%2Fweb"></a>
|
|
16
|
-
<a href="https://github.com/niklas-westman/penpair/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/niklas-westman/penpair/actions/workflows/ci.yml/badge.svg"></a>
|
|
17
|
-
<a href="https://github.com/niklas-westman/penpair/blob/main/LICENSE"><img alt="MIT licence" src="https://img.shields.io/badge/licence-MIT-0A5CFF"></a>
|
|
18
|
-
<img alt="Node 22 or later" src="https://img.shields.io/badge/node-%E2%89%A522-0A5CFF">
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
<p align="center">
|
|
22
|
-
<a href="#start-here-the-setup-guide"><strong>Start here</strong></a> ·
|
|
23
|
-
<a href="#why-penpair">Why</a> ·
|
|
24
|
-
<a href="#quick-start">Quick start</a> ·
|
|
25
|
-
<a href="#web-or-docker">Web or Docker</a> ·
|
|
26
|
-
<a href="#packages">Packages</a> ·
|
|
27
|
-
<a href="#use-it-with-marwes">Marwes</a> ·
|
|
28
|
-
<a href="#check-each-page-with-reflection">Reflection</a> ·
|
|
29
|
-
<a href="#for-an-ai-agent">For an AI agent</a>
|
|
30
|
-
</p>
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
penpair makes a Penpot design file the **source of truth** for your project's UI, and makes your AI assistant dependable at working in it. Prompt for a whole page; the assistant reads the real design, builds with tested operations that look first, write once and read back, and follows written-down rules for how to work in Penpot. You review the result in Penpot as usual, and [Reflection](#check-each-page-with-reflection) can check the page it later builds in code against the design.
|
|
35
|
-
|
|
36
|
-
| | |
|
|
9
|
+
| You want to… | Start here |
|
|
37
10
|
| --- | --- |
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
The easiest way in is to let your AI assistant walk you through it. **[penpair-setup-guide](https://github.com/niklas-westman/penpair/blob/main/skills/penpair-setup-guide/SKILL.md)** is a skill for Claude Code, Codex and GitHub Copilot that guides you from nothing to a first design, one step at a time:
|
|
46
|
-
|
|
47
|
-
1. checks Node and your project,
|
|
48
|
-
2. asks whether Penpot runs **on the web** (recommended) or **in Docker** on your computer,
|
|
49
|
-
3. helps you get a Penpot account and a design file,
|
|
50
|
-
4. installs penpair and connects the Penpot plugin,
|
|
51
|
-
5. shows you **where your MCP link goes**, without the key ever passing through the chat, and verifies it all with `doctor`,
|
|
52
|
-
6. asks whether you want **Marwes UI** tokens in Penpot,
|
|
53
|
-
7. installs the rules your assistant follows,
|
|
54
|
-
8. and ends with a **test POC** on its own page, so you see what it can do.
|
|
55
|
-
|
|
56
|
-
**No install needed:** in your project's folder, tell your assistant:
|
|
57
|
-
|
|
58
|
-
> Read https://raw.githubusercontent.com/niklas-westman/penpair/main/skills/penpair-setup-guide/SKILL.md and follow it to set up penpair in this project.
|
|
59
|
-
|
|
60
|
-
**Or install it as a skill** in your project, so it is there next time (also the way to go if your assistant cannot open web pages):
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Claude Code
|
|
64
|
-
mkdir -p .claude/skills/penpair-setup-guide && curl -fsSL https://raw.githubusercontent.com/niklas-westman/penpair/main/skills/penpair-setup-guide/SKILL.md -o .claude/skills/penpair-setup-guide/SKILL.md
|
|
65
|
-
# Codex
|
|
66
|
-
mkdir -p .agents/skills/penpair-setup-guide && curl -fsSL https://raw.githubusercontent.com/niklas-westman/penpair/main/skills/penpair-setup-guide/SKILL.md -o .agents/skills/penpair-setup-guide/SKILL.md
|
|
67
|
-
# GitHub Copilot
|
|
68
|
-
mkdir -p .github/skills/penpair-setup-guide && curl -fsSL https://raw.githubusercontent.com/niklas-westman/penpair/main/skills/penpair-setup-guide/SKILL.md -o .github/skills/penpair-setup-guide/SKILL.md
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Then ask: *"set up penpair"*. Prefer to do it yourself? Read on: [Quick start](#quick-start).
|
|
72
|
-
|
|
73
|
-
## Why penpair
|
|
74
|
-
|
|
75
|
-
An AI assistant can already reach Penpot through its MCP server: it gets one tool that runs code inside your open file. That is power without hands-on knowledge, and designing a whole page that way fails in small, expensive ways. penpair is what makes it dependable, so you can prompt for a page and get a page.
|
|
76
|
-
|
|
77
|
-
**What goes wrong with a bare connection**, all met while building real pages:
|
|
78
|
-
|
|
79
|
-
- The first export after a change is often a stale render, so the assistant "sees" a defect that is not there, or misses one that is.
|
|
80
|
-
- A token binding appears seconds after the call, so a correct write reads back as "not bound", and applying the same token twice removes it.
|
|
81
|
-
- A request over about 110 KB is refused, a switched page takes effect only after the script ends, and a sleeping browser tab raises an error even though the write already landed.
|
|
82
|
-
- Every new session rediscovers these, and a plain write cannot tell "done" from "half done".
|
|
83
|
-
|
|
84
|
-
**What penpair adds:**
|
|
85
|
-
|
|
86
|
-
| | What you get | Why it matters for a full page |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
| **Skills** | The traps, the flat auto-layout rules, token binding and screen-design judgment, written for the agent and installed into your repo | The assistant starts out knowing what took us hours to learn, and follows your component library and tokens instead of inventing styles |
|
|
89
|
-
| **A safe write protocol** | Look first, write once, read back from scratch, then look at an export | A page is built in verified steps; a half-finished change is reported, not assumed |
|
|
90
|
-
| **Stable exports and an audit** | Exports that wait for a stable render; a check for unbound colours, spacing off your scale, overflow and blank text | The assistant can judge its own page and show you evidence |
|
|
91
|
-
| **One-command setup and `doctor`** | A connection that is checked, a key that never leaves your machine, an agent-readable `doctor --json` | A new teammate or a new agent is productive in minutes |
|
|
92
|
-
|
|
93
|
-
**What a prompt can look like** once it is set up:
|
|
94
|
-
|
|
95
|
-
> Design the candidate list page for tablet portrait as master and detail. Use our list and card components, bind every value to tokens, keep the header from the other pages. Check it, export it and show me.
|
|
96
|
-
|
|
97
|
-
The assistant reads the project's skills, inspects the file, builds the page in one verified write, exports it (twice, so the render is stable), audits it and shows you the frames. You review in Penpot as usual. The protocol and the rules come from building a full page, in desktop, tablet and phone variants, on a real product.
|
|
98
|
-
|
|
99
|
-
penpair does not design for you, and it has no "make me a page" button. It makes the assistant that does the designing reliable, repeatable and checkable.
|
|
11
|
+
| Set up your first project | [Getting started](docs/getting-started.md) |
|
|
12
|
+
| Let an agent guide setup | [Setup guide skill](skills/penpair-setup-guide/SKILL.md) |
|
|
13
|
+
| Join a shared file | [Team guide](docs/team.md) |
|
|
14
|
+
| Build and review a page | [Design workflow](docs/design-workflow.md) |
|
|
15
|
+
| Resolve a failure | [Troubleshooting](docs/troubleshooting.md) |
|
|
16
|
+
| Extend the framework | [Guide and reference map](docs/guide.md) |
|
|
100
17
|
|
|
101
18
|
## Quick start
|
|
102
19
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
You need [Node.js](https://nodejs.org) 22 or later and a terminal. In your project's folder:
|
|
20
|
+
Use Node 22 or newer, your project folder and a visible Penpot editor. Enter the personal MCP address only in the private connection prompt.
|
|
106
21
|
|
|
107
|
-
```
|
|
108
|
-
|
|
22
|
+
```sh
|
|
23
|
+
pnpm add -D @penpair/web
|
|
24
|
+
pnpm exec penpair init --external https://design.penpot.app --gitignore
|
|
25
|
+
pnpm exec penpair connect
|
|
26
|
+
pnpm exec penpair connect --pin-file
|
|
27
|
+
pnpm exec penpair doctor --json
|
|
28
|
+
pnpm exec penpair skills install --dry-run
|
|
109
29
|
```
|
|
110
30
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
It asks where your Penpot is and does the rest, saying in plain words what it is doing and what it is waiting for:
|
|
114
|
-
|
|
115
|
-
1. writes a small config you can commit (`penpair.config.json`, no secrets),
|
|
116
|
-
2. takes your personal key **without showing it** and keeps it in a git-ignored `.env.local`,
|
|
117
|
-
3. waits while you connect the plugin in your browser (**File, MCP Server, Connect**),
|
|
118
|
-
4. reads your file once to prove it works,
|
|
119
|
-
5. installs the rules your AI assistant follows.
|
|
120
|
-
|
|
121
|
-
Run it again any time; it continues where it stopped. `npx @penpair/web doctor` tells you what works, what does not and what to do about it.
|
|
122
|
-
|
|
123
|
-
Then commit what it made (never `.env.local`; it is already git-ignored):
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
git add penpair.config.json .gitignore .skills .agents .claude
|
|
127
|
-
git commit -m "Add penpair"
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Each teammate runs their own `npx @penpair/web connect`: the key belongs to a person, not to the repo. To pin penpair for the team, add it as a dev dependency (`npm i -D @penpair/web`, or `@penpair/docker`; one of the two, not both); the command is then `npx penpair …`. To write your own Penpot operations, use the library from the same package: `import { defineSnippet } from "@penpair/web"`.
|
|
131
|
-
|
|
132
|
-
## Web or Docker
|
|
133
|
-
|
|
134
|
-
Two ways to run Penpot, one way to connect. Pick one; you can switch later.
|
|
135
|
-
|
|
136
|
-
| | **On the web** (recommended) | **In Docker, on your computer** |
|
|
137
|
-
| --- | --- | --- |
|
|
138
|
-
| What | [design.penpot.app](https://design.penpot.app) (free account) or your team's own Penpot | A private Penpot per project on your machine |
|
|
139
|
-
| Install | `@penpair/web` | `@penpair/docker` instead (it includes everything in `@penpair/web`), Docker Desktop, a 2 GB first download |
|
|
140
|
-
| Good for | Getting going fast, sharing with a team by invite | Offline work, data that stays with you, an instance you can throw away |
|
|
141
|
-
| Backup | Penpot's version history and its `.penpot` export | `penpair backup`, `backup verify`, `restore` |
|
|
142
|
-
| Set it up | `npx @penpair/web setup --web` | `npx @penpair/docker setup --docker` (or `npm i -D @penpair/docker`, then `npx penpair setup --docker`) |
|
|
31
|
+
Confirm the owner-authorized file ID, then follow the [skill installation preview](docs/skill-installation.md). Each teammate uses their own key. Prefer a team/web Penpot; [Docker is an alternative](packages/core/skills/penpair-setup/references/web-or-docker.md).
|
|
143
32
|
|
|
144
|
-
|
|
33
|
+
This improvement branch is **unreleased**. Registry installs do not establish that these changes are present: review the branch or use the complete packed-package trial in the [trial runbook](docs/trials.md). Live/human checks remain explicitly pending.
|
|
145
34
|
|
|
146
|
-
## Packages
|
|
35
|
+
## Packages and workflows
|
|
147
36
|
|
|
148
|
-
|
|
149
|
-
| --- | --- | --- |
|
|
150
|
-
| [`@penpair/web`](https://www.npmjs.com/package/@penpair/web) | The `penpair` command for Penpot on the web. Includes everything in the core | Published |
|
|
151
|
-
| [`@penpair/docker`](https://www.npmjs.com/package/@penpair/docker) | The same command, plus Penpot in Docker on your computer: `up`, `down`, `backup`, `restore`. Includes everything in the core | Published |
|
|
152
|
-
| [`@penpair/core`](https://www.npmjs.com/package/@penpair/core) | The library and engine, with the skills. No command of its own; the two above are built on it | Published |
|
|
153
|
-
| `penpair` | The short name for `@penpair/web`, so that `npx penpair setup` works | Waiting for npm to allow the name (it is blocked as too close to an unrelated package). Until then use `npx @penpair/web`. |
|
|
154
|
-
| [`@penpair/marwes-ui`](https://www.npmjs.com/package/@penpair/marwes-ui) | The adapter for the Marwes UI component library: Marwes tokens (and your theme) in Penpot, with drift checks | Published: tokens now, components planned. See below |
|
|
155
|
-
| `@penpair/reflection-check` | The link to Reflection's `reflection-check`, as its own package (today `penpair baselines` does it) | Planned |
|
|
37
|
+
Install one command package: `@penpair/web`, or `@penpair/docker` for a local instance. Both use the neutral `@penpair/core` library. `penpair` is the short-name wrapper. Optional adapters are [`@penpair/marwes-ui`](docs/marwes.md) and [`@penpair/reflection-check`](packages/reflection-check/README.md). Every package is released together at one version; publishing belongs to the owner.
|
|
156
38
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## Use it with Marwes
|
|
160
|
-
|
|
161
|
-
Using [Marwes UI](https://marwes.io), the themeable component library for React, Vue and Svelte? Connecting Marwes is the recommended setup: your Penpot file then speaks the same tokens as your code, so what is designed is what gets built.
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
npm i -D @penpair/marwes-ui
|
|
165
|
-
npx penpair marwes tokens snapshot --write # the expected tokens, from the installed Marwes and your own theme
|
|
166
|
-
npx penpair marwes tokens push --apply # Penpot gets them: Marwes sets, plus a small overlay of what your theme changes
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
The part that keeps it solid is drift: a Marwes upgrade, a theme edit or a hand edit in Penpot is found, not discovered later. `penpair marwes tokens check` runs offline in CI and fails when the committed snapshot no longer matches the installed Marwes; `diff` shows what Penpot lacks and how many shapes each changed token reaches. How it works and how to put it in CI: [Marwes tokens in Penpot, and keeping them true](https://github.com/niklas-westman/penpair/blob/main/docs/marwes-tokens.md). Marwes components in Penpot are not covered yet; they are built as your pages need them.
|
|
170
|
-
|
|
171
|
-
## Check each page with Reflection
|
|
172
|
-
|
|
173
|
-
Designing a page is half the job; the other half is knowing the assistant built what the design says. We recommend pairing penpair with [Reflection](https://github.com/niklas-westman/reflection) (`reflection-check` on npm), which renders your real UI in a browser and compares it with an approved image, state by state, and shows you the difference.
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
prompt ──▶ the assistant designs the page in Penpot ──▶ penpair export ──▶ an approved image per page and state
|
|
177
|
-
│
|
|
178
|
-
you review ◀── Reflection: the rendered page against that image, with a diff ◀── the assistant builds the page in code
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Why this pairing helps:
|
|
182
|
-
|
|
183
|
-
- **The design is the reference, not the assistant's memory.** The exported frame is what the delivery is measured against, so a misreading of the design shows up as a visible difference, not as a surprise in review.
|
|
184
|
-
- **Each page and state is one piece of evidence.** You look at a diff for the list, the open card, the empty state, the phone variant, instead of re-reading the whole change.
|
|
185
|
-
- **Nothing passes on a promise.** Reflection is review-first: a mismatch is shown to you, and an approved image only changes when you say so.
|
|
186
|
-
|
|
187
|
-
**The hand-off is one config block and one command.** List the frames of a page that are the design of a route, in `penpair.config.json`:
|
|
188
|
-
|
|
189
|
-
```json
|
|
190
|
-
"baselines": {
|
|
191
|
-
"page": "Candidate",
|
|
192
|
-
"root": "tests/baselines",
|
|
193
|
-
"frames": [
|
|
194
|
-
{ "frame": "F / candidates", "id": "candidates-desktop", "route": "candidates" },
|
|
195
|
-
{ "frame": "F tablet / candidates", "id": "candidates-tablet", "route": "candidates" }
|
|
196
|
-
]
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
npx @penpair/web baselines # look: which images are new, the same, or changed in the design
|
|
202
|
-
npx @penpair/web baselines --apply # write the new images, a manifest and reflection-cases.json
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
penpair exports each frame at a stable render, writes `tests/baselines/<id>.png`, and generates the Reflection cases for you: each frame's exact size becomes the viewport, so the capture and the reference have the same dimensions, and each check carries `ref: "penpair:<id>"` to join it back to the frame. In your `reflection.config.ts` you add the generated viewports to the route and spread the generated `visualSmoke` into the browser contract (a working example is in the [guide](https://github.com/niklas-westman/penpair/blob/main/docs/guide.md#baselines-for-a-check)), then run `npx reflection run --config reflection.config.ts --mode smoke`.
|
|
206
|
-
|
|
207
|
-
A changed design is never silently approved: when a frame no longer matches its image, `penpair baselines` says `changed` and leaves the image alone until you run `--apply --update`, which is the act of approving the new design. We ran this chain end to end (Penpot frame, `penpair baselines`, Reflection, browser, diff) and the check, its diff image and its `ref` all came out right.
|
|
208
|
-
|
|
209
|
-
One thing to know before you rely on it: the percentage is not a verdict. A correct implementation already differs from a Penpot render by about 1 to 2% on text alone (fonts, anti-aliasing), and a faint colour shift or a one-line copy change can sit below that. Read the diff image and Reflection's diagnostics; they point at the likely cause. What is still planned is the richer link, where each page's states are read from Penpot and shown in Reflection's own viewer (through `reflection-projection`, already tried on a real page of 30 states).
|
|
210
|
-
|
|
211
|
-
## For an AI agent
|
|
212
|
-
|
|
213
|
-
Guiding a person through a first setup? Follow [penpair-setup-guide](https://github.com/niklas-westman/penpair/blob/main/skills/penpair-setup-guide/SKILL.md). Repairing a project that is already set up? Follow [penpair-setup](https://github.com/niklas-westman/penpair/blob/main/packages/core/skills/penpair-setup/SKILL.md). Either way:
|
|
214
|
-
|
|
215
|
-
- `npx @penpair/web doctor --json` says where things stand; every failing check carries a `remedy`.
|
|
216
|
-
- Web is the default; Docker only when the person asks for offline work or local data.
|
|
217
|
-
- **The personal MCP key never goes through you.** The person pastes it into `npx @penpair/web connect` (a hidden prompt), or pipes it from their clipboard.
|
|
218
|
-
- Done means `doctor` reports `ok: true` and "editor and plugin" connected. Then `skills install` gives you the rules for working in Penpot (`penpair-design`) and for judging a screen (`design-judgment`).
|
|
219
|
-
|
|
220
|
-
## Commands
|
|
221
|
-
|
|
222
|
-
`npx @penpair/web --help` lists them all. The ones you need first:
|
|
223
|
-
|
|
224
|
-
| Command | What it does |
|
|
39
|
+
| Shipped skill | Use |
|
|
225
40
|
| --- | --- |
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
231
|
-
| `baselines [--apply] [--update]` | Export a page's frames as approved images, with ready-made Reflection cases |
|
|
232
|
-
| `skills install` | The agent's rules, into `.skills/`, visible to Codex and Claude Code |
|
|
233
|
-
| `up`, `down`, `backup`, `restore` | Docker only (they come with `@penpair/docker`) |
|
|
234
|
-
|
|
235
|
-
## Good to know
|
|
236
|
-
|
|
237
|
-
- Penpot's plugin needs the **editor tab open and visible** while the assistant works, so penpair cannot run unattended overnight. It acts on one Penpot at a time, on the page you have open.
|
|
238
|
-
- Checked against Penpot 2.18.3 (Docker) and the hosted Penpot 2.18.4: the same tools and limits, and a full deep check on both ([compatibility](https://github.com/niklas-westman/penpair/blob/main/docs/compatibility.md)). Not proven yet: a large file with a shared library on the hosted service, paid plans, two editors in one file, and a full `.penpot` export and import round trip.
|
|
239
|
-
- penpair is early (0.1.x). Expect rough edges and quick fixes. The plan and the open questions are in [PLAN.md](https://github.com/niklas-westman/penpair/blob/main/PLAN.md).
|
|
240
|
-
|
|
241
|
-
## Documentation
|
|
242
|
-
|
|
243
|
-
- [Setup guide skill](https://github.com/niklas-westman/penpair/blob/main/skills/penpair-setup-guide/SKILL.md): your assistant walks you through the whole setup
|
|
244
|
-
- [Getting started](https://github.com/niklas-westman/penpair/blob/main/docs/getting-started.md): step by step by hand, for designers and for whoever sets it up
|
|
245
|
-
- [Guide](https://github.com/niklas-westman/penpair/blob/main/docs/guide.md): extension points, instances, commands
|
|
246
|
-
- [Marwes tokens](https://github.com/niklas-westman/penpair/blob/main/docs/marwes-tokens.md): Marwes tokens in Penpot and drift checks
|
|
247
|
-
- [Compatibility](https://github.com/niklas-westman/penpair/blob/main/docs/compatibility.md): what was checked on which Penpot
|
|
248
|
-
- [Releasing](https://github.com/niklas-westman/penpair/blob/main/docs/releasing.md), [Changelog](https://github.com/niklas-westman/penpair/blob/main/CHANGELOG.md)
|
|
249
|
-
|
|
250
|
-
penpair is the other half of [Reflection](https://github.com/niklas-westman/reflection): Reflection checks that an implementation matches its design; penpair holds and drives the design that everything is measured against.
|
|
251
|
-
|
|
252
|
-
## Contributing and security
|
|
253
|
-
|
|
254
|
-
Issues and small pull requests are welcome: see [CONTRIBUTING](https://github.com/niklas-westman/penpair/blob/main/CONTRIBUTING.md). Your Penpot MCP key is a password; read [SECURITY](https://github.com/niklas-westman/penpair/blob/main/SECURITY.md) before you report a problem.
|
|
41
|
+
| [penpair-setup](packages/core/skills/penpair-setup/SKILL.md) | Connect or repair project setup |
|
|
42
|
+
| [penpair-design](packages/core/skills/penpair-design/SKILL.md) | Inspect and operate on a design file |
|
|
43
|
+
| [penpair-recipes](packages/core/skills/penpair-recipes/SKILL.md) | Compose a page with reviewed recipes |
|
|
44
|
+
| [penpair-handoff](packages/core/skills/penpair-handoff/SKILL.md) | Implement from design evidence and compare |
|
|
45
|
+
| [design-judgment](packages/core/skills/design-judgment/SKILL.md) | Assess page structure and hierarchy |
|
|
255
46
|
|
|
256
|
-
|
|
47
|
+
[Cookbook](docs/cookbook.md) · [MCP tools](docs/mcp.md) · [Format contracts](docs/formats.md) · [Compatibility](docs/compatibility.md) · [Recovery](docs/recovery.md) · [Living checklist](docs/improvements.md)
|
|
257
48
|
|
|
258
|
-
[
|
|
49
|
+
See [CONTRIBUTING](CONTRIBUTING.md), [SECURITY](SECURITY.md), [release steps](docs/releasing.md), [CHANGELOG](CHANGELOG.md) and the [MIT licence](LICENSE). Comparison metrics supply evidence for review; they do not approve a design.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Owner approval records
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
penpair approve --page Dashboard --frame FRAME_ID
|
|
5
|
+
# After exporting, looking twice and obtaining the owner's exact words:
|
|
6
|
+
penpair approve --page Dashboard --frame FRAME_ID --write \
|
|
7
|
+
--note 'EXACT_OWNER_WORDS' --by 'OWNER_IDENTITY'
|
|
8
|
+
# Include --case CONFIGURED_BASELINE_ID for a rich theme/state/fixture case.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Preview captures stable evidence and records nothing. Recording captures again and appends a schema-1 owner-words record to `design/approvals.json`, with file/page/frame IDs, revision, scale, actual theme/render context, optional declared case, PNG SHA-256, date, identity, exact note and record hash. The immutable PNG lives under `design/approvals/SHA256.png`. A local exclusive append lock prevents lost records; a stale lock requires inspection. The file is atomically replaced after image bytes exist. Prior records are retained.
|
|
12
|
+
|
|
13
|
+
The command records supplied words; it cannot authenticate a person or establish consent. An agent needs the owner's actual approval for the exact reviewed artifact and must never invent words or run this as a routine automatic success step. No approvals were recorded by this implementation task. Content hashes detect changes, not identity. An empty note/identity, unstable capture, mismatched case, altered approved bytes or credential redaction refuses publication.
|
|
14
|
+
|
|
15
|
+
`baselines --apply --update` now requires a record matching the changed artifact's exact image and capture/case fingerprint. An unrelated revision, file, page, frame, scale, active theme context or case cannot reuse it. Preview/capture inspection remains available without approval. First-reference capture retains its existing explicit `--apply` behavior; historical and initial references are not retroactively attributed to owner words.
|
|
16
|
+
|
|
17
|
+
An exceptional `--apply --update --force-reason 'EXPLICIT_REASON'` may save a reference without a matching owner record. Its provenance says `forced-reference`; the reason persists. Rich case coverage and the implementation-case adapter refuse to count that waiver as owner-approved evidence. A waived reference is useful for an experiment, but cannot silently satisfy the design acceptance gate. Empty reasons and reasons without apply/update refuse.
|
|
18
|
+
|
|
19
|
+
The latest revision is a conservative invalidation boundary: an unrelated design edit may require fresh approval even if pixels stay equal. A PNG alone does not approve structure, behavior or accessibility. Approval records remain local/project artifacts until reviewed; no publish, client configuration or consumer-repository edit occurs automatically.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Capture and approval provenance
|
|
2
|
+
|
|
3
|
+
Baseline manifests use schema 2. `capturedRevision` identifies the coherent capture run, while each frame has separate `captured` and `approved` records. Those records identify the file, page and frame by immutable ID, revision, PNG hash, scale, active theme names, capture time, penpair version, reported Penpot API version and browser user agent when available. A missing environment value is recorded as unknown; the record does not establish that fonts loaded correctly or identify every rendering dependency.
|
|
4
|
+
|
|
5
|
+
An unchanged reference retains its original approval record. A changed image recaptured without `--update` retains its approved bytes, path, revision and hash, while `captured` describes the new candidate. Legacy images without trustworthy provenance are marked `legacy`; a current revision is never invented for them. Changing a baseline still requires the owner's `--apply --update`. Schema 2 does not claim a separate human review record; that is tracked in D2.
|
|
6
|
+
|
|
7
|
+
All configured frames must be present and stable. File/page/revision evidence is checked inside exports and across consecutive exports; the complete frame listing is checked again after capture. Intervening changes, failures or cancellation do not publish a new complete manifest. There is no partial-publication mode.
|
|
8
|
+
|
|
9
|
+
PNG captures live in immutable `captures/<capture-id>/<baseline-id>.png` generations instead of replacing `<baseline-id>.png`. Read approved image paths from manifest frame `file` fields. Generated Reflection cases already use those paths. `penpair-baselines.json` is the authoritative commit point and is published last with a same-directory atomic rename. Its `casesFile` points to the exact immutable case file for that generation. The root `reflection-cases.json` remains a convenience for existing config imports; when reading it together with a manifest, require matching `captureId`, or follow `casesFile` from the manifest. Independent root files cannot be replaced as one filesystem transaction.
|
|
10
|
+
|
|
11
|
+
A process interrupted during output may leave an unreferenced capture generation. Previous manifest references remain usable; inspect before cleaning generations. Staging directories are removed on handled errors. This is filesystem publication safety, not a power-loss-proof multi-file transaction or a remote backup.
|
|
12
|
+
|
|
13
|
+
Standalone PNG export and full-file archive candidates also stage complete bytes before publication. Archives use exclusive creation and never replace an existing file. PNG exports atomically replace the requested output only after a stable result. The export report includes target provenance. Archive restore verification remains a separate scratch import/readback task.
|
package/docs/audits.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Configured advisory audits
|
|
2
|
+
|
|
3
|
+
Configure an optional `audit` block with boolean `contrast`, `library`, `touchTargets`, `names`, `blankText`; explicit `interactiveIds`, `allowBlank`; optional `nameSegments` (default 3); and reasoned exact-node `suppressions: [{rule,nodeId,reason}]`. Unknown settings refuse before connecting. The configured audit uses a complete page-root inspection; legacy filters remain available when configured rules are absent.
|
|
4
|
+
|
|
5
|
+
Run `audit --page PAGE`, or offline `audit --from INSPECTION --token-source TOKENS`. The offline token catalog must share exact file/revision and contain actual resolved values in active set order. Missing, foreign, mixed, transparent, gradient/image, unbound or mismatched paint evidence remains unknown. Contrast measures an opaque sRGB subset and checks that observed paint matches the resolved binding. It does not infer compositing, overlapping paints or browser font/rendering behavior.
|
|
6
|
+
|
|
7
|
+
The review thresholds follow [WCAG contrast guidance](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html): 4.5 ordinarily, 3 for the explicitly assumed large text size/weight. Ratios are compared without rounding. Design units are assumed to correspond to CSS pixels; exceptions and actual implementation behavior still require review. This tool does not establish accessibility compliance.
|
|
8
|
+
|
|
9
|
+
Touch-size signals apply only to explicit interactive IDs. Below 24 units is a warning; below 44 is a preference signal. The [minimum target guidance](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html) includes spacing and other exceptions; [enhanced targets](https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html) use a larger threshold. Size alone does not prove clickability, CSS scale, spacing, keyboard support or conformance.
|
|
10
|
+
|
|
11
|
+
Library coverage reports native copy instances among known nodes, unknown component fields and the largest observed primitive groups. It is a composition signal, not a component quality score. Naming checks slash-separated frame segments/default names. Blank/placeholder text can be allowed by exact node ID; suppressions retain the finding and reason visibly.
|
|
12
|
+
|
|
13
|
+
Findings are advisory and never mutate the file. Complete findings return exit 0 even with warnings; partial source/refused inspection or bounded output truncation remains incomplete (3). The report retains exact total counts, suppressions, source identity and assumptions. A successful traversal does not convert unobserved fields into known values.
|
package/docs/backup.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Hosted-file backup and restore
|
|
2
|
+
|
|
3
|
+
An exported archive is a candidate backup. It becomes a proven recovery path after a successful import and independent content/render comparison. Named Penpot versions and structural JSON snapshots help with recovery/review, but neither is an independent full-file backup.
|
|
4
|
+
|
|
5
|
+
## Export
|
|
6
|
+
|
|
7
|
+
1. Confirm the intended source file's name and ID (`penpair doctor --json`). Pin its ID with `penpair connect --pin-file`.
|
|
8
|
+
2. In Penpot, export the file as `.penpot` through the workspace File menu or dashboard. Include shared library assets according to the recovery you need: retaining external references still depends on those libraries being available. Keep the original archive outside public Git and record its hash, source file ID, export date, Penpot version and library export choice.
|
|
9
|
+
3. Save a new dated archive; retain prior proven backups until the new one has passed the restore check. Keep a separate copy in storage the owner controls.
|
|
10
|
+
|
|
11
|
+
An optional command tries the documented full-file API: `penpair backup file <new.penpot>`. It requests `currentFile.export("penpot", "all")`, checks target/revision stability, validates received byte length, and prints the SHA-256. It never replaces an existing archive. The default raw archive budget is 512 KiB to stay within the tested response envelope; larger files and unsupported/failing APIs point to the manual route. The library's `exportFileArchive` accepts an explicit budget up to 16 MiB, whose transport compatibility must be tested before relying on it.
|
|
12
|
+
|
|
13
|
+
Receiving bytes does not validate the archive's internal format or prove restoration. The command reports `restoreVerified: false`. The previously observed “No matching clause” error is still an unverified live compatibility issue; supplying the documented library-export argument is a candidate to test, not a claimed fix.
|
|
14
|
+
|
|
15
|
+
## Prove the restore on scratch
|
|
16
|
+
|
|
17
|
+
- [ ] Export the source using the chosen library mode and record the provenance/hash.
|
|
18
|
+
- [ ] Import it into a disposable project/file on the intended target Penpot version. Keep the source and its previous proven backup untouched.
|
|
19
|
+
- [ ] Connect from a separate scratch project configuration and pin the imported file ID. Import may change IDs; compare logical names, content, component relationships and token bindings rather than requiring identical imported IDs.
|
|
20
|
+
- [ ] Compare pages and frames, node counts, texts, geometry, library components/instances/overrides, images and fonts, token sets, aliases, set order, themes and effective values. Check an instance's relationship to its main component rather than just its appearance.
|
|
21
|
+
- [ ] Export representative frames in light/dark and intended viewport/state combinations. Wait for stable exports and look twice; compare visually with the source references.
|
|
22
|
+
- [ ] Record the target version, imported file ID, checks, differences and owner review. Mark the candidate proven only when every required check passes.
|
|
23
|
+
|
|
24
|
+
If export/import fails or content differs, preserve both observations and the previous backup. Use the manual route and investigate the exact version/library mode. Never restore over the live shared file as a test. A successful UI import alone does not establish full fidelity.
|
|
25
|
+
|
|
26
|
+
[Penpot's export/import documentation](https://help.penpot.app/user-guide/export-import/export-import-files/) describes the manual routes and library choices. Current API documentation may be newer than the pinned runtime; see [compatibility](compatibility.md).
|
|
27
|
+
|
|
28
|
+
## Optional advisory reminder
|
|
29
|
+
|
|
30
|
+
Reminders are disabled by default. Opt in through `penpair.config.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
"backup": { "noteFile": "design/backup-note.json", "remindAfterDays": 7 }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Keep an owner-maintained note containing:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"schemaVersion": 1,
|
|
41
|
+
"fileId": "<pinned source file id>",
|
|
42
|
+
"recordedAt": "<ISO timestamp when the backup evidence was recorded>",
|
|
43
|
+
"archiveSha256": "<64 hexadecimal characters>",
|
|
44
|
+
"restoreVerified": false
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Set `restoreVerified` to true only after the owner completes the scratch comparison. The note can include links to the owner's evidence, but keep credentials and private storage access URLs out of Git. Doctor warns non-blockingly about missing, malformed, mismatched, old or unverified notes. A recent note records an owner's assertion, not independent proof that a backup is current or restorable. The engine never upgrades it after merely receiving archive bytes.
|
|
49
|
+
|
|
50
|
+
## Current verification limits
|
|
51
|
+
|
|
52
|
+
The bounded exporter, target/revision guards, refusal to overwrite, error/budget handling and reminder behavior are tested with fake clients and local files. The first live archive export/import round trip on hosted Penpot 2.18.4 remains outstanding. Structural snapshots (D3) will provide review evidence and remain distinct from archives. G3 stays open until its required live restore evidence is recorded.
|
|
53
|
+
|
|
54
|
+
[Structural snapshots and diffs](snapshots.md) provide bounded review evidence alongside archives; they cannot replace an export/import restore trial.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Improvement branch review
|
|
2
|
+
|
|
3
|
+
Branch: `codex/penpair-improvements`. Review before merging. No push, merge, version bump, tag, publish, deployment, real Penpot mutation or upstream message was performed.
|
|
4
|
+
|
|
5
|
+
The [living checkbox document](improvements.md) contains the priority order and per-item **Resolved / Changed / Validated / Risks / Next** reports. It mirrors the owner's external brief. At handoff, 37 of 40 top-level items are implemented; the three live acceptance items below stay unchecked. Preparation substeps are checked separately.
|
|
6
|
+
|
|
7
|
+
## Review in this order
|
|
8
|
+
|
|
9
|
+
| Area | Main entry points and evidence |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Target safety, execution and recovery | Config/file guards; one-shot ambiguous writes; positive readback; [journals/recovery](recovery.md); [team coordination](team.md) |
|
|
12
|
+
| Bounded inspection and artifact trust | [Inspection](inspection.md), [formats](formats.md), [snapshots](snapshots.md), [artifacts](artifacts.md), [approvals](approvals.md) |
|
|
13
|
+
| Page construction and token ownership | [Reviewed file scripts](run-file.md), [kit](build-kit.md), [token editing](token-editing.md), [adapter](marwes.md), [mode trials](mode-verification.md) |
|
|
14
|
+
| Implementation evidence | [Handoff](handoff.md), [case coverage](coverage.md), [explicit provider adapter](../packages/reflection-check/README.md) |
|
|
15
|
+
| Agent and operator paths | Five shipped skills; [MCP](mcp.md), [credentials](credentials.md), [status/open](status.md), [advisory audits](audits.md), [troubleshooting](troubleshooting.md) |
|
|
16
|
+
| Release/consumer/upstream preparation | [Release gates](releasing.md), [consumer proposal](tradient-followups.md), [unsent upstream requests](upstream/marwes-requests.md) |
|
|
17
|
+
|
|
18
|
+
Commits are grouped by backlog item. Inspect one item/report and its commit before moving to another. Tests use actual generated plugin bodies where possible, fake transports/servers, recorded structures and temporary consumers. The package smoke installs all six tarballs in clean consumers; it does not publish them.
|
|
19
|
+
|
|
20
|
+
## Intentionally open acceptance gates
|
|
21
|
+
|
|
22
|
+
- [ ] **G3:** Owner-authorized hosted `.penpot` export/import and full token/component/asset/render restore comparison. Archive candidates and structural review data do not prove restore fidelity.
|
|
23
|
+
- [ ] **I12:** Two real people complete onboarding, wrong-file prevention, writer conflict, recovery and one real design-to-code route. Use the [runbook/template](trials.md); no synthetic people or timings are recorded as results.
|
|
24
|
+
- [ ] **C1:** Real Button/Card/StatTile fixture measurements and pinned-native-API proof that instance text/style overrides survive updates. Measurement/drift tooling is built; native library generation deliberately refuses until that proof exists. Measured project constants are not fabricated.
|
|
25
|
+
|
|
26
|
+
The owner requested finishing branch code and leaving live trials pending. Those unresolved gates remain visible rather than being counted as delivered behavior. Native generation depends on that live proof and is the material remaining implementation gate.
|
|
27
|
+
|
|
28
|
+
## Decisions to assess before adoption
|
|
29
|
+
|
|
30
|
+
- Read declarations and trusted project modules are contracts, not execution sandboxes. Default built-in inspection avoids extension imports; explicit adapter/provider/registered-script paths execute trusted code.
|
|
31
|
+
- Shared writer markers are advisory across machines. Journals preserve uncertainty and support explicit reconciliation; they are not transactions, rollback or exactly-once execution.
|
|
32
|
+
- Typed handoff/mapping hints preserve literal design data. They do not infer application state, component semantics, responsive behavior or accessibility from pixels.
|
|
33
|
+
- Rich comparison cases need actual project setup and independent browser readback. Pixel ratios, size signals and heuristic hue findings remain evidence for review.
|
|
34
|
+
- Changed baseline references require exact owner-word evidence or a visible forced-reference waiver; waived references do not satisfy approved rich coverage.
|
|
35
|
+
- The project MCP server has an immutable explicit root/target, strict file guards and bounded responses. Registered writes require server opt-in and call-level apply. Cancellation remains cooperative for trusted code.
|
|
36
|
+
- Private file/Keychain providers are implemented, but no real personal store was exercised. General credential commands remain deferred.
|
|
37
|
+
- The release workflow requires owner activation, protected-environment policy and npm publisher settings. No remote policy or actual OIDC/provenance result has been established. All packages remain 0.1.3; the branch is unreleased.
|
|
38
|
+
- Consumer changes and upstream sends remain owner actions. Existing optional trend/Card composition and the consumer SidebarNav corrected parts of the original brief; historical measurements remain version/context-scoped.
|
|
39
|
+
|
|
40
|
+
## Release automation follow-up
|
|
41
|
+
|
|
42
|
+
R1 adds Changesets version PRs with one fixed group for all six packages and an
|
|
43
|
+
API-verified exact merged-PR gate before publishing. Ordinary feature merges do
|
|
44
|
+
not publish. The PR token has no npm role; publishing uses OIDC and retains the
|
|
45
|
+
existing release guards. Both paths start disabled. Review the [activation
|
|
46
|
+
checklist](releasing.md#one-time-owner-activation) and [local PR draft](pr-improvements.md).
|
|
47
|
+
The pending minor note proposes 0.2.0; actual package versions remain 0.1.3.
|
|
48
|
+
|
|
49
|
+
R2 adds the owner-requested temporary `PENPAIR_RELEASE_EXCLUDE_PENPAIR=true`
|
|
50
|
+
publishing exception for the short-name wrapper. All six versions still move
|
|
51
|
+
together; five required packages must verify and the omission is explicitly
|
|
52
|
+
reported. Remove the variable to restore full-family publishing and use explicit
|
|
53
|
+
continuation to catch up the current missing wrapper. See [release exception](releasing.md#temporary-exception-for-the-short-name).
|
|
54
|
+
|
|
55
|
+
## Validation and handoff
|
|
56
|
+
|
|
57
|
+
Final verification covers `pnpm check`, `pnpm smoke:package`, docs/anchor and generated-remedy checks, targeted skill-entrypoint validation, release helper syntax and workflow YAML boundaries. The final recorded suite has 583 tests across core, adapter, implementation bridge and Docker. The final full branch check and six-package smoke both passed before handoff. Live/editor/browser/human acceptance remains pending as listed above.
|
|
58
|
+
|
|
59
|
+
Review this branch locally against main. External authorization is needed before any push, merge, release activation, publishing, version bump, consumer write or non-scratch Penpot change. No new confirmation is needed for the already authorized local implementation work.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Build kit and recipes
|
|
2
|
+
|
|
3
|
+
`penpair run-file ... --include @kit` defines the neutral plugin namespace `pp`. The library exports the same plain JavaScript text as `kit()`; concatenate it into a guarded snippet rather than importing Node code into the plugin. Inclusion defines functions and recipes without touching the document.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
penpair kit check
|
|
7
|
+
penpair recipes
|
|
8
|
+
penpair kit check --include @kit --include @marwes-kit
|
|
9
|
+
penpair recipes --include @kit --include @marwes-kit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The last two examples require the token adapter configured in the adopting project. A normal core check/list or `@kit` include does not import unrelated adapters. Explicit extension aliases load configured trusted adapter code to discover their source. Dependencies must appear earlier in include order; ambiguous aliases or recipe names refuse. Each kit is at most 32 KiB and combined kit source at most 48 KiB, leaving room within the final wrapped 90 KB snippet guard. Reports retain bytes and source hashes.
|
|
13
|
+
|
|
14
|
+
## Neutral helpers
|
|
15
|
+
|
|
16
|
+
| Helper | Contract |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `tok(name)` | Reads current active sets in catalog order; later active sets win. Missing names and duplicates inside a set refuse. No inactive-set fallback or persistent cache. |
|
|
19
|
+
| `bind(shape, name, properties)` | Queues explicit token-property bindings for `flush`; properties are token binding keys, not arbitrary JavaScript shape fields. |
|
|
20
|
+
| `box(parent, options)` | Makes a flex board; `gap` and four-sided `pad` accept spacing token names or unbound zero. `w`/`h` are explicit geometry. `bg`/`stroke` are color tokens and `radius` is a radius token. |
|
|
21
|
+
| `text(parent, value, options)` | Requires nonempty characters, `typo` and `color` tokens; preflights an exact font family/weight/style and applies the face before bindings. Use `wrap`, `fill`, `grow`, `align` and explicit `font` where needed. |
|
|
22
|
+
| `icon(parent, kind, color, size)` | Square wrapper around a uniformly scaled path on a 24-unit drawing grid. Built-ins: chevron, check, alert, calendar. A custom glyph supplies `path` and `[x,y,width,height]` bounds. |
|
|
23
|
+
| `dot`, `rule` | Small geometric helpers with explicit color tokens. Intrinsic one-pixel rules/strokes are geometry, not inferred library measurements. |
|
|
24
|
+
| `inst(parent, path, name, textOverrides, library?)` | Exact unique component match; validates named text overrides and never removes/reorders instance children. Defaults to the local library. |
|
|
25
|
+
| `style(shape, options)` | Queues requested styling only; an isolated color update preserves existing spacing and bindings. |
|
|
26
|
+
| `fill`, `fillV` | Sets layout-child horizontal/vertical fill sizing. |
|
|
27
|
+
| `settle(root, options)` | Native completion where available, otherwise bounded equal geometry. Time/node/malformed evidence cannot report stable. |
|
|
28
|
+
| `flush({root, timeoutMs})` | Waits for layout, applies queued values/styles, then awaits queued bindings, then settles again. Active token-context changes refuse. |
|
|
29
|
+
|
|
30
|
+
Always `await pp.flush()` before independent readback/export. There are no fixed 1.2/2.5-second styling sleeps. Layout completion does not prove font download, token persistence or pixels; keep positive readback and stable exports. Queues are local to one execution, not a recovery log. Partial errors are not transactions; the enclosing reviewed operation must own and reconcile its roots. Bindings remain subject to the runtime's supported token properties; A4 owns the broader compatibility whitelist.
|
|
31
|
+
|
|
32
|
+
Example body (inside a reviewed `run-file` operation):
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const card = pp.recipe("hello-card", ownedRoot, {
|
|
36
|
+
box: { name: "Hello card", w: 240, gap: CFG.space, pad: [CFG.space, CFG.space, CFG.space, CFG.space], stroke: CFG.color, radius: CFG.radius },
|
|
37
|
+
text: { typo: CFG.typo, color: CFG.color },
|
|
38
|
+
label: "Hello, card",
|
|
39
|
+
});
|
|
40
|
+
await pp.flush({ root: ownedRoot });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A recipe is registered with `pp.defineRecipe(name, (parent, props) => shape, description)` and invoked through `pp.recipe`. Recipe source must remain synchronous at construction time; asynchronous binding/layout work finishes in `flush`. See the [complete repeatable example](../examples/hello-card/README.md).
|
|
44
|
+
|
|
45
|
+
## Adapter recipes
|
|
46
|
+
|
|
47
|
+
`@marwes-kit`, included after `@kit`, defines `mw.card`, `mw.segmented`, `mw.progress`, `mw.iconTile` and `mw.statTile`, and registers corresponding recipe names. Library token defaults live entirely in the adapter; every token can be retargeted through props. Card/progress/tile radius tokens are explicit project declarations or measured inputs. Progress additionally requires an explicit **sizing** height token, since spacing tokens do not bind height. Tile dimensions and content are explicit.
|
|
48
|
+
|
|
49
|
+
These are compositional starting points. They are not generated native component variants or measured parity claims; C1 owns that calibration. Missing library defaults fail visibly rather than producing silently unbound styling.
|
|
50
|
+
|
|
51
|
+
## Opt-in scratch probe
|
|
52
|
+
|
|
53
|
+
`penpair probe --deep --kit --write` adds a journaled kit trial to the ordinary deep probe. Use an owner-authorized scratch file with active color, typography, spacing and radius tokens and the typography's font available. The trial builds a small “Hello, card” under a directly owned temporary root, observes text/bindings/geometry, exports matching renders, removes only that root, and independently compares the original page/token state. Unsupported context refuses; failed export still attempts owned cleanup. Explicit resume is allowed only against the original source and clean/reconciled state.
|
|
54
|
+
|
|
55
|
+
A verified trial saves a private PNG preview under `.penpair/probes/`, with byte hash and capture identity. Look at the preview as the write protocol requires. `visualApproval: false` remains explicit. Redacted/incomplete evidence cannot publish a preview; an existing differing preview is preserved. No new live pinned-version trial has been performed in this branch.
|
|
56
|
+
|
|
57
|
+
Current references: [flex layout](https://doc.plugins.penpot.app/interfaces/FlexLayout), [font application](https://doc.plugins.penpot.app/interfaces/Font), [component instantiation](https://doc.plugins.penpot.app/interfaces/LibraryComponent), [spacing properties](https://doc.plugins.penpot.app/types/TokenSpacingProps) and [sizing properties](https://doc.plugins.penpot.app/types/TokenSizingProps). Runtime capability and independent evidence remain required.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Command results for scripts and tools
|
|
2
|
+
|
|
3
|
+
`--json` emits one schema-versioned stdout document for core commands and the bundled adapter. Human output remains available without it. This replaces earlier command-specific JSON roots: read `data.ok` and `data.checks` for doctor, and `data.valid` for config check. CLI progress and diagnostics go to stderr; diagnostics are also included in the result.
|
|
4
|
+
|
|
5
|
+
The outer schema is exported as `commandResultSchema`, with the TypeScript `CommandResult` type. It contains `schemaVersion: 1`, command name, outcome, phase, exit code, observed target IDs when available, run ID when available, command-owned `data`, human messages, warnings, structured errors with remedies, completeness and timings. An absent target is `null`, never inferred from a file name. Do not decide success from empty errors alone: check outcome, exitCode, completeness and the command's verification fields.
|
|
6
|
+
|
|
7
|
+
| Exit | Outcome | Meaning |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 0 | success | Command completed; an inspect/preview still does not mean it applied or visually approved a change. |
|
|
10
|
+
| 1 | failed | Validation, connection, verification or command failure. Error codes distinguish configuration and transport problems. |
|
|
11
|
+
| 2 | drift | A valid comparison found differences or a stale snapshot. |
|
|
12
|
+
| 3 | incomplete | Required output is missing, truncated or unstable. |
|
|
13
|
+
| 4 | uncertain | A possible write lacks a confirmed outcome. Inspect the run before resuming. |
|
|
14
|
+
| 124 | deadline | The deadline interrupted work without a delivered write being classified uncertain. |
|
|
15
|
+
| 130 | cancelled | Cancellation interrupted work without a delivered write being classified uncertain. |
|
|
16
|
+
|
|
17
|
+
Tree JSON retains its truncation flag, and truncated tree output exits 3 in both formats. Baselines cannot pass with missing/unstable captures. A standalone unstable PNG export preserves its previous output and exits 3. Adapter status marks unavailable Penpot inspection incomplete instead of reporting a complete successful status.
|
|
18
|
+
|
|
19
|
+
Use `--timeout-ms <1..1800000>` for an overall CLI deadline. Each MCP request also retains a 120-second SDK timeout. SIGINT/SIGTERM abort requests and waits; SDK cancellation does not prove that plugin execution stopped. An interruption after a write was delivered therefore remains uncertain (4), with journal evidence retained. There is no forced rollback or detached background continuation. Synchronous extension code must cooperate; a JavaScript deadline cannot preempt a blocking synchronous module. Interactive setup/connect require a terminal, refuse JSON mode, and do not accept this deadline flag. Run doctor in JSON mode afterwards.
|
|
20
|
+
|
|
21
|
+
Library callers can use `runCommand(args, { signal })` to receive the same result without printing it; it enables command JSON data internally. The stdio MCP server uses this service boundary. Supply `runCommand(args, {root, signal})` for request-scoped projects; the process working directory remains unchanged. CLI calls default to the current project. Connection-specific redactors sanitize data, errors and diagnostic boundaries, including known bare key values. Never provide keys in arguments.
|
|
22
|
+
|
|
23
|
+
Adapters should write through `context.output.log/error/result` and reserve `context.log` for progress. `result(value)` sets their authoritative structured data. Existing third-party adapters that write directly to global console/process streams must migrate to this output contract; arbitrary imported code is not sandboxed or intercepted. Unsupported or ignored command flags are refused before execution, including write flags on read commands and invalid numeric budgets.
|
|
24
|
+
|
|
25
|
+
MCP transport accepts an executor envelope `{ result, log? }` in `structuredContent`, or the equivalent JSON text envelope for older servers. Structured content takes precedence and must validate; malformed structured data cannot fall back to a reassuring legacy text response. The SDK continues to handle its advertised schema checks. This is executor-envelope validation, not proof that an arbitrary snippet measured the state correctly. See the [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
|
|
26
|
+
|
|
27
|
+
Explicit mode trials may use a separate cooperative ten-second restoration deadline after main-command cancellation. The command remains cancelled/uncertain where applicable; cleanup does not establish a successful measurement. See [mode verification](mode-verification.md).
|