@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,7 @@
1
+ // penpair: read
2
+ const input = previewInput();
3
+ const fill = input.root.fills && input.root.fills[0];
4
+ return {ok:true,rootId:input.root.id,verification:{
5
+ expected:{color:input.value,opacity:1,fillBinding:null,provenance:input.provenance},
6
+ actual:{color:fill && fill.fillColor,opacity:fill && fill.fillOpacity,fillBinding:input.root.tokens.fill || null,provenance:input.root.getSharedPluginData("penpair","projection")}
7
+ }};
@@ -0,0 +1,5 @@
1
+ // penpair: read
2
+ if (penpot.currentFile.id !== CFG.fileId || penpot.currentPage.id !== CFG.pageId) throw new Error("Confirm the owner-authorized scratch file/page IDs");
3
+ if (penpot.currentPage.root.children.length !== 0) throw new Error("Use the dedicated empty scratch page for the writer exercise");
4
+ if (typeof CFG.marker !== "string" || !CFG.marker) throw new Error("A fresh trial marker is required");
5
+ return { ok: true, fileId: penpot.currentFile.id, pageId: penpot.currentPage.id, children: 0 };
@@ -0,0 +1,8 @@
1
+ // penpair: read
2
+ const raw = penpot.currentFile.getSharedPluginData("penpair", "trial-hold-" + CFG.marker);
3
+ let record = null;
4
+ try { record = raw ? JSON.parse(raw) : null; } catch {}
5
+ return { ok: true, verification: {
6
+ expected: { fileId: CFG.fileId, pageId: CFG.pageId, children: 0, marker: CFG.marker, holdMs: Number(CFG.holdMs), state: "complete" },
7
+ actual: { fileId: penpot.currentFile.id, pageId: penpot.currentPage.id, children: penpot.currentPage.root.children.length, marker: record?.marker ?? null, holdMs: record?.holdMs ?? null, state: record?.state ?? null }
8
+ } };
@@ -0,0 +1,16 @@
1
+ // penpair: write
2
+ // penpair: idempotent
3
+ const milliseconds = Number(CFG.holdMs);
4
+ if (!Number.isInteger(milliseconds) || milliseconds < 1 || milliseconds > 30000) throw new Error("holdMs must be 1–30000");
5
+ if (!CFG.inspection || penpot.currentFile.id !== CFG.inspection.fileId || penpot.currentPage.id !== CFG.inspection.pageId || penpot.currentPage.root.children.length !== 0) throw new Error("Scratch source changed before the hold");
6
+ if (!CFG.marker) throw new Error("A fresh trial marker is required");
7
+ const key = "trial-hold-" + CFG.marker;
8
+ const prior = penpot.currentFile.getSharedPluginData("penpair", key);
9
+ if (prior) throw new Error("Trial marker already exists; inspect it before recovery");
10
+ const record = { schemaVersion: 1, state: "holding", marker: CFG.marker, fileId: CFG.fileId, pageId: CFG.pageId, holdMs: milliseconds };
11
+ penpot.currentFile.setSharedPluginData("penpair", key, JSON.stringify(record));
12
+ // This deliberate delay opens a controlled contention window. It is not layout settling.
13
+ // The operation runner owns the advisory writer lease; this body changes no design objects.
14
+ await new Promise(resolve => setTimeout(resolve, milliseconds));
15
+ penpot.currentFile.setSharedPluginData("penpair", key, JSON.stringify({ ...record, state: "complete" }));
16
+ return { ok: true, heldMilliseconds: milliseconds };
@@ -0,0 +1,23 @@
1
+ // penpair: write
2
+ // penpair: idempotent
3
+ if (!CFG.inspection || penpot.currentFile.id !== CFG.fileId || penpot.currentPage.id !== CFG.pageId) throw new Error("Scratch identity changed");
4
+ if (JSON.stringify(penpot.currentPage.root.children.map(shape => shape.id).sort()) !== JSON.stringify(CFG.inspection.rootIds)) throw new Error("Scratch source changed before the recovery exercise");
5
+ if (CFG.inspection.existingId) {
6
+ const frame = penpot.currentPage.getShapeById(CFG.inspection.existingId);
7
+ if (!frame || frame.name !== CFG.name || frame.width !== 160 || frame.height !== 80 || frame.getSharedPluginData("penpair", "trial") !== CFG.marker) throw new Error("Owned frame has an unexplained third state");
8
+ return { ok: true, alreadyBuilt: true, frameId: frame.id };
9
+ }
10
+ let frame = null, configured = false;
11
+ try {
12
+ frame = penpot.createBoard();
13
+ frame.name = CFG.name;
14
+ frame.setSharedPluginData("penpair", "trial", CFG.marker);
15
+ frame.fills = [];
16
+ frame.resize(160, 80);
17
+ configured = true;
18
+ } finally {
19
+ if (!configured && frame) frame.remove();
20
+ }
21
+ // Deliberate fault: the owned write exists, but the normal acknowledgment is not returned.
22
+ if (CFG.failAfterWrite === "true") throw new Error("Controlled trial failure after the owned write; inspect before explicit resume");
23
+ return { ok: true, frameId: frame.id };
@@ -0,0 +1,10 @@
1
+ // penpair: read
2
+ if (penpot.currentFile.id !== CFG.fileId || penpot.currentPage.id !== CFG.pageId) throw new Error("Confirm the authorized scratch file/page IDs");
3
+ if (typeof CFG.name !== "string" || !CFG.name || typeof CFG.marker !== "string" || !CFG.marker) throw new Error("A unique trial name and ownership marker are required");
4
+ const matches = penpot.currentPage.root.children.filter(shape => shape.name === CFG.name);
5
+ if (matches.length > 1) throw new Error("Recovery trial name is ambiguous");
6
+ const frame = matches[0];
7
+ const marked = penpot.currentPage.root.children.filter(shape => typeof shape.getSharedPluginData === "function" && shape.getSharedPluginData("penpair", "trial") === CFG.marker);
8
+ if (marked.length > 1 || (marked.length === 1 && marked[0] !== frame)) throw new Error("Trial ownership marker moved or duplicated; inspect before recovery");
9
+ if (frame && (typeof frame.getSharedPluginData !== "function" || frame.getSharedPluginData("penpair", "trial") !== CFG.marker)) throw new Error("Existing frame is not this trial's owned object");
10
+ return { ok: true, existingId: frame?.id ?? null, width: frame?.width ?? null, height: frame?.height ?? null, rootIds: penpot.currentPage.root.children.map(shape => shape.id).sort() };
@@ -0,0 +1,7 @@
1
+ // penpair: read
2
+ const matches = penpot.currentPage.root.children.filter(shape => shape.name === CFG.name);
3
+ const frame = matches.length === 1 ? matches[0] : null;
4
+ return { ok: true, frameId: frame?.id ?? null, verification: {
5
+ expected: { fileId: CFG.fileId, pageId: CFG.pageId, name: CFG.name, marker: CFG.marker, width: 160, height: 80 },
6
+ actual: frame ? { fileId: penpot.currentFile.id, pageId: penpot.currentPage.id, name: frame.name, marker: typeof frame.getSharedPluginData === "function" ? frame.getSharedPluginData("penpair", "trial") : null, width: frame.width, height: frame.height } : { missingOrAmbiguous: true }
7
+ } };
@@ -0,0 +1,3 @@
1
+ // penpair: read
2
+ // A benign read: the configured same-execution file guard must block this body on the other file.
3
+ return { ok: true, actualFileId: penpot.currentFile.id, actualPageId: penpot.currentPage.id };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@penpair/web",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
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": {
@@ -32,6 +32,7 @@
32
32
  "files": [
33
33
  "dist",
34
34
  "docs",
35
+ "examples",
35
36
  "LICENSE",
36
37
  "README.md"
37
38
  ],
@@ -47,7 +48,7 @@
47
48
  "access": "public"
48
49
  },
49
50
  "dependencies": {
50
- "@penpair/core": "0.1.3"
51
+ "@penpair/core": "0.3.0"
51
52
  },
52
53
  "scripts": {
53
54
  "build": "tsc -p tsconfig.json",