@penpair/web 0.1.3 → 0.3.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.
Files changed (90) hide show
  1. package/README.md +38 -237
  2. package/docs/adapters.md +83 -0
  3. package/docs/approvals.md +19 -0
  4. package/docs/artifacts.md +13 -0
  5. package/docs/audits.md +13 -0
  6. package/docs/backup.md +54 -0
  7. package/docs/branch-review.md +59 -0
  8. package/docs/build-kit.md +59 -0
  9. package/docs/command-contract.md +27 -0
  10. package/docs/compatibility.md +46 -0
  11. package/docs/conventions/README.md +63 -0
  12. package/docs/conventions/completion-2026-10-22.md +73 -0
  13. package/docs/conventions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  14. package/docs/conventions/plugin-code.md +23 -0
  15. package/docs/conventions/structure.md +48 -0
  16. package/docs/conventions/templates/adapter-command.ts +28 -0
  17. package/docs/conventions/templates/conventions.test.ts +81 -0
  18. package/docs/conventions/templates/frame-inspection.ts +27 -0
  19. package/docs/conventions/templates/frame-label.ts +15 -0
  20. package/docs/conventions/tsconfig.json +13 -0
  21. package/docs/conventions/typescript.md +59 -0
  22. package/docs/conventions/vitest.config.ts +11 -0
  23. package/docs/cookbook.md +25 -0
  24. package/docs/coverage.md +50 -0
  25. package/docs/credentials.md +27 -0
  26. package/docs/design-workflow.md +13 -0
  27. package/docs/extensions-trust.md +13 -0
  28. package/docs/fixtures/browser-capture-2026-10-22.json +217 -0
  29. package/docs/fixtures/feedback-reproductions.md +12 -0
  30. package/docs/fixtures/feedback-scratch.template.json +9 -0
  31. package/docs/formats.md +22 -0
  32. package/docs/getting-started.md +12 -2
  33. package/docs/guide.md +83 -9
  34. package/docs/handoff.md +34 -0
  35. package/docs/improvements.md +1217 -0
  36. package/docs/inspection.md +38 -0
  37. package/docs/layout-settling.md +13 -0
  38. package/docs/marwes-components.md +34 -0
  39. package/docs/marwes-tokens.md +59 -2
  40. package/docs/marwes.md +9 -0
  41. package/docs/mcp.md +40 -0
  42. package/docs/mode-verification.md +35 -0
  43. package/docs/plans/registry-polling-fix.md +101 -0
  44. package/docs/plans/tradient-feedback-0.2.0.md +209 -0
  45. package/docs/plans/tradient-feedback-review.md +83 -0
  46. package/docs/pr-improvements.md +57 -0
  47. package/docs/recovery.md +33 -0
  48. package/docs/releasing.md +172 -22
  49. package/docs/run-file.md +71 -0
  50. package/docs/shots.md +15 -0
  51. package/docs/skill-installation.md +16 -0
  52. package/docs/snapshots.md +20 -0
  53. package/docs/status.md +7 -0
  54. package/docs/team.md +17 -0
  55. package/docs/theme-previews.md +18 -0
  56. package/docs/token-editing.md +22 -0
  57. package/docs/tradient-followups.md +77 -0
  58. package/docs/trials/report-template.json +76 -0
  59. package/docs/trials.md +41 -0
  60. package/docs/troubleshooting.md +26 -0
  61. package/docs/upstream/marwes-1.11.0-evidence.json +15 -0
  62. package/docs/upstream/marwes-requests.md +35 -0
  63. package/examples/component-pilot/renderer.mjs +100 -0
  64. package/examples/coverage-matrix.json +126 -0
  65. package/examples/hello-card/README.md +18 -0
  66. package/examples/hello-card/build.js +24 -0
  67. package/examples/hello-card/helpers.js +27 -0
  68. package/examples/hello-card/inspect.js +4 -0
  69. package/examples/hello-card/readback.js +3 -0
  70. package/examples/reflection-browser/README.md +49 -0
  71. package/examples/reflection-browser/capture.mjs +311 -0
  72. package/examples/reflection-browser/fixture/Abel-Regular.ttf +0 -0
  73. package/examples/reflection-browser/fixture/OFL.txt +93 -0
  74. package/examples/reflection-browser/fixture/index.html +11 -0
  75. package/examples/reflection-browser/fixture-hooks.mjs +24 -0
  76. package/examples/reflection-browser/provider.mjs +90 -0
  77. package/examples/reflection-browser/serve.mjs +37 -0
  78. package/examples/theme-preview/README.md +21 -0
  79. package/examples/theme-preview/apply.js +8 -0
  80. package/examples/theme-preview/helpers.js +12 -0
  81. package/examples/theme-preview/inspect.js +3 -0
  82. package/examples/theme-preview/readback.js +7 -0
  83. package/examples/trials/hold-inspect.js +5 -0
  84. package/examples/trials/hold-readback.js +8 -0
  85. package/examples/trials/hold-writer.js +16 -0
  86. package/examples/trials/recovery-build.js +23 -0
  87. package/examples/trials/recovery-inspect.js +10 -0
  88. package/examples/trials/recovery-readback.js +7 -0
  89. package/examples/trials/wrong-file-read.js +3 -0
  90. package/package.json +3 -2
@@ -0,0 +1,83 @@
1
+ # Adoption feedback branch review
2
+
3
+ Date: 2026-10-22. Branch: `codex/tradient-feedback`. Base: `57f9782`.
4
+
5
+ Code phases 0–6 are implemented and verified. The branch is ready for owner review. Live Penpot acceptance remains pending; the lease/save race is investigated rather than claimed fixed. The owner has authorized pushing this branch and opening its feature PR. No version bump, merge or publication was performed. All six repository versions remain 0.2.0.
6
+
7
+ ## What each update resolves
8
+
9
+ | Finding | Delivered result | Verification and limit |
10
+ | --- | --- | --- |
11
+ | PP-01: native layout timeout blocks default build | Shared bounded auto/native/geometry policy for SDK and kit; CLI selection; three complete matching geometry/hierarchy samples | Native success/failure/hang, transport/cancellation/malformed/target refusal and independent matching exports tested; real Penpot build pending |
12
+ | PP-02: unexplained metadata-only token drift | Stable reason codes and explicit existing ownership migration guidance | Legacy values unchanged while schema/hash differs reproduces and explains staleness; migration preserves provenance |
13
+ | PP-03: readiness is unclear | Bounded origin, transport, fresh execution challenge, file/page identity and file-pin evidence | Sleeping origin/editor, stale/malformed challenge, wrong pin and redaction tested; real editor state trials pending |
14
+ | PP-04: simultaneous theme preview needs custom glue | Offline versioned projection, shared ordered alias resolver, adapter command and owned-root example | Precedence, composites, unknowns and sandbox preservation tested; native frame themes remain unproved |
15
+ | PP-05: real comparison provider is missing | Packaged browser starter with project hooks, actual state/fonts, pointer, occlusion and crop evidence | Eight real local Chromium cases, delayed fonts and six rejection cases; unapproved synthetic references do not establish Penpot parity |
16
+ | PP-06: workflow/companions are hard to discover | Companion setup guide, grouped installed help and first-frame composition checklist | Help, docs, core neutrality and idempotent installed skills tested; composition still requires judgment |
17
+ | PP-07: suspected self-save revision conflict | Optional redacted lease/inspection/revision traces and conservative reproducer | **Unresolved causality.** Own delayed persistence and external edits still conflict; no revision guard was relaxed |
18
+ | PP-08: multi-page snapshots fail | Observed page-ID polling, serialized sessions, per-page progress and starting-page restoration | Delayed/concurrent collectors, manual navigation, source edits and restoration failures tested; real multi-page trial pending |
19
+
20
+ The final review also reproduced and fixed cancellation allowing a third collector to bypass an active session. Queue order now survives cancellation. Layout and doctor reject malformed evidence, and browser frame sampling has a deadline.
21
+
22
+ ## Repository readability follow-up
23
+
24
+ Biome 2.6.2 supplies a separately committed formatting baseline (`b1a9445`).
25
+ [Conventions and executable examples](../conventions/README.md), an `AGENTS.md`
26
+ reference and the repository-local coding skill now guide naming and module
27
+ boundaries. `pnpm check` also enforces formatting, focused lint rules, kebab-case
28
+ code paths and checked templates. The comparison adapter is split into focused
29
+ modules with its existing public entrypoint intact. The
30
+ [completion checklist/report](../conventions/completion-2026-10-22.md) explains
31
+ what each change resolves and the remaining formatter/review limits.
32
+
33
+ ## Validation
34
+
35
+ - `pnpm check`: **643 tests** pass (core 498, Docker 28, token adapter 99, comparison adapter 15, convention examples 3), style/path checks, build/typecheck and six-package version guard pass.
36
+ - `pnpm smoke:package`: all **six tarballs** install and run in clean consumers. Installed help, skills, adapter documentation and browser starter are verified, including the comparison adapter's preserved runtime exports and public TypeScript declarations.
37
+ - Real browser trial: bundled Playwright **1.62.1**, cached Chromium **148.0.7778.96**; desktop/mobile × light/dark × loaded/empty. Missing/wrong font face/weight, wrong theme, scale mismatch, hover contamination and sticky occlusion refuse; delayed font loading succeeds. Baseline bytes remain unchanged and verdict stays `owner-review-required`.
38
+ - Actual captures were visually inspected. That inspection found a sticky header hiding the heading; an occlusion guard and explicit project scroll hook corrected it. [Portable evidence](../fixtures/browser-capture-2026-10-22.json) records the cases without machine paths or credentials.
39
+ - `git diff --check` passes. Changesets cover fixes and new public capabilities; repository versions are unchanged.
40
+
41
+ ## Commits
42
+
43
+ | Step | Commit |
44
+ | --- | --- |
45
+ | Baseline/reproductions | `cc65514` |
46
+ | Bounded layout | `2c60ac5` |
47
+ | Migration/readiness | `b985dc2` |
48
+ | Coordination/snapshots | `b51ad30` |
49
+ | Help/composition | `81f20d3` |
50
+ | Browser starter | `79e8c8e` |
51
+ | Projected themes | `415b291` |
52
+ | Final boundary fixes | `5bcc687` |
53
+
54
+ ## Remaining acceptance
55
+
56
+ - [x] Owner authorizes pushing the branch and opening its feature PR (2026-10-22).
57
+ - [ ] Owner reviews the branch before merge or release work.
58
+ - [ ] Owner confirms a disposable Penpot copy by public file ID; populate the [scratch manifest](../fixtures/feedback-scratch.template.json). No credentials belong in it.
59
+ - [ ] Run shipped CLI/kit build, repeat it and change one heading across the eight cases; independently compare IDs, bindings, geometry and two inspected exports.
60
+ - [ ] Verify fresh doctor reads with responsive/background/disconnected scratch editor states.
61
+ - [ ] Capture idle/no-op and controlled external-edit traces; establish PP-07 causality before reviewing a revision-order change.
62
+ - [ ] Run a two-page snapshot with restoration and projected-color preservation checks for global selections, catalog/order and unrelated objects.
63
+ - [ ] Prove any native frame-theme or component override capability separately; projected appearance is insufficient.
64
+ - [ ] After owner review and acceptance decisions, prepare the release through Changesets and existing protected publication workflow.
65
+
66
+ The [living execution guide](tradient-feedback-0.2.0.md) contains the per-phase Resolved / Changed / Validated / Risks / Next reports. Pending live items remain unchecked; this branch does not claim rollback, exactly-once writes, native component parity or human visual approval.
67
+
68
+ ## Release readiness check — 2026-10-22
69
+
70
+ - [x] Working branch is based on remote main (`57f9782`) verified during the readiness check.
71
+ - [x] Pending Changesets plan **0.3.0 for all six packages** without changing versions on this feature branch.
72
+ - [x] GitHub has both release activation variables set to `true`, and `CHANGESETS_PR_TOKEN` is present. Both release workflows are active; recent version-PR workflow runs succeeded.
73
+ - [x] `npm-release` allows the `main` branch and requires the owner as reviewer, with self-review allowed.
74
+ - [x] The temporary `PENPAIR_RELEASE_EXCLUDE_PENPAIR=true` exception remains enabled: all six repository versions advance together, while the five scoped packages must publish and verify.
75
+ - [x] Remove trailing whitespace from the added font license without changing its text, and replace stale hardcoded versions in the release guide. Branch whitespace checks and current style checks pass.
76
+ - [ ] Push/open the feature PR and pass its remote CI before merging.
77
+ - [ ] Review/merge the generated **Release Penpair** version PR, approve the environment and verify the five required registry versions/provenance.
78
+
79
+ The code is ready for a feature PR with the live acceptance limits above visible.
80
+ Its merge does not publish: it creates/updates the version PR. Publication runs
81
+ after that separate PR is merged and the environment is approved. The recent
82
+ publish workflows were skipped; this check verifies repository configuration,
83
+ not a completed npm OIDC publication or npm account publisher permissions.
@@ -0,0 +1,57 @@
1
+ # PR draft
2
+
3
+ Target: `main` ← `codex/penpair-improvements`
4
+
5
+ Title: **Add guarded design workflows and reviewed whole-family releases**
6
+
7
+ ## Description
8
+
9
+ Penpot operations now pin the target file, stop ambiguous write retries, require
10
+ positive readback evidence and record recovery progress. Structured inspection,
11
+ token ownership/reconciliation, explicit design cases, approvals, snapshots and
12
+ implementation handoffs make design-to-code work reproducible and reviewable.
13
+
14
+ The branch also adds project MCP tools, reusable build recipes, focused agent
15
+ skills, private credential references, advisory audits and clearer documentation.
16
+ The neutral core and one-way package dependency rules remain enforced.
17
+
18
+ Releases now use Changesets to collect notes into one **Release Penpair** PR. All
19
+ six packages receive the same bump, including unchanged packages. Merging that
20
+ reviewed version PR can trigger the existing guarded whole-family publisher using
21
+ npm OIDC. Feature merges only collect notes. The owner-approved temporary
22
+ `PENPAIR_RELEASE_EXCLUDE_PENPAIR=true` repository variable can omit only the
23
+ blocked short-name wrapper from publishing, with explicit logs/summary and all
24
+ other packages still required. All six repository versions continue together. A separate GitHub PR token is needed
25
+ for automatic version-PR checks. Both automation paths remain disabled until
26
+ owner activation; no package version is changed by this PR. The pending note
27
+ proposes 0.2.0 in the separate generated version PR.
28
+
29
+ ## Validation
30
+
31
+ - `pnpm check`: build, typecheck, 583 tests and lockstep/config checks.
32
+ - `pnpm smoke:package`: all six tarballs install and run in clean consumers.
33
+ - Real Changesets CLI tests verify unchanged-package bumps, highest-level
34
+ selection, changelogs, note consumption and exact workspace dependencies.
35
+ - A disposable full version-wrapper rehearsal moves every package, changelog and
36
+ the source constant to 0.2.0; this branch remains at 0.1.3.
37
+ - All three workflows pass actionlint 1.7.12 and parsed permission/gate checks.
38
+ - Gate tests cover ordinary pushes, exact merged version PRs, manual confirmation,
39
+ pending notes, malformed auth evidence and workflow permission separation.
40
+ - Detached-checkout regression runs the exact CI Changesets command against the
41
+ base SHA and verifies consumed-note version PRs through their dedicated path.
42
+ Version-PR creation also supports a main-only manual trigger.
43
+
44
+ ## Open acceptance and activation
45
+
46
+ - Hosted export/import restore proof (G3), two-person live trials (I12), and
47
+ real component measurement/override proof (C1) remain unchecked by request.
48
+ Native component generation remains gated.
49
+ - Configure GitHub main/environment protection, the PR token and trusted
50
+ publishers for every npm package before enabling release automation. Verify
51
+ the previously refused short name and any unpublished package bootstrap.
52
+ - npm publication is sequential; partial failures require explicit recovery.
53
+ - No remote policy, live Penpot mutation, consumer edit, upstream message,
54
+ release, tag or deployment was performed.
55
+
56
+ Review details: [branch review](branch-review.md), [living checklist and
57
+ reports](improvements.md), [release activation](releasing.md#one-time-owner-activation).
@@ -0,0 +1,33 @@
1
+ # Recovering interrupted writes
2
+
3
+ CLI archive, registered write operations and adapter token pushes record their plans in `.penpair/runs/<run-id>.json` before applying a step. The directory is ignored by Git before evidence is written; records use mode 0600. Evidence can contain design content, so keep the directory private. Endpoint credentials are redacted, including known bare keys. A journal whose evidence needed redaction cannot automatically resume.
4
+
5
+ Inspect a failure locally:
6
+
7
+ ```sh
8
+ penpair runs
9
+ penpair runs <run-id>
10
+ ```
11
+
12
+ These commands work offline, even when project configuration is broken. Resume the original command with its original inputs and explicit apply:
13
+
14
+ ```sh
15
+ penpair archive --page Drafts --name Archive --keep Live --parts Parts --apply --resume <run-id>
16
+ penpair run <write-name> --param name=value --apply --resume <run-id>
17
+ ```
18
+
19
+ Adapter token push also accepts `--apply --resume <run-id>`. It uses the saved original actions and the current committed snapshot. Keep the same engine, operation implementation, snapshot, parameters, prune and checkpoint choices. Changed inputs or source signatures are refused. The direct library runner opts in through `OperationOptions.journal`; unjournaled library calls do not gain recovery automatically.
20
+
21
+ Resume checks file identity and independently reads actual state. Verified steps are checked again. Token batches rebuild residual work, including partial additions and replacements, rather than blindly replaying previous writes. Third values, changed protected shape structure, or ambiguous identity stop recovery. An archive with a partially created parent whose ownership cannot be established stops for inspection. Custom operations may supply an explicit reconciliation function; otherwise only unchanged inputs or independently verified final state permit recovery.
22
+
23
+ Add `--checkpoint` to request a run-keyed named Penpot version before operation steps. That flag requires a supported API and independently verified checkpoint; an unavailable checkpoint stops the run. Keep the flag when resuming. A checkpoint is a recovery aid, and is neither a transaction nor proof that a separate backup can be restored. See [backup guidance](backup.md).
24
+
25
+ A response lost after a mutation leaves the step uncertain, even if the file changed successfully. Completed batches remain applied. There is no automatic rollback over human edits. This release's recovery tests use simulated Penpot execution and HTTP transport faults; live recovery on a scratch file remains to be checked. Journaled CLI writers also use local locks and shared advisory markers; see [team coordination](team.md).
26
+
27
+ ## Coordination evidence
28
+
29
+ Use `--trace --apply` on journaled core writes or `marwes tokens push` to retain bounded lease/revision events in the private run journal. Inspect with `penpair runs RUN_ID`. Events contain identity, stage and time; they omit document text, token values and endpoint credentials. A revision change remains a conflict even if delayed marker persistence is suspected. Tracing does not establish causality or permit automatic retries.
30
+
31
+ ## Fresh doctor evidence
32
+
33
+ `penpair doctor --json --timeout-ms 5000` uses a total origin/connection/execution budget (1000–30000 ms). It reports origin reachability, connected transport, a fresh random editor challenge with file/page IDs, and file-pin matching separately. Observation times/durations are measured; heartbeat and unobserved API versions stay unknown. A reachable origin or a connected transport alone is insufficient. Reconnect/foreground an unresponsive editor and obtain a fresh read before starting another operation.
package/docs/releasing.md CHANGED
@@ -1,44 +1,154 @@
1
1
  # Releasing
2
2
 
3
- All packages in this repository are released **together, at one version, even a package that did not change**: `@penpair/core`, `@penpair/web`, `@penpair/docker`, `@penpair/marwes-ui`, `penpair`, and anything added under `packages/` later. The root of the repository is a private workspace and is never published. The rule is enforced, not remembered:
3
+ All packages in this repository are released **together, at one version, even a package that did not change**: `@penpair/core`, `@penpair/web`, `@penpair/docker`, `@penpair/marwes-ui`, `@penpair/reflection-check`, `penpair`, and anything added under `packages/` later. The root of the repository is a private workspace and is never published. The rule is enforced, not remembered:
4
4
 
5
5
  | Guard | What it stops |
6
6
  | --- | --- |
7
7
  | `pnpm check` (and CI) runs `scripts/check-versions.mjs` | Any package at a different version, a version that differs from `packages/core/src/version.ts`, a private package, an internal dependency that is not `workspace:*`, a package without the publish guard |
8
- | `pnpm bump patch\|minor\|major\|x.y.z` | Drift starting at all: it sets every package and `version.ts` in one step |
8
+ | `pnpm version-packages` | One Changesets fixed group versions every package, synchronizes `version.ts` and refreshes the lockfile |
9
+ | `pnpm check:versions` | Missing, duplicate, ignored or separately grouped release packages, including packages added later |
9
10
  | `prepublishOnly` in every package | `pnpm publish` inside one package: it refuses unless `pnpm release` started it, and checks the versions again |
10
11
  | `pnpm release` | A half release: it checks, publishes the whole set in dependency order, looks each one up on npm, and reports anything missing |
11
12
 
12
13
  Publishing cannot be undone: a version, once published, cannot be replaced. Check first, publish once.
13
14
 
14
- ## One time
15
-
16
- 1. The npm organisation `penpair` exists (npmjs.com, Add organization, free for public packages).
17
- 2. `npm whoami` prints your user name. If it answers `401`, run `npm login`.
15
+ ## Normal release: review a version PR
16
+
17
+ 1. Add `pnpm changeset` to each package/script change, select an affected package
18
+ and patch/minor/major, and describe the user-visible change. Commit the note
19
+ with the feature. Documentation-only changes need no note; a script change
20
+ with no release impact can use `pnpm changeset --empty`.
21
+ 2. Merge the feature PR. Once enabled, `.github/workflows/version-pr.yml` creates
22
+ or updates one **Release Penpair** PR from `changeset-release/main`.
23
+ 3. Review the generated package changelogs and shared version. The highest bump
24
+ requested across pending notes applies to **every** member of the fixed group,
25
+ including unchanged packages. `pnpm version-packages` runs Changesets, updates
26
+ the CLI source constant, refreshes the lockfile and checks consistency. It
27
+ never publishes. Never run it on a feature branch. Feature-PR release-note
28
+ checks use the exact PR base SHA, so detached CI checkouts need no local main
29
+ branch. Version PRs instead verify the increased shared version and consumed
30
+ notes through the dedicated release verifier.
31
+ 4. Merge the version PR after CI passes. `.github/workflows/release.yml` checks
32
+ the GitHub API for that exact merged, same-repository release PR and verifies
33
+ a higher committed version with no pending notes. Ordinary main pushes skip
34
+ publishing. The protected `npm-release` environment may require an additional
35
+ owner approval, depending on its configured protection.
36
+ 5. The existing `pnpm release` entrypoint reruns checks and all package smoke
37
+ installs, publishes packed tarballs in dependency order with npm OIDC and
38
+ provenance, and verifies every required registry version (all six by default). A moved main branch stops
39
+ the job instead of publishing a different commit. Do not merge another main
40
+ change until a release finishes; use manual recovery if it has moved.
41
+
42
+ Automation is opt-in through the activation switches below. Merging a feature PR
43
+ creates or updates the version PR; publishing starts only after that reviewed
44
+ version PR is merged and any environment approval is granted. Pending Changesets
45
+ determine the next shared version. Package versions remain unchanged on feature
46
+ branches until the separate version PR is generated and reviewed.
47
+
48
+ ## One-time owner activation
49
+
50
+ - [ ] Review and merge the release implementation; do not enable automation on
51
+ an unreviewed branch. Confirm GitHub's current action/runtime requirements.
52
+ - [ ] Protect `main`: require the CI job and owner approval, dismiss stale
53
+ approvals, restrict bypasses, and disable automatic merging of release PRs.
54
+ Prefer squash/merge commits; the automatic gate requires GitHub's recorded
55
+ merge SHA to equal the exact main push (ambiguous rebase history fails closed).
56
+ - [ ] Add repository secret `CHANGESETS_PR_TOKEN`: a fine-grained GitHub token
57
+ limited to this repository with Contents and Pull requests read/write, or a
58
+ managed GitHub App token with the same rights. No npm permission is needed.
59
+ Use a bot identity and restrict writes to `changeset-release/main` where your
60
+ GitHub plan supports it. The token lets generated PR checks run automatically;
61
+ do not give it branch-protection bypass. Never put it in repository files.
62
+ - [ ] Set `PENPAIR_RELEASE_AUTOMATION_ENABLED=true` to enable version PR creation.
63
+ After this workflow is on main, open **Actions → Release version PR → Run
64
+ workflow**, choose `main`, and run it to create the first version PR without
65
+ another commit. The manual trigger still requires the activation variable and
66
+ main branch. Inspect the generated PR and confirm its CI checks actually run.
67
+ - [ ] Create/protect environment `npm-release`, restrict deployments to main and
68
+ configure required reviewers if retaining a second approval before publish.
69
+ - [ ] Configure the exact GitHub repository, `release.yml` workflow and
70
+ `npm-release` environment as npm trusted publisher for **every** package.
71
+ Node >=22.14.0/npm >=11.5.1 and supported hosted runners are required; the
72
+ workflow uses Node 24 and checks npm before publishing.
73
+ - [ ] Verify publishing/bootstrap permissions for every package, especially the
74
+ previously refused short name `penpair` and the new comparison adapter. A
75
+ missing or blocked required package stops publishing. The owner-approved
76
+ temporary `PENPAIR_RELEASE_EXCLUDE_PENPAIR=true` exception may omit only
77
+ the short-name wrapper; every other package remains required.
78
+ - [ ] Set `PENPAIR_TRUSTED_RELEASE_ENABLED=true` only when all required packages
79
+ are provisioned (five while the explicit short-name exception is enabled). Merge the reviewed version PR to release. Inspect registry versions
80
+ and provenance for all packages afterward.
81
+
82
+ No npm token is needed for this path. The PR token authorizes GitHub changes;
83
+ short-lived OIDC authorizes npm publishing. The versioning job has no OIDC
84
+ permission; the publishing job has no repository write token. No workflow creates
85
+ or pushes tags or GitHub releases automatically.
86
+
87
+ ## Temporary exception for the short name
88
+
89
+ While npm cannot accept `penpair`, set this **repository Actions variable**:
90
+
91
+ ```text
92
+ PENPAIR_RELEASE_EXCLUDE_PENPAIR=true
93
+ ```
18
94
 
19
- ## Each release
95
+ This is the only configurable publishing exclusion. The same shared version and
96
+ Changesets fixed group still cover **all six** packages. Both automatic and manual
97
+ CI releases publish and verify the other five, skip registry access/publication
98
+ for `penpair`, and display the exception in logs and the GitHub step summary.
99
+ A successful run means all five required packages are verified; it does not claim
100
+ the full six-package family is published. Any other missing package still fails.
101
+ The exception is rejected if a required package starts depending on the wrapper.
102
+
103
+ The variable defaults to `false`/unset. Values other than `true`, `false` or empty
104
+ fail before release work. This variable does not enable automation or publishing.
105
+ Keep the two activation switches disabled until owner setup is complete. While
106
+ the exception is enabled, leave only `penpair`'s npm trusted publisher pending.
107
+ The `npm-release` environment does not need its own copy of this variable.
108
+
109
+ Once the name is available, configure its trusted publisher and set the repository
110
+ variable to `false` or remove it. For the latest reviewed version whose other five
111
+ packages already exist on npm, use manual dispatch with that exact version and
112
+ `continue_release=true`; the unchanged recovery guard publishes only the missing
113
+ wrapper. If nothing at the current version has been published, release normally.
114
+ The first run without the exception refuses a partial family unless continuation
115
+ is explicit. Historical skipped versions are not automatically backfilled.
116
+
117
+ Local owner-led releases can use the same environment variable. The older local
118
+ `--except penpair` option below still deliberately returns INCOMPLETE/exit 3; it
119
+ is separate from the configured CI exception, which succeeds once its five
120
+ required packages are independently verified.
121
+
122
+ ## Local owner-led recovery
123
+
124
+ `pnpm bump patch|minor|major|x.y.z` remains a deliberate emergency tool, never
125
+ combined with Changesets versioning for the same release. Normal versions are
126
+ owned by the generated version PR. Do not hand-edit package versions. The root
127
+ workspace version is private and is excluded.
128
+
129
+ For a reviewed, committed, clean and pushed main release, local `npm login`/OTP
130
+ publishing remains available:
20
131
 
21
132
  ```bash
22
- pnpm bump patch # or minor, major, or an exact x.y.z: every package and version.ts move together
23
- # add a section to CHANGELOG.md
24
- pnpm check # typecheck, tests, build and the version check
25
- git commit -am "Release 0.1.3" && git push
26
-
27
- pnpm release # dry run: checks everything, prints the plan, publishes nothing
28
- pnpm release --publish # the real thing; npm asks for your one-time code (or add --otp <code>)
133
+ pnpm release # dry run; publishes nothing
134
+ pnpm release --publish --confirm-version X.Y.Z
29
135
  ```
30
136
 
31
- `pnpm release` refuses unless all of this is true, and tells you which part is not:
32
-
33
- - every package has the same version, and the source version matches;
34
- - you are on `main`, the working tree is clean, and `main` equals `origin/main`, so what is published is what is on GitHub;
35
- - npm has none of the packages at this version (or it is an interrupted release, below);
36
- - `pnpm check` and `pnpm smoke:package` pass (the smoke test packs every package, installs the tarballs in empty projects and runs them).
37
-
38
- Then it publishes in dependency order (the core, then the packages that depend on it), waits until npm shows each one, and finally looks up all of them. If any is missing it says `INCOMPLETE` and exits with 3.
137
+ The manual GitHub **Publish reviewed Penpair release** dispatch also remains:
138
+ enter the exact version already on main. It rejects mismatches or pending notes;
139
+ set `continue_release` only after inspecting an interrupted release. The dispatch
140
+ still passes through the protected environment and all unchanged release guards.
39
141
 
40
142
  ## If a release stops half way
41
143
 
144
+ After a successful upload, npm may still be processing the version. The release
145
+ checks registry visibility immediately and then once per minute within a
146
+ ten-minute deadline **for each uploaded package**, printing progress while it
147
+ waits. Each metadata query has a 30-second maximum (or the remaining deadline,
148
+ whichever is smaller), with npm's internal retries disabled. Query time counts
149
+ toward the deadline. A registry error still stops the release; only an actual
150
+ not-found result waits for propagation. The visibility wait never republishes.
151
+
42
152
  A publish can fail after some packages are out (a network error, a wrong one-time code, a name npm refuses). The version is then on npm for some packages and not for others, which must not stay that way. Fix the cause and run:
43
153
 
44
154
  ```bash
@@ -47,6 +157,26 @@ pnpm release --publish --continue # publishes only what is missing at this ve
47
157
 
48
158
  Without `--continue`, `pnpm release` refuses to run while only some packages are at the version, so a partial release cannot be forgotten.
49
159
 
160
+ For GitHub recovery, run **Publish reviewed Penpair release** on `main`, enter
161
+ the exact interrupted version and set `continue_release=true`. Approve
162
+ `npm-release` when requested. This checks the registry again, skips versions
163
+ already present and publishes only missing required packages. Inspect the final
164
+ package/provenance verification before counting the release complete.
165
+
166
+ Finish an interrupted release before merging unrelated changes or a tooling fix
167
+ with new pending Changesets. The manual release gate requires a reviewed version
168
+ with no pending notes; merging another feature can prevent continuation until
169
+ that release state is reconciled. Do not discard pending package-release notes
170
+ or weaken guards to force recovery through.
171
+
172
+ An empty, tooling-only Changeset contains no package release entries. Changesets
173
+ can consume that metadata without changing versions. If such a note blocks an
174
+ interrupted release, handle it in a reviewed metadata-only cleanup, preserve its
175
+ summary in the completion report and verify unchanged package/source versions
176
+ plus the existing manual gate. This does not apply to notes requesting a package
177
+ release. The [0.3.0 early-merge recovery](plans/registry-polling-fix.md#early-merge-recovery)
178
+ records the concrete case and checks.
179
+
50
180
  ## A package npm will not accept yet
51
181
 
52
182
  `penpair` (the short name) is refused by npm while its name is blocked as too close to another package. To release the rest and keep the rule visible, leave it out explicitly:
@@ -64,6 +194,7 @@ npm view @penpair/core version
64
194
  npm view @penpair/web version
65
195
  npm view @penpair/docker version
66
196
  npm view @penpair/marwes-ui version
197
+ npm view @penpair/reflection-check version
67
198
  ```
68
199
 
69
200
  Then, in an empty folder, run `npx @penpair/web --version`, `npx @penpair/web setup --help` and `npx @penpair/docker --version` (and, once the short name is published, the same with `npx penpair`). Tag the release if you want one: `git tag v<version> && git push origin v<version>`.
@@ -75,3 +206,22 @@ The core ships `dist`, `skills`, `LICENSE` and a short `README.md`. The web pack
75
206
  ## After publishing
76
207
 
77
208
  Update the version in `PLAN.md` and add the change log line. A problem found in a published version is answered with a new version of **all** packages, never by changing a published one.
209
+
210
+ ## Authentication and limits
211
+
212
+ See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) and
213
+ [provenance requirements](https://docs.npmjs.com/generating-provenance-statements/).
214
+ Publishing validates the workflow context and rejects long-lived npm token
215
+ fallback, packs with pnpm to rewrite `workspace:*` to exact versions, and publishes
216
+ single tarballs with npm and provenance. These local checks are operational guards;
217
+ npm authenticates the actual identity against owner-managed publisher settings.
218
+
219
+ npm cannot atomically publish multiple packages. A network/permission failure can
220
+ leave some published and others missing; the release reports failure and requires
221
+ explicit continuation at the same version. A rerun never silently resumes.
222
+
223
+ GitHub branch/environment protection and npm settings must be checked remotely
224
+ before activation. Tests use the real Changesets CLI in disposable workspaces and
225
+ fake GitHub/OIDC evidence; they do not establish account permissions or a live
226
+ publishing result. Owner activation, pushes, merges, version bumps and publishing
227
+ remain separate actions.
@@ -0,0 +1,71 @@
1
+ # Running reviewed plugin bodies from files
2
+
3
+ `run-file` sends explicitly selected JavaScript bodies to the Penpot plugin. It does not import those files or unrelated project extensions into the Node process. Files must be `.js` or `.mjs` bodies for an async function, not host modules. Paths resolve inside the configured project, including real symlink targets. Config-free standalone execution is unavailable.
4
+
5
+ ```sh
6
+ penpair run-file design/read.js --page Candidate --param id=SHAPE_ID \
7
+ --include design/helpers.js --include design/project-helpers.js
8
+ ```
9
+
10
+ Each file that runs read-only at top level starts with `// penpair: read`. This includes helper preludes and inspection/readback phases. Helpers are concatenated in caller order before the phase body, after the page guard. Parameters are literal strings available as `CFG`; duplicate or malformed keys refuse. `CFG.page` is owned by `--page`, and `CFG.inspection` is reserved for writes.
11
+
12
+ A read declaration is a trusted contract, never a JavaScript sandbox. Review includes too. A helper may define functions used by a later write phase, while its top-level inclusion must itself remain read-only. Read executions disable automatic suspension retries and require an explicit `ok: true` result.
13
+
14
+ ## Reviewed writes
15
+
16
+ To preserve the write protocol, a write file needs both leading declarations:
17
+
18
+ ```js
19
+ // penpair: write
20
+ // penpair: idempotent
21
+ ```
22
+
23
+ It also requires explicit read-only `--inspect` and `--readback` files. The command runs only inspection by default:
24
+
25
+ ```sh
26
+ penpair run-file design/rename.js --inspect design/rename-inspect.js \
27
+ --readback design/rename-readback.js --page Candidate \
28
+ --param id=SHAPE_ID --param name=ReviewedName
29
+ ```
30
+
31
+ Review that inspection, then add `--apply`. Undeclared or unverified raw writes refuse even when `--apply` is supplied. This is stricter than a raw executor: acknowledging arbitrary code is insufficient to claim an independently verified design change. For a custom code-defined verifier use a registered snippet operation.
32
+
33
+ A small example follows. Inspection captures the original target:
34
+
35
+ ```js
36
+ // penpair: read
37
+ const shape = penpot.currentPage.getShapeById(CFG.id);
38
+ if (!shape) throw new Error("Target missing");
39
+ return { ok: true, id: shape.id, beforeName: shape.name, desiredName: CFG.name };
40
+ ```
41
+
42
+ The write uses that independent observation, tolerates its already-completed state and refuses a third state:
43
+
44
+ ```js
45
+ // penpair: write
46
+ // penpair: idempotent
47
+ const shape = penpot.currentPage.getShapeById(CFG.id);
48
+ if (!shape || shape.id !== CFG.inspection.id) throw new Error("Target changed");
49
+ if (shape.name !== CFG.inspection.beforeName && shape.name !== CFG.name) throw new Error("Source changed");
50
+ shape.name = CFG.name;
51
+ return { ok: true };
52
+ ```
53
+
54
+ Readback reads the real target from scratch:
55
+
56
+ ```js
57
+ // penpair: read
58
+ const shape = penpot.currentPage.getShapeById(CFG.id);
59
+ return { ok: true, verification: {
60
+ expected: { id: CFG.id, name: CFG.name },
61
+ actual: shape ? { id: shape.id, name: shape.name } : { missing: true }
62
+ } };
63
+ ```
64
+
65
+ The common verifier requires matching nonempty expected/actual objects. Empty `problems: []` cannot pass. The code's author still owns the meaningful postconditions, read-only declarations and idempotency. Export affected frames and look at them twice as the write protocol requires; structural verification does not approve pixels.
66
+
67
+ ## Identity, budgets and recovery
68
+
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
+
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.
package/docs/shots.md ADDED
@@ -0,0 +1,15 @@
1
+ # Named exports and ad-hoc shots
2
+
3
+ ```sh
4
+ penpair export --name "Dashboard / Desktop" --page Dashboard dashboard.png
5
+ penpair export <shape-id> dashboard.png --page Dashboard
6
+ penpair shots --page Dashboard --prefix "Dashboard /" --out design/shots
7
+ ```
8
+
9
+ Names match exactly on the selected page, including nested shapes. Zero matches or multiple matches fail; ambiguity reports candidate IDs instead of silently choosing one. Names shared by multiple pages are also refused for name-based page selection. Use an ID when the shape name is ambiguous. The name search visits at most 10,000 nodes and refuses incomplete lookup. A lookup's file/page/revision must still match the stable export before bytes can be published.
10
+
11
+ `shots` selects top-level frames with an optional prefix. It reuses the stable PNG exporter and the capture identity checks. It supports up to 100 selected frames per run; narrow the prefix for larger pages. Empty or unstable selections exit 3 and publish no new manifest. Intervening edits or export failure preserve the previous complete manifest. `--out` is project-relative and cannot escape through a symlink. With no config, built-in capture supports explicit `--standalone`.
12
+
13
+ The output contains immutable `captures/<run-id>/shot-0001.png` images and a `penpair-shots.json` manifest, published after every capture succeeds. Each entry records the original name, target IDs, revision, PNG dimensions/hash and available render environment. No baseline approval is created. Old generations remain usable; review references before cleaning them. Interrupted local publication can leave an unreferenced generation, as described in [artifact publication](artifacts.md).
14
+
15
+ Use `--json` for the shared command envelope. Unknown option errors suggest a nearby spelling when one is plausible, but never reinterpret or execute the suggestion. Named exports keep the existing scale option, and `shots` accepts it too. Stable bytes and geometry are technical capture evidence; review the resulting images before judging visual correctness.
@@ -0,0 +1,16 @@
1
+ # Install skills and integrate project instructions
2
+
3
+ ```sh
4
+ penpair skills install --agents AGENTS.md --dry-run
5
+ penpair skills install --agents AGENTS.md
6
+ ```
7
+
8
+ The preview lists planned files/links, shows the resolved instruction path and prints the owned-block diff. Dry-run writes nothing, including skill copies and links. Add `--json` for the versioned report. Skill paths reuse the project's existing real skill folder; `--to` selects another project-relative folder. Generated links are relative to the selected instruction file, with spaces and special characters encoded.
9
+
10
+ Without `--agents`, exactly one existing AGENTS.md or CLAUDE.md is selected. If neither exists, installation prints rows and leaves instruction creation to an explicit destination. If both exist, it edits neither and exits 3 with `instructions.status: choice-required`; choose `--agents` explicitly. Skills may still have been installed. `setup --agents <path>` passes that destination to the same installer. Setup without a destination follows the same existing-file policy and reports when instruction integration still needs a choice.
11
+
12
+ The installer appends a skills section when needed or replaces only the contents between `<!-- penpair:skills:start -->` and `<!-- penpair:skills:end -->`. Existing text outside the owned region, including line endings, is preserved. Duplicate, reversed or unmatched markers stop installation before any skill files are written. An instruction file edited after planning is preserved; retry after reviewing its current contents. A repeat run with identical content is a no-op. Existing file permissions are retained.
13
+
14
+ Local outputs must remain inside the project after resolving existing symlinks. An instruction link escaping the project or a dangling link is refused. The resolved destination is shown so a project wrapper or shared in-project target is visible. These operations do not edit global agent configuration. Entry-point/output checks do not sandbox arbitrary extensions.
15
+
16
+ Skill files modified by the project are kept unless `--force` is explicit. Project facts remain project-owned even with force, and rename migration preserves the prior facts. All writes are planned before execution, published as complete files, and instructions are written after skill copies/links. This is not a multi-file transaction: a filesystem failure or a concurrent owner edit can leave skill installation partly applied while preserving the instruction file. Review the report and rerun; unchanged files are skipped.
@@ -0,0 +1,20 @@
1
+ # Structural snapshots for review
2
+
3
+ ```sh
4
+ penpair snapshot
5
+ penpair snapshot --write --out design/snapshots/current
6
+ penpair snapshot diff design/snapshots/before --from design/snapshots/current
7
+ penpair snapshot diff design/snapshots/before --from design/snapshots/current --out design/changes.md
8
+ ```
9
+
10
+ Default mode captures for inspection and writes nothing. `--write` publishes schema-1, page-split structure and token JSON. Every page starts at the actual page root, so loose text/groups outside boards are retained. A reserved structured-inspection selector `@page-root` supports this scope. Page names must be unique for the editor's name-based selection API; ambiguity refuses rather than selecting a similarly named page.
11
+
12
+ The snapshot records file/page/node identities, hierarchy/order, geometry, full text, layout, styles, token bindings, component source IDs/overrides and explicit unknown fields, plus the typed token/theme catalog. All pages and tokens must share the same file/revision, with an independent listing afterward. Changed source, incomplete transfer or credential redaction refuses publication. Capturing can switch the current editor page; no design objects are changed, and the original visible page is restored in a bounded `finally` step. Same-client collections are serialized; page changes require observed IDs rather than a fixed delay. External navigation fails clearly. If capture and restoration both fail, both errors and per-page progress are retained. The previous published manifest stays intact.
13
+
14
+ Review budgets: 100 pages, 50,000 total nodes, 8 MiB per page/token artifact, 128 MiB total, five-minute collection budget checked between bounded captures. Individual captures retain I8 call/time/transfer limits. Source fields that the plugin API cannot observe remain explicit unknowns; a complete traversal does not make those fields known.
15
+
16
+ Artifacts use deterministic key/node/page order, with hierarchy indices retained. Volatile capture times and transport metrics are omitted. Identical source produces identical files. Immutable `generations/HASH/` artifacts are written first and a hash-checked `snapshot.json` pointer is published last. A local exclusive publication lock refuses competing writers. An older revision or another file cannot replace the canonical pointer. Prior generations remain available; deleting them is an owner action.
17
+
18
+ Offline `snapshot diff` validates all manifest/artifact hashes and identities, then reports added/removed pages/nodes and changed text, bindings, layout, geometry, styles, metadata and token catalogs. Foreign file IDs are not comparable. Large diffs show an explicit truncated result with an exact total change count (exit 3); full bounded comparisons with changes exit 2, unchanged exit 0. Markdown keeps design-supplied names/text inside literal untrusted-data fences. The command does not post comments or contact a forge.
19
+
20
+ These artifacts are **review evidence**, not full-fidelity backups. They do not embed external library assets, font/image bytes, collaboration history or every native feature. Keep the [archive/hosted restore workflow](backup.md) for disaster recovery; structural diffs cannot establish a successful restore. Native component override proof and live hosted round trips remain pending.
package/docs/status.md ADDED
@@ -0,0 +1,7 @@
1
+ # Project status and file opening
2
+
3
+ `penpair status --json` reports deployment reachability, configured file pin, actual connected identity/revision/pages, observed API capabilities, skill installation differences and baseline coverage for web or local profiles. Unknown/missing evidence remains visible and produces incomplete status; a reachable web page alone cannot make the project healthy. It does not run a write probe, install skills, activate themes or approve images.
4
+
5
+ By default configured adapters are listed as uninspected. `status --adapters` explicitly loads trusted configured modules and their optional `defineAdapter({status: async context => lines})` hooks. Each line has `id`, `label`, `state` (`ok`, `warning`, `failed`, `unknown`) and `detail`. The token adapter contributes declaration freshness, explicitly separate from a live token comparison. Third-party hooks execute project code and should use the context output contract. Status never implies sandboxed imports or complete native/browser acceptance.
6
+
7
+ `penpair open --print` returns the public URL derived from the configured origin and encoded owner-confirmed file ID. `penpair open` invokes the platform browser opener for that same URL. No MCP key enters it. If the deployment needs extra team context, use the owner's full editor link and confirm the ID in doctor. Browser launch success cannot prove that a login/file page rendered; inspect it normally. Tests use a stub opener, not a real browser session.
package/docs/team.md ADDED
@@ -0,0 +1,17 @@
1
+ # Working on a shared design
2
+
3
+ Run `penpair onboard` (or `--json`) in the project for a checklist generated from its configured origin and pinned file. It works offline and does not load adapters. The owner invites each colleague's own account to the shared file. Each person creates their own MCP key, runs `penpair connect`, opens the pinned file and connects the MCP plugin. Share configuration and the file pin through Git; keep personal keys and `.penpair/` recovery evidence private.
4
+
5
+ A direct file link is included when the pin is known. If Penpot needs team context, use the owner's complete editor link and confirm the file ID with doctor. A Docker `localhost` origin is only reachable on that computer; colleagues need a shared deployment. Onboarding does not grant access or provision accounts.
6
+
7
+ ## Coordinating writes
8
+
9
+ Journaled CLI archive/registered operations and adapter token pushes acquire an exclusive local file for this checkout and, when supported, a shared `penpair/writer` marker on the Penpot file. The marker contains a random ownership token, run ID, operation, username and times. Another active writer refuses the operation. Read-only inspection stays available. Independent readback confirms ownership after acquisition; every step checks it again. The marker expires after 15 minutes and a longer run stops before its next step. There is no background renewal.
10
+
11
+ Agree on a write window and keep the editor awake. A human can still edit. Penpot's [shared plugin-data API](https://doc.plugins.penpot.app/interfaces/File) supplies separate reads/writes rather than an atomic compare-and-swap, so this is advisory across machines. It cannot prevent two simultaneous acquisitions or act as a distributed mutex. The local lock protects this checkout, not other clones or computers. When shared methods are absent, the command warns and uses only the local lock.
12
+
13
+ A crashed local process can leave a lock behind. Inspect its run with `penpair runs <run-id>`, check the file, and coordinate with the named writer before explicitly adding `--force-lock --apply` to the original command. A still-running local process cannot be forced out. The override can replace a foreign shared marker or abandoned local lock; it is not a safe way to interrupt someone. Simultaneous forced takeovers have no exclusion guarantee. Malformed shared marker data requires manual inspection; force does not silently discard it.
14
+
15
+ Revisions are checked inside each journaled operation mutation. Archive also rechecks candidate IDs and geometry. Adapter pushes compare the CLI's source snapshot before planning and recheck the just-inspected token state inside each mutation. These guards detect stale input but do not make a multi-step build transactional. Known self-writes, including checkpoint and marker creation, occur before subsequent revision observations. Custom snippet authors must verify their own relevant content preconditions as well.
16
+
17
+ Direct library calls that omit journal options, native checkpoint helpers invoked separately, and the explicitly destructive scratch deep probe do not acquire the journal's writer lease. Coordinate those manually. No live two-editor trial has been run for this change. See [recovery](recovery.md) after an uncertain write; do not automatically roll back intervening work.
@@ -0,0 +1,18 @@
1
+ # Isolated projected theme previews
2
+
3
+ ```sh
4
+ penpair marwes tokens preview --from design/tokens.json --theme Light
5
+ penpair marwes tokens preview --from design/tokens.json --theme Mode/Dark --out design/dark-preview.json
6
+ ```
7
+
8
+ This is an **offline projection** of declared values. The adapter reads the retained snapshot without importing the installed application theme or connecting to Penpot. Output is schema 1, `kind: projected-theme-preview`, with a hash of the source artifact, its declared snapshot hash, selected theme/set identities and catalog order, resolved values, explicit unknowns, `complete` and `nativeBindings:false`. Missing native IDs remain null; names are not invented IDs.
9
+
10
+ Later sets in catalog order win, regardless of the theme membership list order. Aliases, numeric expressions and structured values use the same neutral resolver as adapter mode verification. Missing sets/ambiguous names refuse; missing aliases, cycles, unsupported units/types or budget excess remain unknown (exit 3). A complete projection exits 0. Output paths stay within the project, refuse overwrites and cannot replace the source. Projection does not validate color meaning, layout, fonts or native component anatomy.
11
+
12
+ See the [owned scratch example](../examples/theme-preview/README.md) for two independent color previews. Projected fills carry separate provenance and are not native color bindings. Existing typography/spacing/radius bindings require their own independent readback.
13
+
14
+ ## Pinned native capability evidence
15
+
16
+ Inspected on 2026-10-22 against repository-pinned **Penpot 2.18.3**. The [pinned token implementation](https://github.com/penpot/penpot/blob/2.18.3/frontend/src/app/plugins/tokens.cljs) activates themes/sets in the file token library. The [pinned shape implementation](https://github.com/penpot/penpot/blob/2.18.3/frontend/src/app/plugins/shape.cljs) exposes token application by token and property. The [official Theme reference](https://doc.plugins.penpot.app/interfaces/TokenTheme) describes active set membership and grouped activation. These contracts do not establish an independently selected theme per frame. This is source-based evidence, not a live runtime capability proof.
17
+
18
+ The adoption report observed hosted API 2.18.4, which is distinct from the pinned Docker runtime. No frame-level theme setter has been verified on either runtime here. Native per-frame color binding, native component parity and override preservation therefore remain unclaimed. A future live capability trial must inspect the actual API, change only owned scratch roots and prove unchanged global selections/catalog/order and unrelated objects. A method name or projected appearance is insufficient.