@penpair/web 0.4.0 → 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.
@@ -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.
@@ -62,6 +62,8 @@ Assembly creates the owned overview/map sections and reports `awaiting-copy`.
62
62
  Copy source design frames natively using a reviewed, guided copy/paste operation
63
63
  by a person or an authorized AI using existing browser tools. Assembly does not
64
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.
65
67
  Preserve visual fidelity, native components, token bindings, text and layout;
66
68
  do not recreate frames from screenshots or normalize their dimensions. A source
67
69
  using auto height must retain it. Floating-point geometry noise is not a reason
@@ -1,6 +1,6 @@
1
1
  # Bounded layout settling
2
2
 
3
- Normal operation writes, PNG exports, baselines, shots and token push verification now use `waitForStableLayout`. A supported shape/global native wait is preferred. Older APIs fall back to geometry samples, requiring two consecutive equal observations of the target subtree. Missing targets, changed file/page identity, malformed geometry, more than 10,000 sampled nodes, native failure and expired budgets cannot establish stability.
3
+ Normal operation writes, PNG exports, baselines, shots and token push verification now use `waitForStableLayout`. A supported shape/global native wait is preferred. Older APIs fall back to geometry samples, requiring three consecutive equal observations of the target subtree. Missing targets, changed file/page identity, malformed sampled geometry and expired budgets cannot establish stability. Native failure falls back to geometry in `auto` mode; `native` mode still requires a successful native wait.
4
4
 
5
5
  The default layout budget is 30 seconds, with 200 ms fallback intervals. A run is also limited to 1,000 samples. Each layout observation forwards its remaining request budget through guards to the MCP SDK and disables suspension retries; a read timeout does not silently restart the budget. Custom clients must honor the optional `ToolCallOptions.timeoutMs` contract. A late native response is rejected even if it claims completion. The overall CLI `--timeout-ms` remains a separate bound on the complete command.
6
6
 
@@ -11,3 +11,19 @@ Exports retain the first capture as an initial observation, wait for layout, and
11
11
  The previous unconditional ten-second operation sleep is replaced by bounded evidence. Library callers can retain an explicitly measured `settleMs` minimum from 0 to 30 seconds for an additional asynchronous contract such as binding persistence. It is a delay, not verification. A missing binding still fails independent readback and may be reconciled through the journal once it arrives. Page switching retains its existing short navigation delay. No new live timing or compatibility claim is made.
12
12
 
13
13
  Library options may select a target and budget through `layout`, or explicitly set `layout: false` when the caller supplies its own settlement evidence. CLI design commands do not expose that opt-out. Multi-step mutations remain nontransactional: a post-write layout failure can leave completed work applied; inspect the run and explicitly resume after resolving it. The later build kit should reuse these primitives rather than introduce another fixed-sleep convention.
14
+
15
+ ## Scope and node budgets
16
+
17
+ `run-file`, `run`, `archive`, token edit writers, `export` and `shots` accept:
18
+
19
+ ```sh
20
+ penpair run-file design/build.js --inspect design/inspect.js --readback design/readback.js \
21
+ --page Candidate --layout-root BOARD_ID --layout-root OTHER_BOARD_ID \
22
+ --layout-max-nodes 20000 --apply
23
+ ```
24
+
25
+ Each repeatable `--layout-root` identifies an existing shape on the operation's page. Roots must be distinct, nonoverlapping subtrees. Missing roots refuse rather than falling back to the page. The same roots are observed before planning, after writing and during recovery. New shape IDs cannot be selected before creation; select their existing parent or use the page default. Without explicit roots the current page is sampled; exports retain their existing shape scope. Library callers use `layout.shapeIds` (or the existing singular `shapeId`) and `layout.maxNodes`.
26
+
27
+ `--layout-max-nodes` is an integer from 1 to 100,000, shared across the selected subtrees; the default remains 10,000. When the traversal exceeds it, the observer retains a bounded depth-first prefix. A successful native wait or three equal bounded samples can continue the operation with `method: "partial"` and `truncated: true`. This establishes stability only for the sample, not for unsampled shapes. Invalid geometry, cycles or duplicate IDs encountered in the sample still refuse. Use explicit roots for complete evidence on a large page. Partial evidence never replaces positive independent write readback or visual export inspection.
28
+
29
+ Reports and journal observations, visible through `penpair runs RUN_ID`, record `rootIds`, `nodeCount` (the sampled count), `maxNodes` and `truncated`. The sample count is a lower bound on subtree size when truncated. Journal input hashes retain the chosen scope and budget, so changing either requires a new run. The build kit's `pp.settle(root, {maxNodes})` shares this policy and evidence; it also accepts an array of root shapes.
package/docs/mcp.md CHANGED
@@ -55,3 +55,5 @@ args = ["mcp", "--root", "/absolute/path/to/project"]
55
55
  ```
56
56
 
57
57
  Restart/reconnect after reviewed target or tool-capability changes. Keep the actual Penpot editor visible. SDK in-memory tests establish discovery, typed input refusal, explicit write opt-in, root isolation and target-change refusal; actual agent/editor compatibility remains a pending I12 trial.
58
+
59
+ For explicit tab activation, heartbeat diagnostics and the limits of background operation, see [recovering a suspended Penpot tab](browser-sessions.md). The CLI `wake` action is separate from the read-only doctor tool.
package/docs/run-file.md CHANGED
@@ -69,3 +69,5 @@ The common verifier requires matching nonempty expected/actual objects. Empty `p
69
69
  Final wrapped UTF-8 snippets obey the existing 90 KB guard, including helpers, guards, parameters and the captured inspection passed to apply. Every phase compiles offline before connecting; an oversized runtime inspection can still make apply refuse before delivery. Each input file is bounded independently, and at most 20 distinct include paths and 100 parameters are accepted. Reports identify the ordered source text hashes actually prepared. No implicit imports, glob expansion or transpilation occurs.
70
70
 
71
71
  Writes reuse pinned file guards, revisions, writer coordination, positive independent verification and redacted journals. Optional `--checkpoint`, `--force-lock` and explicit `--apply --resume RUN_ID` follow normal recovery rules. Source/parameter changes refuse resume; a final state already independently verified is not written again. Partial unexplained state requires inspection; there is no rollback or enforced idempotency sandbox. A redacted journal cannot be presented as complete verification or automatically resumed.
72
+
73
+ On a large page, scope layout settling to the existing boards touched by the write with repeatable `--layout-root SHAPE_ID`. `--layout-max-nodes N` sets the shared sample budget (1–100,000; default 10,000). The roots must exist before the write and remain present through readback. Reports and `penpair runs RUN_ID` retain the roots and sampled node count. Budget overflow continues with explicitly partial layout evidence; independent readback is still required. See [layout settling](layout-settling.md) for scope and partial-sample limits.
@@ -11,7 +11,7 @@ Generated from the core error remedy inventory. Regenerate with `pnpm docs:gener
11
11
  | `incomplete-output` | Required output is missing, truncated or unstable. Narrow the scope or resolve the capture problem before accepting it as complete. |
12
12
  | `mcp-unavailable` | Start the Penpot instance and check the MCP URL. Run `penpair probe` to see what answers. |
13
13
  | `plugin-disconnected` | In the open Penpot file choose File, MCP Server, Connect, and keep that tab visible, then run the command again. |
14
- | `plugin-suspended` | The browser put the Penpot tab to sleep. Bring it to the foreground and keep it visible while a command runs. A suspended-tab error does not mean nothing was written: read the page before running a write again. |
14
+ | `plugin-suspended` | The browser put the Penpot tab to sleep. Bring it to the foreground, or use penpair wake --debug-port PORT with a dedicated debugging browser, then run penpair doctor. Waking is temporary recovery. A suspended-tab error does not mean nothing was written: read the page and reconcile the run before explicitly replaying a write. |
15
15
  | `snippet-too-large` | Split the snippet. Penpot's proxy refuses request bodies of about 110 KB, so snippets stay under 90 KB. |
16
16
  | `stale-inspection` | The file, revision, query or inspected content changed. Discard the old cursor/chunks and start a fresh capture. |
17
17
  | `tool-failed` | Penpot's tool reported a failure; the message above says what. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@penpair/web",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "The penpair command for Penpot on the web (design.penpot.app or your team's own): guided setup, a safe write protocol, skills for AI agents. Includes everything in @penpair/core.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,7 +48,7 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@penpair/core": "0.4.0"
51
+ "@penpair/core": "0.4.1"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "tsc -p tsconfig.json",