@penpair/web 0.3.7 → 0.4.1

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/docs/adapters.md CHANGED
@@ -27,7 +27,7 @@ Use it when your application uses Marwes UI. Follow the [adapter overview](marwe
27
27
  5. Run a project provider to open that state, read it back, capture the browser and compare the image.
28
28
  6. Review the screenshots and differences; record human approval separately.
29
29
 
30
- The adapter checks that the returned evidence identifies the requested case and expected state, references the correct unchanged baseline, and has matching PNG dimensions/scale and font evidence. A complete result within the configured difference threshold is technical evidence, not visual approval or an accessibility assessment.
30
+ The adapter checks that the returned evidence identifies the requested case and expected state, references the correct unchanged baseline, and has matching image dimensions/scale (PNG or static lossless WebP) and font evidence. A complete result within the configured difference threshold is technical evidence, not visual approval or an accessibility assessment.
31
31
 
32
32
  The provider is project code with an `async runCase(test)` export. It supplies actual route/theme/state/fixture setup, browser readback, screenshot capture and image comparison through the project's pinned tools. It can integrate the project's existing Reflection/browser checker. The adapter itself does not install a browser or execute a particular Reflection engine automatically.
33
33
 
package/docs/approvals.md CHANGED
@@ -8,7 +8,7 @@ penpair approve --page Dashboard --frame FRAME_ID --write \
8
8
  # Include --case CONFIGURED_BASELINE_ID for a rich theme/state/fixture case.
9
9
  ```
10
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.
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, encoded-image SHA-256, date, identity, exact note and record hash. The immutable image lives under `design/approvals/SHA256.webp` or the preserved PNG form `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
12
 
13
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
14
 
@@ -16,4 +16,35 @@ The command records supplied words; it cannot authenticate a person or establish
16
16
 
17
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
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.
19
+ The latest revision is a conservative invalidation boundary: an unrelated design edit may require fresh approval even if pixels stay equal. An image 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.
20
+
21
+ ## Image format and migration
22
+
23
+ New image approvals default to static lossless WebP. `approve --format png` or
24
+ `approve --format webp` selects an exact encoding. For `approve --case CASE_ID`
25
+ without explicit `--format`, the baseline resolver preserves an existing
26
+ reference's actual format; otherwise it uses `baselines.format`, then WebP.
27
+ An approval without `--case` uses explicit `--format` or WebP. Review the actual
28
+ encoded candidate before supplying owner words; equal decoded pixels do not make
29
+ PNG and WebP byte approvals interchangeable.
30
+
31
+ Changing `baselines.format` or supplying `baselines --format webp` alone does not
32
+ convert an existing PNG reference. Preview migration explicitly:
33
+
34
+ ```sh
35
+ penpair baselines --format webp --migrate-format
36
+ penpair approve --page Dashboard --frame FRAME_ID --case CASE_ID --format webp
37
+ ```
38
+
39
+ After inspecting the new-format artifact and obtaining the owner's actual words,
40
+ record it using the same approval command with `--write --by OWNER --note
41
+ EXACT_OWNER_WORDS`. Then apply the migration:
42
+
43
+ ```sh
44
+ penpair baselines --format webp --migrate-format --apply --update
45
+ ```
46
+
47
+ The CLI requires `--format` with `--migrate-format`; applying requires `--update`.
48
+ Normal exact-image/case approval rules still apply. Prior PNG approval records
49
+ and generations stay intact; migration publishes a new immutable generation.
50
+ Public `export` and `shots` output remain PNG and are independent of this choice.
package/docs/artifacts.md CHANGED
@@ -1,13 +1,33 @@
1
1
  # Capture and approval provenance
2
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.
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, encoded-image 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
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.
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`. Exact owner words are retained separately in [approval records](approvals.md); manifest provenance alone cannot manufacture consent.
6
6
 
7
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
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.
9
+ Baseline captures live in immutable `captures/<capture-id>/<baseline-id>.webp` or `.png` generations. New references default to static lossless WebP; existing PNG references keep PNG during ordinary captures and updates. Historical `<baseline-id>.png` files are not renamed or converted implicitly. 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
10
 
11
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
12
 
13
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.
14
+
15
+ ## Baseline encoding and explicit migration
16
+
17
+ Always follow the manifest's exact `file` path and encoded-byte hash; do not
18
+ construct a `.png` or `.webp` path from the baseline ID. PNG and WebP with identical
19
+ pixels have different approval identities. An existing reference keeps its
20
+ actual encoding even when `baselines.format` or `--format` selects another
21
+ format. New references choose explicit `--format`, then config, then WebP.
22
+
23
+ An explicit `--format webp --migrate-format` previews migration. Publishing it
24
+ also requires `--apply --update` and the normal matching exact new-format approval
25
+ (or explicit waiver rules). Earlier immutable generations and owner records
26
+ remain unchanged. See [approval migration](approvals.md#image-format-and-migration).
27
+
28
+ WebP conversion is lossless and independently verifies decoded RGBA bytes,
29
+ dimensions and ICC preservation. Animation, unsupported bit depth, excessive
30
+ size or pixel/metadata mismatch refuses conversion and preserves the previous
31
+ manifest. Public PNG exports, shot generations and pinned release-source PNG
32
+ renders retain their existing format; baseline defaults do not redefine those
33
+ artifacts.
@@ -0,0 +1,147 @@
1
+ # Recovering a suspended Penpot tab
2
+
3
+ Penpair executes live design operations through the Penpot browser plugin. A
4
+ reachable Penpot server does not establish that its editor tab is responsive.
5
+ Browser timer throttling, freezing and discarding are separate from computer
6
+ sleep; keeping the computer awake does not prevent them.
7
+
8
+ ## Wake an existing tab
9
+
10
+ From the configured project, with an owner-confirmed file pin and an existing
11
+ MCP connection:
12
+
13
+ ```sh
14
+ penpair wake --debug-port 9222
15
+ penpair wake --debug-port 9222 --timeout-ms 10000 --json
16
+ ```
17
+
18
+ The browser must already expose a debugging endpoint on `127.0.0.1` at the
19
+ specified port. Penpair does not start a browser or enable debugging in an
20
+ existing browser. `--timeout-ms` accepts 1000–30000; the default 5000 ms budget
21
+ covers activation and verification together.
22
+
23
+ Penpair lists the browser's targets, requires exactly one editor page matching
24
+ both the project's Penpot origin and pinned `file-id`, then requests activation
25
+ through `/json/activate/<target-id>`. It refuses duplicate matching tabs rather
26
+ than choosing one. Hosted deployments require an exact origin. For a local
27
+ instance, both `http://127.0.0.1:<frontend-port>` and
28
+ `http://localhost:<frontend-port>` are accepted, matching Penpair's existing
29
+ opening commands. Other hosts and ports are refused; duplicate matching tabs
30
+ across the two aliases still fail.
31
+
32
+ After activation, Penpair performs a fresh read with a challenge and checks the
33
+ actual file ID. Exit 0 means activation was acknowledged and that read verified
34
+ the intended editor. Activation can succeed while readiness fails: JSON then
35
+ reports `activated: true, ready: false`. A browser may also leave a minimized
36
+ window minimized; check the actual window when activation alone is insufficient.
37
+
38
+ This command does not reload, navigate, restart the plugin, inject page code or
39
+ resume a write. Its `keepAlive: "none"` result means activation is momentary
40
+ recovery, with no sustained keep-alive mechanism. Browser focus can affect legacy
41
+ MCP routing, which is why a fresh identity check follows activation. A target can
42
+ navigate between discovery and activation; the acknowledgment is not an atomic
43
+ guarantee of tab identity or exclusive ownership.
44
+
45
+ If a write became uncertain, retain its journal, inspect the intended target
46
+ independently and reconcile it before explicitly resuming. Waking a tab does not
47
+ establish whether the previous write completed. See [recovery](recovery.md).
48
+
49
+ ## Understand doctor evidence
50
+
51
+ ```sh
52
+ penpair doctor --json
53
+ ```
54
+
55
+ Doctor stays read-only: it does not activate tabs or install a keep-alive.
56
+ The execution check exposes `evidence.pluginHealth`:
57
+
58
+ ```json
59
+ {
60
+ "state": "suspended",
61
+ "heartbeatAgeMs": 36000,
62
+ "heartbeatSource": "upstream-error",
63
+ "keepAlive": { "method": "unknown", "verified": false }
64
+ }
65
+ ```
66
+
67
+ | Field | Meaning |
68
+ | --- | --- |
69
+ | `state` | `responsive` after a verified fresh read; `suspended` when the upstream failure explicitly reports suspension; otherwise `unknown`. |
70
+ | `heartbeatAgeMs` | Age reported in an upstream error such as `no heartbeat for 36s`; `null` when unavailable. |
71
+ | `heartbeatSource` | `upstream-error` when an age was parsed, otherwise `unavailable`. |
72
+ | `keepAlive` | The current connection does not expose independently verified browser keep-alive telemetry, so its method remains `unknown` and `verified` remains `false`. |
73
+
74
+ The check's `observedAt` records when Penpair observed this evidence. A successful
75
+ read does not imply a zero-second heartbeat age or prove that the next background
76
+ request will succeed. A generic timeout does not establish suspension.
77
+
78
+ ## Browser settings and a dedicated window
79
+
80
+ Chrome's **Settings → Performance → Always keep these sites active** exclusion
81
+ can prevent Memory Saver from deactivating the configured Penpot site. It does
82
+ not establish that background timers or Penpot layout will run without delay.
83
+ See [Chrome's performance settings](https://support.google.com/chrome/answer/12929150?hl=en).
84
+
85
+ For an explicit experiment, start a separate Chrome instance with its own profile
86
+ outside the repository. This macOS example uses an installed Google Chrome:
87
+
88
+ ```sh
89
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
90
+ --user-data-dir="$HOME/Library/Application Support/Penpair/browser-profile" \
91
+ --remote-debugging-address=127.0.0.1 \
92
+ --remote-debugging-port=9222 \
93
+ --disable-background-timer-throttling \
94
+ --disable-renderer-backgrounding \
95
+ --disable-backgrounding-occluded-windows \
96
+ --new-window https://design.penpot.app
97
+ ```
98
+
99
+ Use the project's configured origin for a different deployment. These launch
100
+ switches do not retrofit an already running ordinary browser instance. Since
101
+ Chrome 136, remote debugging requires a non-default user-data directory. Keep
102
+ that profile private: it can retain account cookies and cached designs. Keep the
103
+ debugging port on loopback and do not expose it on a network. See
104
+ [Chrome's debugging changes](https://developer.chrome.com/blog/remote-debugging-port/).
105
+
106
+ Login and plugin connection in that dedicated profile remain human setup steps;
107
+ Penpair does not copy credentials or complete them automatically. The flags above
108
+ are an experimental configuration, not a verified guarantee of background
109
+ Penpot operation. No managed-browser lifecycle, browser dependency or browser
110
+ download is added by the wake command.
111
+
112
+ A silent audio track does **not** count for Chrome's audio timer-throttling
113
+ exemption. Web Workers, SharedWorkers and Web Locks have different lifecycle
114
+ behavior, but are not verified here as a complete solution for the Penpot plugin.
115
+ Penpair does not inject these workarounds. See
116
+ [Chrome timer throttling](https://developer.chrome.com/blog/timer-throttling-in-chrome-88)
117
+ and [the Page Lifecycle API](https://developer.chrome.com/docs/web-platform/page-lifecycle-api).
118
+
119
+ ## Verification status and the remaining acceptance trial
120
+
121
+ The initial manual trial raised an existing Penpot window through native browser
122
+ controls without reloading it. Port 9222 was unavailable, and the chat's MCP
123
+ connection reported no connected Penpot instance. That trial therefore did not
124
+ prove restored plugin heartbeats. A separate real CDP trial used the installed
125
+ Brave browser with a temporary unauthenticated profile and the three background
126
+ flags above. The wake implementation changed a disposable local fixture page
127
+ from hidden to visible. No Penpot account or design was loaded, so this proves
128
+ browser activation only. Automated tests also cover target selection, activation
129
+ requests, deadlines and refusal paths using fake and local HTTP endpoints.
130
+
131
+ The required sustained-background acceptance trial remains **unverified**. Use an
132
+ owner-authorized disposable file and the intended installed browser build:
133
+
134
+ 1. Record the browser version, launch flags, profile, settings and initial doctor
135
+ evidence. Verify the file pin and establish a normal responsive connection.
136
+ 2. Run 30 frames, one journaled write per frame, each lasting 20–60 seconds, with
137
+ the Penpot window behind another window. Do not manually focus or reload it.
138
+ 3. Independently read back the results, export and visually inspect them. Record
139
+ heartbeat failures, layout timeouts and any required intervention.
140
+ 4. Test minimized, occluded and screen-locked states separately; success in one
141
+ state does not establish the others. Computer sleep is a separate boundary.
142
+ 5. If any write becomes uncertain, stop and reconcile before replay. Do not
143
+ increase retries or weaken journal, identity or layout checks to pass.
144
+
145
+ One successful activation is not a passing 30-frame run. The dedicated-browser
146
+ configuration and any future plugin keep-alive require that real acceptance
147
+ trial before they can be advertised as reliable background execution.
@@ -12,9 +12,11 @@ What has been checked, where, and what differs. Update it whenever a Penpot is p
12
12
  | Deep check (`probe --deep --write`): flex layout, text, token set and binding, components and swap, clone, PNG export, clean-up | all steps pass, nothing left behind (run on a fresh instance's empty file) | all steps pass, nothing left behind (run twice) |
13
13
  | `pages`, `tree`, `audit`, `tokens dump`, `export` (waits for a stable render), `archive` with `--apply`, a project snippet that writes (`run seed-demo`) | all in use | all verified on seeded sample content |
14
14
  | `doctor` (external profile) | ok | ok |
15
+ | Cross-page clone followed by `appendChild` / `insertChild` | refused with either page active; scratch cleanup verified ([details](copy-between-pages.md)) | not tried |
15
16
  | `init`, `up`, `doctor` for a Penpot penpair starts (a fresh instance on its own port, then the owner's account, key and plugin connection) | ok: 16 seconds to answer with the images cached; `doctor` all ok | not applicable |
16
17
 
17
18
  ## Differences found
19
+ - **Cross-page reparenting is refused on local 2.18.3.** Clones stay on the source page. Use native editor copy/paste; see [copying between pages](copy-between-pages.md).
18
20
  - **A new token set starts inactive, with no themes in the file.** Seen on both the hosted Penpot and a fresh local instance. (An earlier note that local sets start active was wrong or applied to a file with themes.)
19
21
  - **A token binding is recorded after the call, not during it.** On the hosted Penpot it appeared after about half a second to a few seconds; reading it straight away shows nothing. Code that checks a binding has to wait for it.
20
22
  - **Applying the same token again removes the binding** (seen on the hosted Penpot, as in the local notes).
@@ -42,11 +44,11 @@ The `Scratch Penpot compatibility smoke` workflow is manual-dispatch only. Confi
42
44
 
43
45
  Library helpers accept the project's guarded client:
44
46
 
45
- - `waitForStableLayout(client, { shapeId, timeoutMs })`: prefers supported native completion; otherwise polls geometry. Native failure, target changes, incomplete samples and timeout cannot pass. Geometry stability does not establish token/font/pixel correctness; independent readback and stable exports remain necessary. Default operation/export and token verification paths now use this helper; see [layout settling](layout-settling.md). Live behavior on pinned versions remains unverified.
47
+ - `waitForStableLayout(client, { shapeId, timeoutMs })`: prefers supported native completion; otherwise polls geometry. In `auto` mode a failed native wait can fall back to geometry. Budget overflow reports explicitly partial evidence; target changes, malformed samples and timeout cannot pass. Geometry stability does not establish token/font/pixel correctness; independent readback and stable exports remain necessary. Default operation/export and token verification paths use this helper; see [layout settling](layout-settling.md).
46
48
  - `validateFileIntegrity(client)`: distinguishes unsupported from a valid empty issue list or actual referential-integrity problems.
47
49
  - `createCheckpoint(client, { label, runId }, { apply })`: optional native version history. Without apply it inspects; with apply it saves a run-keyed named version once and independently finds it again. Reusing that run ID reuses an existing checkpoint; it does not provide atomic cross-client locking. No automatic restore/rollback is performed.
48
50
 
49
- Current upstream references: [Penpot API](https://doc.plugins.penpot.app/interfaces/Penpot), [File API](https://doc.plugins.penpot.app/interfaces/File), [FileVersion API](https://doc.plugins.penpot.app/interfaces/FileVersion). Unit fixtures cover missing, present and throwing APIs. These helpers have not yet been exercised against the pinned 2.18.3/2.18.4 instances; the live compatibility table above is intentionally unchanged.
51
+ Current upstream references: [Penpot API](https://doc.plugins.penpot.app/interfaces/Penpot), [File API](https://doc.plugins.penpot.app/interfaces/File), [FileVersion API](https://doc.plugins.penpot.app/interfaces/FileVersion). Unit fixtures cover missing, present and throwing APIs. A read-only layout check on local 2.18.3 on 2026-10-25 observed two existing boards: three equal geometry samples covered 192 nodes; a five-node budget returned `method: "partial"`, `nodeCount: 5`, `truncated: true` and both requested root IDs. This does not prove a live write on a page over 10,000 nodes, native wait timing, integrity validation or version-history behavior. Those remain unverified.
50
52
 
51
53
  ## Hosted archive candidate
52
54
 
@@ -0,0 +1,54 @@
1
+ # Copy boards between pages
2
+
3
+ Penpair currently cannot copy or move an existing board between pages through
4
+ the Penpot plugin API. There is no `copy-boards` command. Use native copy/paste
5
+ in the Penpot editor, leaving the originals in place, then independently inspect
6
+ the destination and compare stable exports. Keep component references, token
7
+ bindings, sizes and child counts intact. See [design release assembly](design-releases.md)
8
+ for its additional fidelity requirements.
9
+
10
+ ## Tested API boundary
11
+
12
+ A disposable two-page probe on local Penpot 2.18.3 on 2026-10-25 tested a board
13
+ with one rectangle child. Each `clone()` stayed on the source page. Appending or
14
+ inserting the clone into a board on the other page failed with:
15
+
16
+ ```text
17
+ Cannot modify a page that is not currently active
18
+ ```
19
+
20
+ Both `appendChild(clone)` and `insertChild(0, clone)` failed with either the
21
+ source page or the destination page active. Independent readback found both
22
+ clones on the source page, no destination children, and the original board and
23
+ child unchanged. Two destination exports were inspected and showed the empty
24
+ board. Both disposable pages were removed; a separate read confirmed their
25
+ absence and the original pages' names and top-level shape IDs were preserved.
26
+ This establishes a local API limitation, not hosted-version or component/token
27
+ copy fidelity.
28
+
29
+ The [upstream shape implementation](https://github.com/penpot/penpot/blob/develop/frontend/src/app/plugins/shape.cljs)
30
+ checks that both parent and child pages are active before either structural
31
+ operation. The [Board API](https://doc.plugins.penpot.app/interfaces/Board)
32
+ provides cloning and reparenting, but no cross-page transfer operation. The
33
+ [File API](https://doc.plugins.penpot.app/interfaces/File) exposes export,
34
+ validation and history, without a file-change submission method.
35
+
36
+ ## File-change route
37
+
38
+ Penpair's connection calls the editor's MCP tools; it has no authenticated
39
+ Penpot backend file-change client or supported plugin method for submitting raw
40
+ file changes. Internal `$page` metadata does not provide that capability.
41
+ Changing a proxy's internals, reconstructing a board from its visible properties,
42
+ or patching backend file objects would not establish preserved component
43
+ overrides, token bindings, references, undo behavior or revision coordination.
44
+
45
+ There is therefore no safe file-change route in the current implementation.
46
+ Automatic cross-page copying stops at this boundary. A future supported transfer
47
+ API or separately validated native editor integration would need exact source
48
+ selection, a dry run, journaled recovery, independent fidelity readback and stable
49
+ exports before shipping a copy command. Moving must remain explicit.
50
+
51
+ For large pages, scope a writer's layout observation with repeatable
52
+ `--layout-root <shape-id>` and, when needed, `--layout-max-nodes <n>`; see
53
+ [layout settling](layout-settling.md). Reducing the observation scope does not
54
+ add a cross-page copy capability.
package/docs/coverage.md CHANGED
@@ -20,9 +20,9 @@ A baseline frame may declare an explicit viewport, theme label, UI state, fixtur
20
20
 
21
21
  `theme`, `state` and `fixture` are declared design intent. A label alone does not prove actual theme activation, application state or data loading. Optional `activeThemeIds` asserts the complete exact active native theme-ID set at capture; omit it for a manually styled frame and retain that uncertainty. Baseline capture remains read-only toward the Penpot document: it never switches themes. Use the separate [mode trial](mode-verification.md) for temporary theme switching.
22
22
 
23
- Frame geometry must match the declared CSS viewport within less than 0.01 CSS px per axis, allowing floating-point noise from Penpot transforms and auto sizing. Baseline capture, case approval and offline coverage use the same tolerance. Exported PNG dimensions must still exactly equal the declared viewport multiplied by the observed export scale and rounded to whole pixels; the geometry tolerance never applies to PNG dimensions. A failed viewport/theme assertion marks `case-mismatch`, exits incomplete and preserves the previous generation. New provenance retains raw, unrounded design dimensions, active native theme IDs where available, API/tool version, renderer description and capture time. Font names are declarations; loaded font versions remain unobserved.
23
+ Frame geometry must match the declared CSS viewport within less than 0.01 CSS px per axis, allowing floating-point noise from Penpot transforms and auto sizing. Baseline capture, case approval and offline coverage use the same tolerance. Encoded image dimensions (PNG or static lossless WebP) must still exactly equal the declared viewport multiplied by the observed export scale and rounded to whole pixels; the geometry tolerance never applies to image dimensions. A failed viewport/theme assertion marks `case-mismatch`, exits incomplete and preserves the previous generation. New provenance retains raw, unrounded design dimensions, active native theme IDs where available, API/tool version, renderer description and capture time. Font names are declarations; loaded font versions remain unobserved.
24
24
 
25
- Case metadata is part of approval identity. Changing a fixture/state/theme/viewport declaration requires `--apply --update` even if the PNG bytes happen to match. Moving a matching image to a different file/page/frame identity also requires review. Recapture preserves the original approval until that explicit update. Metadata is pinned before asynchronous capture.
25
+ Case metadata is part of approval identity. Changing a fixture/state/theme/viewport declaration requires `--apply --update` even if the image bytes happen to match. Moving a matching image to a different file/page/frame identity also requires review. Recapture preserves the original approval until that explicit update. Metadata is pinned before asynchronous capture.
26
26
 
27
27
  ## Required matrix
28
28
 
@@ -30,7 +30,7 @@ Add `baselines.coverage.required` as an explicit list of route/viewport/theme/st
30
30
 
31
31
  See [an example matrix](../examples/coverage-matrix.json) spanning desktop/mobile, light/dark and an empty state. Only request combinations the product intends to support; no matrix is inferred from frame sizes or names.
32
32
 
33
- `penpair baselines coverage --json` runs offline. It loads the version-2 manifest, checks the configured file pin, validates project-bound artifact paths, independently hashes approved PNG bytes and reports:
33
+ `penpair baselines coverage --json` runs offline. It loads the version-2 manifest, checks the configured file pin, validates project-bound artifact paths, independently hashes approved PNG or static lossless WebP bytes and reports:
34
34
 
35
35
  - `missing-case`: no configured frame for the required tuple.
36
36
  - `missing-capture`: a frame is configured but has no approved capture.
@@ -41,7 +41,7 @@ See [an example matrix](../examples/coverage-matrix.json) spanning desktop/mobil
41
41
 
42
42
  `designCasesComplete` means all requested design slots have matching approved intent. `browserVerification: not-run` and `accessibility: not-assessed` remain separate, even when every design slot is complete. Unknown font loading, absent activation assertions and missing renderer metadata are listed. The offline report exits 3 for gaps; it never connects to Penpot or loads project extensions.
43
43
 
44
- Ordinary `baselines` can publish a coherent capture of its configured frames while the broader required matrix still has gaps; that command returns incomplete when configured requirements remain uncovered. The manifest distinguishes capture completeness from matrix coverage. An incomplete capture itself still publishes nothing. Manifest reads are bounded to 16 MiB and each approved PNG check to 64 MiB; exceeding those bounds cannot produce covered evidence.
44
+ Ordinary `baselines` can publish a coherent capture of its configured frames while the broader required matrix still has gaps; that command returns incomplete when configured requirements remain uncovered. The manifest distinguishes capture completeness from matrix coverage. An incomplete capture itself still publishes nothing. Manifest reads are bounded to 16 MiB and each approved image check to 64 MiB; exceeding those bounds cannot produce covered evidence.
45
45
 
46
46
  ## Browser integration
47
47
 
@@ -62,3 +62,19 @@ Baseline entries require `frame` or `frameId`, or both. `frameId` is a unique bo
62
62
  When both are supplied, a different current board name reports `renamed` and the entry is never captured under its stale name. Review the rename and update the expected name before retrying. ID-only entries follow the ID and report its current name. Page searches stop with an explicit error if the 10,000-shape budget truncates the listing.
63
63
 
64
64
  `approve --case CASE_ID --page PAGE --frame ID` compares `--frame` directly with a configured `frameId`; name-only cases retain the unique-name check. Nested frames use the same exact frame-ID capture identity for approval matching. The baseline manifest continues to record `frameId`; Reflection case format is unchanged.
65
+
66
+ ## Image encoding
67
+
68
+ New baseline references default to static lossless WebP. Existing PNG references
69
+ and approvals retain their exact original bytes, paths and hashes during normal
70
+ capture/update. Neither `baselines.format` nor `--format` alone migrates an
71
+ existing reference. Use explicit `--format webp --migrate-format` for a preview;
72
+ application also requires `--apply --update` and matching exact new-format image
73
+ approval. A PNG approval does not cover equivalent WebP pixels. See
74
+ [approval and migration](approvals.md#image-format-and-migration).
75
+
76
+ WebP conversion preserves exact dimensions, decoded RGBA pixels including RGB
77
+ under transparency, and the source ICC profile. Unsupported animated/16-bit
78
+ input or failed conversion verification leaves the prior generation intact.
79
+ The conversion budget is 64 megapixels; geometry tolerance never relaxes image
80
+ pixel-size or pixel-identity checks.
@@ -0,0 +1,164 @@
1
+ # Design releases
2
+
3
+ A design release freezes a reviewed design handoff for an explicit product SemVer,
4
+ independent of the Penpair package version. It joins
5
+ [journey maps](journey-maps.md), [integration maps](integration-maps.md), exact
6
+ design cases and immutable evidence so a later implementation agent can identify
7
+ what was reviewed. This is a design release, separate from publishing Penpair's
8
+ npm packages or accepting a frontend implementation.
9
+
10
+ ## Local commands
11
+
12
+ Create the plan from actual source inspection, using your own product SemVer.
13
+ These paths illustrate project-owned inputs. A release accepts at most 100
14
+ selected source screens. Use the local project command, such as
15
+ `pnpm exec penpair`, when installed as a dependency.
16
+
17
+ ```sh
18
+ # Save a planned release, then inspect the remote assembly preview.
19
+ penpair design-release plan --from design/releases/1.0.0/plan.json \
20
+ --out design/releases/1.0.0/release.json
21
+ penpair design-release assemble --from design/releases/1.0.0/release.json
22
+
23
+ # After reviewing the preview and authorizing the assembly write:
24
+ penpair design-release assemble --from design/releases/1.0.0/release.json --apply
25
+ ```
26
+
27
+ Next, perform the guided native copy and record actual source/target identities
28
+ in `receipts.json`. Verification reads the live frames and saves evidence;
29
+ finalization below is a preview and records no owner approval.
30
+
31
+ ```sh
32
+ penpair design-release verify --from design/releases/1.0.0/release.json \
33
+ --receipts design/releases/1.0.0/receipts.json
34
+ penpair design-release finalize --from design/releases/1.0.0/release.json
35
+ penpair design-release check --from design/releases/1.0.0/release.json --live
36
+ penpair design-release context --from design/releases/1.0.0/release.json --draft
37
+ ```
38
+
39
+ Only apply finalization after the owner supplies actual words for the exact
40
+ verified candidate, as described below. After finalization, omit `--draft` to
41
+ request approved context. Missing required proof keeps verification/finalization
42
+ incomplete; these commands do not bypass the native support limits.
43
+
44
+ ## Prepare and assemble
45
+
46
+ Use the `design-release` command family to plan, assemble, verify, finalize,
47
+ check and produce context. Use the installed command's help for current argument
48
+ names. Planning exposes required inputs and unresolved work. Assembly records
49
+ identities and hashes; it does not invent source observations or owner approval.
50
+ Each selected source needs its complete `inspection` reference (project-relative
51
+ `path` and exact `sha256`). Pin a stable source PNG through its `render` reference
52
+ before verification; a draft without this reference cannot pass fidelity. A
53
+ source using tokens also needs a complete pinned token-inspection `dependencies`
54
+ reference. Required dependency changes invalidate the pinned source evidence.
55
+
56
+ Include the parent and every linked detail-map contract in the release's
57
+ `contracts` list, each with its independent exact review. A reviewed parent link
58
+ is not approval of the child graph. See [linked detail maps](integration-maps.md#linked-detail-maps).
59
+
60
+ Keep the release page in the same pinned Penpot file, named `release/<version>`.
61
+ Assembly creates the owned overview/map sections and reports `awaiting-copy`.
62
+ Copy source design frames natively using a reviewed, guided copy/paste operation
63
+ by a person or an authorized AI using existing browser tools. Assembly does not
64
+ perform an unsupported cross-page clone.
65
+ The [cross-page API assessment](copy-between-pages.md) records the tested
66
+ boundary and why there is no automatic file-change fallback.
67
+ Preserve visual fidelity, native components, token bindings, text and layout;
68
+ do not recreate frames from screenshots or normalize their dimensions. A source
69
+ using auto height must retain it. Floating-point geometry noise is not a reason
70
+ to change source sizing.
71
+
72
+ The live sequence is read-only inspection, pinned snapshot, one idempotent write,
73
+ independent readback and stable exports that are looked at. Snippets stay under
74
+ 90 KB. If a write response is uncertain, inspect what exists before retrying.
75
+ Keep unmanaged content intact; a page name alone cannot authorize replacement.
76
+ Do not copy via a lossy or structurally incomplete fallback when native fidelity
77
+ cannot be established: report the missing proof.
78
+
79
+ ## Supported native verification limits
80
+
81
+ Native verification is deliberately narrower than native copy/paste. Selecting a
82
+ screen for this workflow does not establish that its content can be verified.
83
+ The current verifier blocks these cases:
84
+
85
+ - **All component instances, copies, main instances and component roots.** The
86
+ inspection cannot establish complete override state. Retaining links during a
87
+ copy is required, but it does not prove override fidelity.
88
+ - **Groups without an explicit `maskedGroup` observation.** An absent mask flag
89
+ is unknown, not proof that the group is unmasked.
90
+ - **Unknown text range/style observations**, including unobserved mixed-range
91
+ state, and any other unknown or missing required inspection field.
92
+ - **Paths/vector shapes whose actual path content was not observed.** A path
93
+ type or matching raster export cannot establish the native path data.
94
+ - Unsupported shape content, incomplete hierarchy, unobservable interactions or
95
+ missing pinned source render/dependency evidence.
96
+
97
+ A selected screen containing one of these cases remains blocked with a reason;
98
+ it cannot be finalized by waiving the finding or accepting equal pixels. Retain
99
+ its native structure and source evidence, then obtain the missing observation or
100
+ extend and validate inspection support before retrying. Do not detach components,
101
+ flatten groups, remove styles or substitute raster images to bypass the gate.
102
+ Simple screens whose required fields are fully observed can be assessed, but
103
+ that is not a claim of complete support for every native Penpot feature.
104
+
105
+ ## Verify the package
106
+
107
+ Supply a project-owned receipt list with explicit `sourceId` and
108
+ `targetFrameId` values to `verify --receipts FILE`. Check release frame identities
109
+ against the selected sources and retain immutable inspection/export evidence
110
+ with exact paths and hashes. Compare source and release frames independently,
111
+ including component/token identity and sizing
112
+ behavior, and inspect stable exports. A current rendering cannot rewrite the
113
+ bytes of an already recorded artifact. Native release objects remain mutable,
114
+ including changes inherited through shared components and tokens. A later change
115
+ is drift and needs a new reviewed generation, not a silent refresh of finalized
116
+ evidence.
117
+
118
+ New baseline evidence uses lossless WebP. Existing PNG baselines and image
119
+ approvals remain valid in their original format. Conversion must be a deliberate
120
+ migration with new evidence and review; never change approved bytes in place.
121
+ Penpot can supply PNG during export, so conversion must retain exact dimensions
122
+ and pixels. Quality 100 alone does not establish lossless encoding.
123
+
124
+ The release needs matching exact-artifact review records and no unresolved
125
+ blocking findings. Preserve the distinction between:
126
+
127
+ - Map review: the owner's supplied words bind an exact contract hash.
128
+ - Design image approval: the existing exact case/image approval protocol.
129
+ - Technical verification: independently observed structure and image evidence.
130
+ - Implementation acceptance: actual application behavior, browser evidence and
131
+ owner review, assessed separately after implementation.
132
+
133
+ ## Finalize and continue
134
+
135
+ The release record progresses from `planned` through `awaiting-copy` and
136
+ `verified` to `final`. Finalization records the reviewed release identity and
137
+ immutable evidence. It must not promote missing evidence, waivers or inferred owner approval into a
138
+ successful design gate. Finalized means the design package is ready to guide
139
+ implementation; it does not mean the application has been built or checked.
140
+
141
+ Finalization previews by default. Only use `finalize --apply --by OWNER --note
142
+ EXACT_WORDS` after the owner has supplied those words for the exact candidate.
143
+ Keep journal/run identity when reconciling interrupted remote work.
144
+
145
+ Run checks again before using the package later. Missing or modified artifacts,
146
+ changed map/source hashes and changed release evidence must remain visible as
147
+ drift or incomplete evidence. A saved-source check does not contact Penpot;
148
+ live freshness requires an explicit fresh observation. Context must identify
149
+ its verification scope and any truncation. Release context includes actual frozen
150
+ `mapContexts`, exact release-page `mapTargets`, independently pending implementation
151
+ status and next actions. Linked detail navigation targets the release's own child
152
+ boards; each child contract retains its separate review requirement.
153
+ Its `proposedBaselines` is a configuration fragment with the release page, WebP
154
+ format, baseline root and explicit target `frameId` entries. Review and merge it
155
+ into the existing `baselines` block; context does not rewrite project config or
156
+ approve the new target image identities.
157
+
158
+ Use `penpair-handoff` with the approved integration context, then `penpair-verify`
159
+ for the actual frontend. Keep those later implementation results separately;
160
+ never rewrite the finalized design to match an implementation failure.
161
+
162
+ The [reset-password example](../examples/design-release-maps/README.md) is an
163
+ unapproved fixture showing decisions and gaps. It is not a finalized release or
164
+ proof of a live Penpot copy.
package/docs/guide.md CHANGED
@@ -10,6 +10,7 @@ consumer evidence.
10
10
  | Path | Documents |
11
11
  | --- | --- |
12
12
  | First run / shared file | [Choose an installation route](installation.md), [manual installation](getting-started.md), [team](team.md), [validation](validation.md) |
13
+ | Intent / release | [Journey maps](journey-maps.md), [integration maps](integration-maps.md), [design releases](design-releases.md) |
13
14
  | Build / handoff | [Cookbook](cookbook.md), [workflow](design-workflow.md), [kit](build-kit.md), [file scripts](run-file.md), [handoff](handoff.md) |
14
15
  | Evidence | [Inspection](inspection.md), [advisory audits](audits.md), [snapshots](snapshots.md), [approvals](approvals.md), [coverage](coverage.md), [artifacts](artifacts.md) |
15
16
  | Integration | [Companion adapters](adapters.md), [MCP](mcp.md), [formats](formats.md), [extension trust](extensions-trust.md), [credentials](credentials.md), [token adapter](marwes.md) |
@@ -95,6 +96,7 @@ For registered writes, supply an explicit `verify({ inspect, apply, readback })`
95
96
  "baselines": {
96
97
  "page": "Candidate",
97
98
  "root": "tests/baselines",
99
+ "format": "webp",
98
100
  "threshold": 0.05,
99
101
  "frames": [
100
102
  { "frame": "F / candidates", "id": "candidates-desktop", "route": "candidates" },
@@ -107,8 +109,23 @@ For registered writes, supply an explicit `verify({ inspect, apply, readback })`
107
109
  - Each entry requires `frame` or `frameId` (or both). `frameId` is a board UUID, unique across entries, resolved anywhere on the configured page. Frames inside boards, such as release frames inside section boards, need `frameId`. Name-only entries still match top-level frames only.
108
110
  - With both fields, `frame` is an expected name: a changed name reports `renamed` and captures nothing for that entry. Review and update the expected name before retrying. ID-only entries use the board's current name in reports. A truncated page search (10,000 shapes) stops the capture instead of using a partial listing.
109
111
  - For example: `{ "frameId": "10216397-acdf-805e-8008-d69b71f841d4", "frame": "Login / Empty", "id": "login-empty", "route": "login" }`.
110
- - `id` names the image (`<id>.png`) and the check. `route` is the route in your app this frame is the design of; a frame without a route is exported but gets no check.
111
- - Without `--apply` the command only looks and reports each frame as `new`, `same`, `changed`, `unstable` (the export never settled) or `missing`/`renamed`. `--apply` writes new images, `penpair-baselines.json` (file, revision, sizes, hashes) and `reflection-cases.json`. A `changed` image is replaced only with `--apply --update`: that is the act of approving the new design, and the owner's call. The exit code is 2 when a design differs from its image, 3 when a frame is missing, renamed or unstable.
112
+ - `id` names the image (`<id>.webp` for a new default reference; existing PNG references retain `.png`) and the check. `route` is the route in your app this frame is the design of; a frame without a route is exported but gets no check.
113
+ - Without `--apply` the command only looks and reports each frame as `new`, `same`, `changed`, `unstable` (the export never settled) or `missing`/`renamed`. `--apply` writes new images, `penpair-baselines.json` (file, revision, sizes, hashes) and `reflection-cases.json`. A `changed` image is replaced only with `--apply --update` and matching exact-artifact owner approval (or an explicitly recorded waiver); see [approval records](approvals.md). The exit code is 2 when a design differs from its image, 3 when a frame is missing, renamed or unstable.
114
+
115
+ New references default to static lossless WebP. `baselines.format` accepts
116
+ `"webp"` or `"png"`; a command's `--format` overrides that choice for new
117
+ references. Existing approved images keep their original encoding during ordinary
118
+ captures and updates, even when config or `--format` selects another format.
119
+ Approved byte hashes are never silently converted.
120
+
121
+ To preview an explicit migration, run `penpair baselines --format webp
122
+ --migrate-format`. After reviewing and recording matching new-format approval,
123
+ apply it with `penpair baselines --format webp --migrate-format --apply --update`.
124
+ `--migrate-format` requires explicit `--format`, and applying it requires
125
+ `--update`. Use `approve --format webp` (plus the intended `--case`, when present)
126
+ to review/record the same encoding; PNG approval cannot approve WebP bytes.
127
+ See [approval and migration details](approvals.md#image-format-and-migration).
128
+ Public `export` and `shots` remain PNG.
112
129
 
113
130
  The generated `reflection-cases.json` holds, per route, the viewports (the image's exact size) and the `visualSmoke` cases (review-only, `strict: false`). A Reflection config uses it like this; only the route's own expectations are written by hand:
114
131
 
@@ -169,6 +186,6 @@ Use `inspect <shape-id> --page <name> --all` for bounded structured evidence wit
169
186
 
170
187
  ## State, theme and responsive coverage
171
188
 
172
- Add explicit `frames[].case` metadata and a required matrix under `baselines.coverage`. Case geometry allows less than 0.01 CSS px of Penpot floating-point noise per axis; exported PNG dimensions remain exact at the capture scale, and raw geometry stays in provenance. `baselines coverage --json` validates approved images offline and exposes missing/unsupported slots. Rich cases remain pending browser setup mappings; see [coverage semantics and approval](coverage.md).
189
+ Add explicit `frames[].case` metadata and a required matrix under `baselines.coverage`. Case geometry allows less than 0.01 CSS px of Penpot floating-point noise per axis; encoded image dimensions (PNG or lossless WebP) remain exact at the capture scale, and raw geometry stays in provenance. `baselines coverage --json` validates approved images offline and exposes missing/unsupported slots. Rich cases remain pending browser setup mappings; see [coverage semantics and approval](coverage.md).
173
190
 
174
191
  For project-specific agent tools, use the [explicit-root stdio server and client examples](mcp.md). It exposes typed guarded services and defaults to omitting design-write tools.
package/docs/handoff.md CHANGED
@@ -29,6 +29,6 @@ An optional schema-1 component map names exact source identities:
29
29
 
30
30
  Names and equal pixels do not establish a mapping. There are no guessed library IDs in shipped maps: the adapter's native component catalogue awaits C1 proof. Static scalar props are owner-declared hints; text props resolve only explicit descendants of that instance. Missing text IDs stay unknown. No inferred click handlers, state transitions, keyboard behavior, accessibility roles, focus order or responsive layout claims are emitted. The current recorded state does not describe every breakpoint or theme.
31
31
 
32
- `spec check` regenerates from the supplied structured capture and map, detecting manual edits, source/revision changes and mapping changes (exit 2). It does not contact Penpot or establish current live freshness. A PNG export manifest alone cannot prove structural/text freshness; use a new structured inspection rather than treating an unchanged image as proof. Live human handoff/adoption acceptance remains pending under I12; native mapping generation remains gated under C1. JSON output uses the shared command envelope.
32
+ `spec check` regenerates from the supplied structured capture and map, detecting manual edits, source/revision changes and mapping changes (exit 2). It does not contact Penpot or establish current live freshness. An image export manifest alone cannot prove structural/text freshness; use a new structured inspection rather than treating an unchanged image as proof. Live human handoff/adoption acceptance remains pending under I12; native mapping generation remains gated under C1. JSON output uses the shared command envelope.
33
33
 
34
34
  Owner review can be retained as an [exact-artifact approval record](approvals.md). Changed baseline references require matching supplied owner words or an explicitly labeled waiver; a comparison metric does not supply approval.