@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.
- package/README.md +38 -237
- package/docs/adapters.md +83 -0
- package/docs/approvals.md +19 -0
- package/docs/artifacts.md +13 -0
- package/docs/audits.md +13 -0
- package/docs/backup.md +54 -0
- package/docs/branch-review.md +59 -0
- package/docs/build-kit.md +59 -0
- package/docs/command-contract.md +27 -0
- package/docs/compatibility.md +46 -0
- package/docs/conventions/README.md +63 -0
- package/docs/conventions/completion-2026-10-22.md +73 -0
- package/docs/conventions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/docs/conventions/plugin-code.md +23 -0
- package/docs/conventions/structure.md +48 -0
- package/docs/conventions/templates/adapter-command.ts +28 -0
- package/docs/conventions/templates/conventions.test.ts +81 -0
- package/docs/conventions/templates/frame-inspection.ts +27 -0
- package/docs/conventions/templates/frame-label.ts +15 -0
- package/docs/conventions/tsconfig.json +13 -0
- package/docs/conventions/typescript.md +59 -0
- package/docs/conventions/vitest.config.ts +11 -0
- package/docs/cookbook.md +25 -0
- package/docs/coverage.md +50 -0
- package/docs/credentials.md +27 -0
- package/docs/design-workflow.md +13 -0
- package/docs/extensions-trust.md +13 -0
- package/docs/fixtures/browser-capture-2026-10-22.json +217 -0
- package/docs/fixtures/feedback-reproductions.md +12 -0
- package/docs/fixtures/feedback-scratch.template.json +9 -0
- package/docs/formats.md +22 -0
- package/docs/getting-started.md +12 -2
- package/docs/guide.md +83 -9
- package/docs/handoff.md +34 -0
- package/docs/improvements.md +1217 -0
- package/docs/inspection.md +38 -0
- package/docs/layout-settling.md +13 -0
- package/docs/marwes-components.md +34 -0
- package/docs/marwes-tokens.md +59 -2
- package/docs/marwes.md +9 -0
- package/docs/mcp.md +40 -0
- package/docs/mode-verification.md +35 -0
- package/docs/plans/registry-polling-fix.md +101 -0
- package/docs/plans/tradient-feedback-0.2.0.md +209 -0
- package/docs/plans/tradient-feedback-review.md +83 -0
- package/docs/pr-improvements.md +57 -0
- package/docs/recovery.md +33 -0
- package/docs/releasing.md +172 -22
- package/docs/run-file.md +71 -0
- package/docs/shots.md +15 -0
- package/docs/skill-installation.md +16 -0
- package/docs/snapshots.md +20 -0
- package/docs/status.md +7 -0
- package/docs/team.md +17 -0
- package/docs/theme-previews.md +18 -0
- package/docs/token-editing.md +22 -0
- package/docs/tradient-followups.md +77 -0
- package/docs/trials/report-template.json +76 -0
- package/docs/trials.md +41 -0
- package/docs/troubleshooting.md +26 -0
- package/docs/upstream/marwes-1.11.0-evidence.json +15 -0
- package/docs/upstream/marwes-requests.md +35 -0
- package/examples/component-pilot/renderer.mjs +100 -0
- package/examples/coverage-matrix.json +126 -0
- package/examples/hello-card/README.md +18 -0
- package/examples/hello-card/build.js +24 -0
- package/examples/hello-card/helpers.js +27 -0
- package/examples/hello-card/inspect.js +4 -0
- package/examples/hello-card/readback.js +3 -0
- package/examples/reflection-browser/README.md +49 -0
- package/examples/reflection-browser/capture.mjs +311 -0
- package/examples/reflection-browser/fixture/Abel-Regular.ttf +0 -0
- package/examples/reflection-browser/fixture/OFL.txt +93 -0
- package/examples/reflection-browser/fixture/index.html +11 -0
- package/examples/reflection-browser/fixture-hooks.mjs +24 -0
- package/examples/reflection-browser/provider.mjs +90 -0
- package/examples/reflection-browser/serve.mjs +37 -0
- package/examples/theme-preview/README.md +21 -0
- package/examples/theme-preview/apply.js +8 -0
- package/examples/theme-preview/helpers.js +12 -0
- package/examples/theme-preview/inspect.js +3 -0
- package/examples/theme-preview/readback.js +7 -0
- package/examples/trials/hold-inspect.js +5 -0
- package/examples/trials/hold-readback.js +8 -0
- package/examples/trials/hold-writer.js +16 -0
- package/examples/trials/recovery-build.js +23 -0
- package/examples/trials/recovery-inspect.js +10 -0
- package/examples/trials/recovery-readback.js +7 -0
- package/examples/trials/wrong-file-read.js +3 -0
- package/package.json +3 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Build kit and recipes
|
|
2
|
+
|
|
3
|
+
`penpair run-file ... --include @kit` defines the neutral plugin namespace `pp`. The library exports the same plain JavaScript text as `kit()`; concatenate it into a guarded snippet rather than importing Node code into the plugin. Inclusion defines functions and recipes without touching the document.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
penpair kit check
|
|
7
|
+
penpair recipes
|
|
8
|
+
penpair kit check --include @kit --include @marwes-kit
|
|
9
|
+
penpair recipes --include @kit --include @marwes-kit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The last two examples require the token adapter configured in the adopting project. A normal core check/list or `@kit` include does not import unrelated adapters. Explicit extension aliases load configured trusted adapter code to discover their source. Dependencies must appear earlier in include order; ambiguous aliases or recipe names refuse. Each kit is at most 32 KiB and combined kit source at most 48 KiB, leaving room within the final wrapped 90 KB snippet guard. Reports retain bytes and source hashes.
|
|
13
|
+
|
|
14
|
+
## Neutral helpers
|
|
15
|
+
|
|
16
|
+
| Helper | Contract |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `tok(name)` | Reads current active sets in catalog order; later active sets win. Missing names and duplicates inside a set refuse. No inactive-set fallback or persistent cache. |
|
|
19
|
+
| `bind(shape, name, properties)` | Queues explicit token-property bindings for `flush`; properties are token binding keys, not arbitrary JavaScript shape fields. |
|
|
20
|
+
| `box(parent, options)` | Makes a flex board; `gap` and four-sided `pad` accept spacing token names or unbound zero. `w`/`h` are explicit geometry. `bg`/`stroke` are color tokens and `radius` is a radius token. |
|
|
21
|
+
| `text(parent, value, options)` | Requires nonempty characters, `typo` and `color` tokens; preflights an exact font family/weight/style and applies the face before bindings. Use `wrap`, `fill`, `grow`, `align` and explicit `font` where needed. |
|
|
22
|
+
| `icon(parent, kind, color, size)` | Square wrapper around a uniformly scaled path on a 24-unit drawing grid. Built-ins: chevron, check, alert, calendar. A custom glyph supplies `path` and `[x,y,width,height]` bounds. |
|
|
23
|
+
| `dot`, `rule` | Small geometric helpers with explicit color tokens. Intrinsic one-pixel rules/strokes are geometry, not inferred library measurements. |
|
|
24
|
+
| `inst(parent, path, name, textOverrides, library?)` | Exact unique component match; validates named text overrides and never removes/reorders instance children. Defaults to the local library. |
|
|
25
|
+
| `style(shape, options)` | Queues requested styling only; an isolated color update preserves existing spacing and bindings. |
|
|
26
|
+
| `fill`, `fillV` | Sets layout-child horizontal/vertical fill sizing. |
|
|
27
|
+
| `settle(root, options)` | `strategy: auto | native | geometry`, default auto. Auto caps native at min(500 ms, remaining/4), then requires three complete equal geometry/hierarchy samples. Time/node/malformed evidence cannot report stable. |
|
|
28
|
+
| `flush({root, timeoutMs, strategy})` | Waits for layout, applies queued values/styles, then awaits queued bindings, then settles again. Active token-context changes refuse. |
|
|
29
|
+
|
|
30
|
+
Always `await pp.flush()` before independent readback/export. There are no fixed 1.2/2.5-second styling sleeps. Layout completion does not prove font download, token persistence or pixels; keep positive readback and stable exports. Queues are local to one execution, not a recovery log. Partial errors are not transactions; the enclosing reviewed operation must own and reconcile its roots. Bindings remain subject to the runtime's supported token properties; A4 owns the broader compatibility whitelist.
|
|
31
|
+
|
|
32
|
+
Example body (inside a reviewed `run-file` operation):
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const card = pp.recipe("hello-card", ownedRoot, {
|
|
36
|
+
box: { name: "Hello card", w: 240, gap: CFG.space, pad: [CFG.space, CFG.space, CFG.space, CFG.space], stroke: CFG.color, radius: CFG.radius },
|
|
37
|
+
text: { typo: CFG.typo, color: CFG.color },
|
|
38
|
+
label: "Hello, card",
|
|
39
|
+
});
|
|
40
|
+
await pp.flush({ root: ownedRoot });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A recipe is registered with `pp.defineRecipe(name, (parent, props) => shape, description)` and invoked through `pp.recipe`. Recipe source must remain synchronous at construction time; asynchronous binding/layout work finishes in `flush`. See the [complete repeatable example](../examples/hello-card/README.md).
|
|
44
|
+
|
|
45
|
+
## Adapter recipes
|
|
46
|
+
|
|
47
|
+
`@marwes-kit`, included after `@kit`, defines `mw.card`, `mw.segmented`, `mw.progress`, `mw.iconTile` and `mw.statTile`, and registers corresponding recipe names. Library token defaults live entirely in the adapter; every token can be retargeted through props. Card/progress/tile radius tokens are explicit project declarations or measured inputs. Progress additionally requires an explicit **sizing** height token, since spacing tokens do not bind height. Tile dimensions and content are explicit.
|
|
48
|
+
|
|
49
|
+
These are compositional starting points. They are not generated native component variants or measured parity claims; C1 owns that calibration. Missing library defaults fail visibly rather than producing silently unbound styling.
|
|
50
|
+
|
|
51
|
+
## Opt-in scratch probe
|
|
52
|
+
|
|
53
|
+
`penpair probe --deep --kit --write` adds a journaled kit trial to the ordinary deep probe. Use an owner-authorized scratch file with active color, typography, spacing and radius tokens and the typography's font available. The trial builds a small “Hello, card” under a directly owned temporary root, observes text/bindings/geometry, exports matching renders, removes only that root, and independently compares the original page/token state. Unsupported context refuses; failed export still attempts owned cleanup. Explicit resume is allowed only against the original source and clean/reconciled state.
|
|
54
|
+
|
|
55
|
+
A verified trial saves a private PNG preview under `.penpair/probes/`, with byte hash and capture identity. Look at the preview as the write protocol requires. `visualApproval: false` remains explicit. Redacted/incomplete evidence cannot publish a preview; an existing differing preview is preserved. No new live pinned-version trial has been performed in this branch.
|
|
56
|
+
|
|
57
|
+
Current references: [flex layout](https://doc.plugins.penpot.app/interfaces/FlexLayout), [font application](https://doc.plugins.penpot.app/interfaces/Font), [component instantiation](https://doc.plugins.penpot.app/interfaces/LibraryComponent), [spacing properties](https://doc.plugins.penpot.app/types/TokenSpacingProps) and [sizing properties](https://doc.plugins.penpot.app/types/TokenSizingProps). Runtime capability and independent evidence remain required.
|
|
58
|
+
|
|
59
|
+
Host operations retain a 30 s total layout deadline; kit settle/flush default to 10 s. `native` reports failure without fallback; `geometry` skips native waiting. Diagnostics include the chosen strategy, native outcome, fallback reason and elapsed time. Neither transport loss, cancellation, changed target nor malformed/truncated data becomes a successful fallback. `run-file`, `export` and `shots` accept `--layout auto|native|geometry`. The default is suitable for ordinary builds; there is no CLI off mode.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Command results for scripts and tools
|
|
2
|
+
|
|
3
|
+
`--json` emits one schema-versioned stdout document for core commands and the bundled adapter. Human output remains available without it. This replaces earlier command-specific JSON roots: read `data.ok` and `data.checks` for doctor, and `data.valid` for config check. CLI progress and diagnostics go to stderr; diagnostics are also included in the result.
|
|
4
|
+
|
|
5
|
+
The outer schema is exported as `commandResultSchema`, with the TypeScript `CommandResult` type. It contains `schemaVersion: 1`, command name, outcome, phase, exit code, observed target IDs when available, run ID when available, command-owned `data`, human messages, warnings, structured errors with remedies, completeness and timings. An absent target is `null`, never inferred from a file name. Do not decide success from empty errors alone: check outcome, exitCode, completeness and the command's verification fields.
|
|
6
|
+
|
|
7
|
+
| Exit | Outcome | Meaning |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 0 | success | Command completed; an inspect/preview still does not mean it applied or visually approved a change. |
|
|
10
|
+
| 1 | failed | Validation, connection, verification or command failure. Error codes distinguish configuration and transport problems. |
|
|
11
|
+
| 2 | drift | A valid comparison found differences or a stale snapshot. |
|
|
12
|
+
| 3 | incomplete | Required output is missing, truncated or unstable. |
|
|
13
|
+
| 4 | uncertain | A possible write lacks a confirmed outcome. Inspect the run before resuming. |
|
|
14
|
+
| 124 | deadline | The deadline interrupted work without a delivered write being classified uncertain. |
|
|
15
|
+
| 130 | cancelled | Cancellation interrupted work without a delivered write being classified uncertain. |
|
|
16
|
+
|
|
17
|
+
Tree JSON retains its truncation flag, and truncated tree output exits 3 in both formats. Baselines cannot pass with missing/unstable captures. A standalone unstable PNG export preserves its previous output and exits 3. Adapter status marks unavailable Penpot inspection incomplete instead of reporting a complete successful status.
|
|
18
|
+
|
|
19
|
+
Use `--timeout-ms <1..1800000>` for an overall CLI deadline. Each MCP request also retains a 120-second SDK timeout. SIGINT/SIGTERM abort requests and waits; SDK cancellation does not prove that plugin execution stopped. An interruption after a write was delivered therefore remains uncertain (4), with journal evidence retained. There is no forced rollback or detached background continuation. Synchronous extension code must cooperate; a JavaScript deadline cannot preempt a blocking synchronous module. Interactive setup/connect require a terminal, refuse JSON mode, and do not accept this deadline flag. Run doctor in JSON mode afterwards.
|
|
20
|
+
|
|
21
|
+
Library callers can use `runCommand(args, { signal })` to receive the same result without printing it; it enables command JSON data internally. The stdio MCP server uses this service boundary. Supply `runCommand(args, {root, signal})` for request-scoped projects; the process working directory remains unchanged. CLI calls default to the current project. Connection-specific redactors sanitize data, errors and diagnostic boundaries, including known bare key values. Never provide keys in arguments.
|
|
22
|
+
|
|
23
|
+
Adapters should write through `context.output.log/error/result` and reserve `context.log` for progress. `result(value)` sets their authoritative structured data. Existing third-party adapters that write directly to global console/process streams must migrate to this output contract; arbitrary imported code is not sandboxed or intercepted. Unsupported or ignored command flags are refused before execution, including write flags on read commands and invalid numeric budgets.
|
|
24
|
+
|
|
25
|
+
MCP transport accepts an executor envelope `{ result, log? }` in `structuredContent`, or the equivalent JSON text envelope for older servers. Structured content takes precedence and must validate; malformed structured data cannot fall back to a reassuring legacy text response. The SDK continues to handle its advertised schema checks. This is executor-envelope validation, not proof that an arbitrary snippet measured the state correctly. See the [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
|
|
26
|
+
|
|
27
|
+
Explicit mode trials may use a separate cooperative ten-second restoration deadline after main-command cancellation. The command remains cancelled/uncertain where applicable; cleanup does not establish a successful measurement. See [mode verification](mode-verification.md).
|
package/docs/compatibility.md
CHANGED
|
@@ -27,3 +27,49 @@ What has been checked, where, and what differs. Update it whenever a Penpot is p
|
|
|
27
27
|
- Paid plans, and whether the free plan keeps the remote MCP.
|
|
28
28
|
- Two people in the same hosted file while an agent writes.
|
|
29
29
|
- Penpot's own version history as a backup for an external project.
|
|
30
|
+
|
|
31
|
+
## Automated checks and manual scratch smoke
|
|
32
|
+
|
|
33
|
+
The local suite includes a real HTTP fake MCP server: protocol negotiation, tool discovery, authentication rejection/redaction, read-only suspension retry, write acknowledgment loss and malformed replies. Generated normalization invariants exercise color case, font-list storage and typography normalization. These are transport/storage tests, not evidence of new live Penpot compatibility.
|
|
34
|
+
|
|
35
|
+
After `pnpm build`, an owner can run `pnpm smoke:live --scratch-file <disposable-file-id> --origin <expected-origin> --expected-version <x.y.z> --confirm-scratch`. Set `PENPAIR_MCP_URL` in the environment (never as a command argument or committed value). The harness refuses missing confirmation, wrong instance/file and version drift before the mutating probe. It uses the existing tagged deep check and independently verifies cleanup. Every required step must be observed; empty reports cannot pass. An uncertain write is never replayed automatically; use the tag/run ID to inspect leftovers.
|
|
36
|
+
|
|
37
|
+
The `Scratch Penpot compatibility smoke` workflow is manual-dispatch only. Configure a protected `penpot-smoke` environment and its `PENPAIR_SMOKE_MCP_URL` secret, then open the disposable file and keep its authenticated editor/plugin connected. CI cannot create that browser session from a key alone. The workflow stores a redacted text report; update the live compatibility table only after reviewing an actual run. A scheduled nightly hosted job remains deferred until editor-session lifecycle is solved. The first live run of this harness is still outstanding; no hosted/local version gained a new verified check from the unit tests.
|
|
38
|
+
|
|
39
|
+
## Capability-driven native APIs
|
|
40
|
+
|
|
41
|
+
`probe` now emits report `schemaVersion: 1`, the observed Penpot API version, target IDs, observation time, and presence of layout wait, integrity validation, version-history and markup/style/font helpers. Presence is not a behavior test. `probe --integrity` invokes `File.validate()` only when supported; unsupported is explicit, issues return `valid: false` and a failing CLI status, and thrown/malformed API results do not count as valid. The ordinary probe and doctor do not create versions or run write-capability tests.
|
|
42
|
+
|
|
43
|
+
Library helpers accept the project's guarded client:
|
|
44
|
+
|
|
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.
|
|
46
|
+
- `validateFileIntegrity(client)`: distinguishes unsupported from a valid empty issue list or actual referential-integrity problems.
|
|
47
|
+
- `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
|
+
|
|
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.
|
|
50
|
+
|
|
51
|
+
## Hosted archive candidate
|
|
52
|
+
|
|
53
|
+
`backup file` requests the documented `currentFile.export("penpot", "all")` signature, bounds the returned payload and refuses to accept an archive if file identity/revision changed. Simulated tests do not resolve the observed live export failure or establish import fidelity. Follow the [scratch round-trip checklist](backup.md#prove-the-restore-on-scratch) before marking this path proven; the live table remains unchanged.
|
|
54
|
+
|
|
55
|
+
## Build-brief observations reviewed 2026-10-21
|
|
56
|
+
|
|
57
|
+
Scope: hosted Penpot 2.18.4, as reported in the design-build brief. These reports were incorporated into the shipped [plugin gotchas reference](../packages/core/skills/penpair-design/references/penpot-plugin-gotchas.md#build-report-hosted-penpot-2184-reviewed-2026-10-21); they were not independently rerun on this branch and do not add passes to the live matrix.
|
|
58
|
+
|
|
59
|
+
| Reported area | Workaround or verification requirement |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Stretch row nested in a hugging parent | Try start alignment plus intentional explicit child height; inspect actual geometry. |
|
|
62
|
+
| Fill child changes parent hug sizing | Reassert auto only when hug is the intended parent contract; read back sizing. |
|
|
63
|
+
| Fixed sizing leaves the 100 px default | Resize to explicit intended dimensions. |
|
|
64
|
+
| Empty characters rejected | Omit the node or deliberately use a non-breaking space. |
|
|
65
|
+
| `fillOpacity` cannot be assigned | Use whole-shape opacity or the fill array; verify after token binding. |
|
|
66
|
+
| Token formulas and adding a set to an existing theme | Inspect resolved values under the intended active theme; portability is a separate question. |
|
|
67
|
+
| Instance children cannot be freely removed/reordered | Match structural variants; verify supported overrides. |
|
|
68
|
+
| Layout padding setters differ from token binding keys | Keep setter and binding names distinct. |
|
|
69
|
+
| Component creation splits path/name and can race saved bindings | Use the local library API and observe bindings before creation. |
|
|
70
|
+
| A font token alone did not guarantee the exported face | Find/apply the actual font variant and review the export. |
|
|
71
|
+
| Square resizing distorts a non-square path | Preserve aspect ratio inside a fixed-size wrapper. |
|
|
72
|
+
| Layout/export takes roughly 10–30 seconds to settle | Bounded layout evidence plus independent stable exports; no fixed-delay proof. |
|
|
73
|
+
| Hidden editor loses heartbeat, observed after 6–8 minutes | Keep a separate visible editor window; retry only declared reads and inspect uncertain writes. |
|
|
74
|
+
|
|
75
|
+
The existing doctor remedy already says to keep the tab visible. Its text now also suggests a separate window; it does not predict the browser's suspension deadline. Recipe enforcement will be delivered with the kit. Native/fallback layout behavior on live pinned versions remains to be checked. The documentation does not imply those later items are implemented.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Code conventions
|
|
2
|
+
|
|
3
|
+
Start with the row matching the change. Copy the relevant pattern, then use the
|
|
4
|
+
nearest production module's domain vocabulary. The examples compile and run in
|
|
5
|
+
`pnpm conventions:check`; production code never imports them.
|
|
6
|
+
|
|
7
|
+
The [completion report](completion-2026-10-22.md) records the initial rollout,
|
|
8
|
+
resolved problems, verification and limits.
|
|
9
|
+
|
|
10
|
+
| Work | Read | Checked example |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Choose a package, folder or module boundary | [Structure](structure.md) | [Comparison adapter](../../packages/reflection-check/src/index.ts) |
|
|
13
|
+
| Write a helper, contract or test | [TypeScript](typescript.md) | [Frame label](templates/frame-label.ts), [tests](templates/conventions.test.ts) |
|
|
14
|
+
| Add an adapter command | [TypeScript](typescript.md), [structure](structure.md) | [Offline command](templates/adapter-command.ts) |
|
|
15
|
+
| Generate code for the Penpot sandbox | [Plugin code](plugin-code.md) | [Read-only snippet](templates/frame-inspection.ts) |
|
|
16
|
+
| Change release or workspace scripts | [Structure](structure.md), [releasing](../releasing.md) | Existing sibling in `scripts/` |
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm format # Format supported source/configuration files
|
|
20
|
+
pnpm lint:fix # Apply safe lint fixes
|
|
21
|
+
pnpm check:style # Check formatting, focused lint rules and code directory names
|
|
22
|
+
pnpm conventions:check # Typecheck and execute these examples
|
|
23
|
+
pnpm check # Style, build, typecheck, examples, tests and version consistency
|
|
24
|
+
pnpm smoke:package # Verify the packaged installation shapes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`biome.json` is the formatting authority: two spaces, LF, a 100-column target,
|
|
28
|
+
double quotes, semicolons, expanded object/array layouts and parenthesized arrow
|
|
29
|
+
parameters. Braces and one variable per declaration keep control flow readable.
|
|
30
|
+
Use the editor's Biome extension with the repository's pinned version if desired;
|
|
31
|
+
`.editorconfig` supplies the basic whitespace settings in other editors.
|
|
32
|
+
|
|
33
|
+
Biome checks filenames; `check-code-paths.mjs` checks directories containing code
|
|
34
|
+
under `packages/`, `scripts/`, `examples/` and this folder, including new untracked
|
|
35
|
+
files. Both run in CI through `pnpm check`. Human/agent review still checks whether
|
|
36
|
+
names describe intent and whether responsibilities should be split. A formatter
|
|
37
|
+
cannot judge those decisions.
|
|
38
|
+
|
|
39
|
+
Vertical spacing is also an author/review responsibility: Biome keeps intentional
|
|
40
|
+
blank lines, but does not add separation between declarations or logical steps.
|
|
41
|
+
Follow [TypeScript spacing guidance](typescript.md) even when `check:style` passes.
|
|
42
|
+
|
|
43
|
+
The [repository skill](../../.agents/skills/penpair-code-conventions/SKILL.md)
|
|
44
|
+
applies this workflow to implementation and refactoring work. It develops Penpair
|
|
45
|
+
itself; the separately shipped `packages/core/skills/` teach consumers how to use
|
|
46
|
+
Penpair with their designs.
|
|
47
|
+
|
|
48
|
+
## Coverage and exceptions
|
|
49
|
+
|
|
50
|
+
- Generated `dist/`, dependencies, ignored local artifacts and data fixtures are
|
|
51
|
+
outside formatting. Keep captured evidence unchanged.
|
|
52
|
+
- `docs/trials/` and `docs/upstream/` preserve external evidence.
|
|
53
|
+
- Raw plugin bodies in `examples/hello-card/`, `examples/theme-preview/` and
|
|
54
|
+
`examples/trials/` may contain a top-level `return`, which Biome's module parser
|
|
55
|
+
rejects. Their existing sandbox tests compile them as async function bodies.
|
|
56
|
+
Follow [plugin code](plugin-code.md) when editing them.
|
|
57
|
+
- JavaScript inside template strings is string data to Biome. Format that source
|
|
58
|
+
deliberately and test the generated snippet. Do not run blind whitespace
|
|
59
|
+
rewriting over strings, serialized snapshots or embedded protocols.
|
|
60
|
+
|
|
61
|
+
Add new file-type examples when an actual feature needs a pattern. Keep them
|
|
62
|
+
minimal and executable; do not introduce a framework or dependency just for a
|
|
63
|
+
template.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Readability conventions completion — 2026-10-22
|
|
2
|
+
|
|
3
|
+
Branch: `codex/tradient-feedback`. All repository package versions remain 0.2.0.
|
|
4
|
+
|
|
5
|
+
## Completed checklist and what each update resolves
|
|
6
|
+
|
|
7
|
+
- [x] **Readable formatting baseline.** Pin Biome 2.6.2, use two-space indentation
|
|
8
|
+
and expanded layouts, add braces and separate variable declarations. This
|
|
9
|
+
removes compressed source layouts and makes subsequent diffs consistent. The
|
|
10
|
+
mechanical baseline is its own commit, `b1a9445`, for review.
|
|
11
|
+
- [x] **Maintained conventions.** Add task-based guidance for naming, package
|
|
12
|
+
ownership, module extraction, TypeScript, tests and sandbox code. This gives new
|
|
13
|
+
work a concrete placement and implementation pattern instead of relying on
|
|
14
|
+
memory or copying unrelated frontend conventions.
|
|
15
|
+
- [x] **Checked examples.** Typecheck actual core contracts and run helper,
|
|
16
|
+
sandbox-generation and path-rule tests. This catches stale examples and proves
|
|
17
|
+
that the generated plugin body compiles and behaves as documented.
|
|
18
|
+
- [x] **Agent discovery.** Link the conventions and repository-local
|
|
19
|
+
`penpair-code-conventions` skill from `AGENTS.md`, with contributor routing from
|
|
20
|
+
`CONTRIBUTING.md`. This makes readable names and early responsibility extraction
|
|
21
|
+
part of the implementation workflow.
|
|
22
|
+
- [x] **Continuous enforcement.** Include Biome, directory naming and example
|
|
23
|
+
validation in `pnpm check`, which existing CI already runs. The CI job name stays
|
|
24
|
+
stable for branch protection. These checks prevent automatic style/path drift;
|
|
25
|
+
review still judges the meaning of names and module boundaries.
|
|
26
|
+
- [x] **Comparison adapter extraction.** Replace the dense entrypoint with focused
|
|
27
|
+
public contracts, planning, capture validation, provider validation, PNG I/O,
|
|
28
|
+
orchestration and command modules. Public imports, report fields, approval gates
|
|
29
|
+
and command behavior remain compatible. Shared test fixtures and capture tests
|
|
30
|
+
also have focused files.
|
|
31
|
+
- [x] **Installed contract verification.** Extend package smoke testing to import
|
|
32
|
+
the packed adapter's public functions/default export and typecheck its public
|
|
33
|
+
contracts. This catches missing extracted files or broken declaration exports.
|
|
34
|
+
|
|
35
|
+
## Validation
|
|
36
|
+
|
|
37
|
+
- `pnpm check` passes: **640 package tests plus 3 convention tests**, style/path
|
|
38
|
+
checks, builds, typechecks and the six-package version guard.
|
|
39
|
+
- `pnpm smoke:package` passes for all **six tarballs**, including the new runtime
|
|
40
|
+
export and declaration checks.
|
|
41
|
+
- The comparison package's tests/helpers also pass a separate strict TypeScript
|
|
42
|
+
check. Skill YAML metadata, UI metadata and local convention/skill links resolve.
|
|
43
|
+
- Before the manual extraction, an AST comparison across 199 changed
|
|
44
|
+
JavaScript/TypeScript files found no differences beyond formatting, equivalent
|
|
45
|
+
control-flow blocks and separate declarations. The initial formatted baseline
|
|
46
|
+
passed all 630 pre-existing tests.
|
|
47
|
+
- `git diff --check` passes. A patch Changeset records the delivered refactor and
|
|
48
|
+
documentation. No version bump, push, publication or live Penpot write occurred.
|
|
49
|
+
|
|
50
|
+
## Limits
|
|
51
|
+
|
|
52
|
+
Biome cannot determine whether a name expresses the right domain meaning or when
|
|
53
|
+
responsibilities deserve separate modules; the skill and review cover those
|
|
54
|
+
decisions. Raw async plugin bodies, JavaScript inside strings, generated artifacts
|
|
55
|
+
and captured fixtures retain the handling described in the [conventions index](README.md).
|
|
56
|
+
This update does not constitute new live Penpot or browser acceptance. The
|
|
57
|
+
[existing pending acceptance](../plans/tradient-feedback-review.md#remaining-acceptance)
|
|
58
|
+
stays open.
|
|
59
|
+
|
|
60
|
+
## Vertical spacing correction
|
|
61
|
+
|
|
62
|
+
- [x] Separate declarations and individual steps in the component pilot module.
|
|
63
|
+
- [x] Clarify that Biome retains author-supplied blank lines but does not insert
|
|
64
|
+
the separation required by the conventions.
|
|
65
|
+
|
|
66
|
+
Resolved: `packages/marwes-ui/src/components.ts` passed Biome while remaining
|
|
67
|
+
visually dense. The file now has blank lines between its contracts, functions,
|
|
68
|
+
guards and work steps. The diff contains only added blank lines, and Biome keeps
|
|
69
|
+
them. `pnpm check:style` and all three existing component tests pass. No behavior
|
|
70
|
+
changed; the full suite was not rerun for this whitespace-only follow-up.
|
|
71
|
+
|
|
72
|
+
Automatic vertical-spacing enforcement remains outside the current Biome checks;
|
|
73
|
+
the conventions and code review require it explicitly.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"3.2.4","results":[[":templates/conventions.test.ts",{"duration":5.28132800000003,"failed":false}]]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Penpot plugin source
|
|
2
|
+
|
|
3
|
+
Node-side TypeScript and the Penpot editor sandbox are different environments.
|
|
4
|
+
The sandbox has `penpot` and explicit injected values such as `CFG`; it cannot
|
|
5
|
+
import Node filesystem modules or reach local project paths. Keep Node I/O outside
|
|
6
|
+
the generated source. Use the neutral kit/snippet APIs and actual upstream names.
|
|
7
|
+
|
|
8
|
+
Raw plugin files are async function bodies and may return at the top level.
|
|
9
|
+
`*.ts` generators are ordinary Node modules and are formatted by Biome; their
|
|
10
|
+
template string contents need deliberate formatting. Prefer readable multiline
|
|
11
|
+
source with descriptive names and braces. Preserve string values and parameter
|
|
12
|
+
serialization when editing whitespace.
|
|
13
|
+
|
|
14
|
+
The [frame inspection example](templates/frame-inspection.ts) shows a small
|
|
15
|
+
read-only generator. Its tests compile the generated body and execute it against
|
|
16
|
+
a sandbox stub. This catches syntax and behavior errors that formatting the
|
|
17
|
+
outer TypeScript module cannot catch.
|
|
18
|
+
|
|
19
|
+
For writes, follow [the write protocol](../../packages/core/skills/penpair-design/references/write-protocol.md):
|
|
20
|
+
inspect, pin a snapshot, perform one idempotent write, independently read back,
|
|
21
|
+
then export and look. Preserve file/page guards, bounded layout settling and the
|
|
22
|
+
under-90-KB transport limit. A syntax test does not prove live Penpot behavior.
|
|
23
|
+
Tests may stub read-only sandbox objects; live acceptance remains separate.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Folder and module structure
|
|
2
|
+
|
|
3
|
+
Place code with the responsibility that owns it. The dependency direction is
|
|
4
|
+
command packages/adapters → core; the core never imports an adapter or names a
|
|
5
|
+
consumer project or component library.
|
|
6
|
+
|
|
7
|
+
| Location | Owns |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `packages/core/src/` | Neutral engine, validation, transport and public extension contracts |
|
|
10
|
+
| `packages/web/`, `packages/docker/`, `packages/penpair/` | Command distribution and profile-specific lifecycle |
|
|
11
|
+
| `packages/<adapter>/src/` | Library/tool knowledge and its adapter commands |
|
|
12
|
+
| `packages/<package>/test/` | That package's behavior and integration contracts |
|
|
13
|
+
| `scripts/` and `scripts/lib/` | Workspace/release tooling and reusable script helpers |
|
|
14
|
+
| `examples/<feature>/` | Consumer-facing workflows and explicit sample project knowledge |
|
|
15
|
+
| `packages/core/skills/` | Shipped consumer workflows |
|
|
16
|
+
| `.agents/skills/` | Skills for developing this repository |
|
|
17
|
+
| `docs/conventions/templates/` | Checked examples, never runtime dependencies |
|
|
18
|
+
|
|
19
|
+
Use kebab-case for new code directories and filenames. Prefer a domain name such
|
|
20
|
+
as `capture-validation.ts` over `helpers.ts`, `utils.ts` or `manager.ts`. Add a
|
|
21
|
+
feature folder when several related files form one cohesive area; keep small
|
|
22
|
+
features as siblings rather than adding a directory around every file. Preserve
|
|
23
|
+
tool-required names such as `AGENTS.md`, `SKILL.md` and `README.md`.
|
|
24
|
+
|
|
25
|
+
## Split as soon as a responsibility stands on its own
|
|
26
|
+
|
|
27
|
+
Extract a responsibility when it has its own inputs/outputs, invariants or useful
|
|
28
|
+
tests. Do this in the change that introduces it. Typical splits are public types,
|
|
29
|
+
argument parsing/provider loading, pure planning, evidence validation and image
|
|
30
|
+
I/O. Keep a package's public `index.ts` focused on exports and adapter wiring.
|
|
31
|
+
Preserve public imports when moving implementations.
|
|
32
|
+
|
|
33
|
+
The comparison adapter demonstrates this shape: `case-plan.ts` plans approved
|
|
34
|
+
cases, `evidence-validation.ts` validates observed state, `capture-validation.ts`
|
|
35
|
+
checks browser geometry, `png-evidence.ts` reads bounded images, `run-cases.ts`
|
|
36
|
+
orchestrates evidence collection, and `command.ts` handles CLI/provider loading.
|
|
37
|
+
|
|
38
|
+
Do not split tightly coupled steps solely to make files short. Avoid one-line
|
|
39
|
+
forwarders and a forest of generic folders. Repeated domain code, mixed I/O and
|
|
40
|
+
validation, a long boolean condition, or unrelated reasons for a file to change
|
|
41
|
+
are stronger signals than a line count. Around 200 lines in a module or 40 lines
|
|
42
|
+
in a function is a review prompt, not an automatic failure or a reason to hide
|
|
43
|
+
complexity in another large helper.
|
|
44
|
+
|
|
45
|
+
Before extracting, identify the public contract and the tests that protect it.
|
|
46
|
+
After extracting, read the orchestration from top to bottom: names should explain
|
|
47
|
+
the workflow without opening every helper. Keep shared types below implementations
|
|
48
|
+
in the dependency direction and avoid circular imports or unnecessary barrels.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AdapterCommand, AdapterContext } from "../../../packages/core/src/adapters.js";
|
|
2
|
+
|
|
3
|
+
async function runExampleStatus(
|
|
4
|
+
context: AdapterContext,
|
|
5
|
+
commandArguments: string[],
|
|
6
|
+
): Promise<number> {
|
|
7
|
+
if (commandArguments.length > 0) {
|
|
8
|
+
throw new Error("Usage: example-status");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const report = {
|
|
12
|
+
configured: Boolean(context.config.file),
|
|
13
|
+
};
|
|
14
|
+
context.output?.result(report);
|
|
15
|
+
context.log(JSON.stringify(report));
|
|
16
|
+
|
|
17
|
+
return report.configured ? 0 : 3;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Register in an adapter's defineAdapter({ commands: [...] }) when adopting this pattern.
|
|
21
|
+
export const exampleStatusCommand: AdapterCommand = {
|
|
22
|
+
name: "example-status",
|
|
23
|
+
summary: "Report offline file-pin readiness",
|
|
24
|
+
usage: [
|
|
25
|
+
"penpair example-status",
|
|
26
|
+
],
|
|
27
|
+
run: runExampleStatus,
|
|
28
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { compiles } from "../../../packages/core/src/snippet.js";
|
|
3
|
+
import { findInvalidCodeDirectories } from "../../../scripts/check-code-paths.mjs";
|
|
4
|
+
import { formatFrameLabel } from "./frame-label.js";
|
|
5
|
+
import { createFrameInspection } from "./frame-inspection.js";
|
|
6
|
+
|
|
7
|
+
describe("convention examples", () => {
|
|
8
|
+
it("normalizes names and refuses incomplete labels", () => {
|
|
9
|
+
expect(
|
|
10
|
+
formatFrameLabel({
|
|
11
|
+
pageName: " Dashboard ",
|
|
12
|
+
frameName: " Empty ",
|
|
13
|
+
}),
|
|
14
|
+
).toBe("Dashboard / Empty");
|
|
15
|
+
expect(() =>
|
|
16
|
+
formatFrameLabel({
|
|
17
|
+
pageName: " ",
|
|
18
|
+
frameName: "Empty",
|
|
19
|
+
}),
|
|
20
|
+
).toThrow("requires");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("compiles and executes the generated read-only sandbox body", async () => {
|
|
24
|
+
const inspection = createFrameInspection("Empty");
|
|
25
|
+
expect(compiles(inspection.code)).toBeUndefined();
|
|
26
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
27
|
+
const executeInspection = new AsyncFunction("penpot", inspection.code);
|
|
28
|
+
const frame = {
|
|
29
|
+
id: "frame-1",
|
|
30
|
+
type: "board",
|
|
31
|
+
name: "Empty",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
await executeInspection({
|
|
36
|
+
currentPage: {
|
|
37
|
+
children: [
|
|
38
|
+
frame,
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
).toEqual({
|
|
43
|
+
ok: true,
|
|
44
|
+
frameId: "frame-1",
|
|
45
|
+
frameName: "Empty",
|
|
46
|
+
});
|
|
47
|
+
expect(
|
|
48
|
+
await executeInspection({
|
|
49
|
+
currentPage: {
|
|
50
|
+
children: [
|
|
51
|
+
frame,
|
|
52
|
+
frame,
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
).toMatchObject({
|
|
57
|
+
ok: false,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("checks new code folders while leaving data paths outside the code rule", () => {
|
|
62
|
+
expect(
|
|
63
|
+
findInvalidCodeDirectories([
|
|
64
|
+
"packages/core/src/capture-validation.ts",
|
|
65
|
+
"examples/browser-capture/provider.mjs",
|
|
66
|
+
"docs/conventions/templates/frame-label.ts",
|
|
67
|
+
"docs/trials/ImportedEvidence/report.json",
|
|
68
|
+
]),
|
|
69
|
+
).toEqual([]);
|
|
70
|
+
expect(
|
|
71
|
+
findInvalidCodeDirectories([
|
|
72
|
+
"packages/core/src/captureHelpers/geometry.ts",
|
|
73
|
+
"packages/core/src/captureHelpers/fonts.ts",
|
|
74
|
+
"examples/BrowserCapture/provider.mjs",
|
|
75
|
+
]),
|
|
76
|
+
).toEqual([
|
|
77
|
+
"examples/BrowserCapture",
|
|
78
|
+
"packages/core/src/captureHelpers",
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { snippet, type Snippet } from "../../../packages/core/src/snippet.js";
|
|
2
|
+
|
|
3
|
+
export function createFrameInspection(frameName: string): Snippet {
|
|
4
|
+
if (!frameName.trim()) {
|
|
5
|
+
throw new Error("Frame name is required");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return snippet(
|
|
9
|
+
"example-frame-inspection",
|
|
10
|
+
`
|
|
11
|
+
const matchingFrames = penpot.currentPage.children.filter((shape) => {
|
|
12
|
+
return shape.type === "board" && shape.name === CFG.frameName;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (matchingFrames.length !== 1) {
|
|
16
|
+
return { ok: false, message: "Expected one uniquely named frame" };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const frame = matchingFrames[0];
|
|
20
|
+
return { ok: true, frameId: frame.id, frameName: frame.name };
|
|
21
|
+
`,
|
|
22
|
+
{
|
|
23
|
+
frameName,
|
|
24
|
+
},
|
|
25
|
+
"read",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface FrameLabel {
|
|
2
|
+
pageName: string;
|
|
3
|
+
frameName: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function formatFrameLabel(label: FrameLabel): string {
|
|
7
|
+
const pageName = label.pageName.trim();
|
|
8
|
+
const frameName = label.frameName.trim();
|
|
9
|
+
|
|
10
|
+
if (!pageName || !frameName) {
|
|
11
|
+
throw new Error("A frame label requires a page name and a frame name");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return `${pageName} / ${frameName}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# TypeScript and naming
|
|
2
|
+
|
|
3
|
+
Use `pnpm format` and `pnpm check:style`; do not hand-align code against Biome.
|
|
4
|
+
Separate top-level declarations and distinct steps inside a function with blank
|
|
5
|
+
lines. Use braces for every branch and loop, and declare one variable at a time.
|
|
6
|
+
Prefer early exits that leave the main path easy to follow.
|
|
7
|
+
|
|
8
|
+
Biome preserves these blank lines but does not insert them between declarations
|
|
9
|
+
or logical steps. Add them while writing/reviewing code, before running the
|
|
10
|
+
formatter. A passing style check alone does not prove that vertical spacing follows
|
|
11
|
+
this convention. Separate each top-level declaration (including interfaces and
|
|
12
|
+
type aliases), and separate input preparation, guard clauses, work and returns
|
|
13
|
+
inside a function. Keep a documentation comment attached to its declaration.
|
|
14
|
+
|
|
15
|
+
## Names carry intent
|
|
16
|
+
|
|
17
|
+
- Files and folders: `capture-validation.ts`, `page-session/`. Keep conventional
|
|
18
|
+
`index.ts`, `*.test.ts`, `*.config.ts` and tool-required filenames.
|
|
19
|
+
- Values and functions: `captureMetadata`, `baselineImage`, `implementationCase`,
|
|
20
|
+
`validateCaptureMetadata`. Use a verb for an action and a noun for a value.
|
|
21
|
+
- Types: `ImplementationCase`, `BrowserCaptureMetadata`. Prefer interfaces for
|
|
22
|
+
object contracts and unions for finite alternatives.
|
|
23
|
+
- Booleans: `fontsReady`, `hasCurrentApproval`, `isComplete`.
|
|
24
|
+
- Units and encodings: `timeoutMs`, `maxSnippetBytes`, `screenshotHash`,
|
|
25
|
+
`imageWidth`. Make conversions visible where they happen.
|
|
26
|
+
- Avoid one-letter domain names (`c`, `m`, `r`, `b`) and unexplained abbreviations
|
|
27
|
+
(`cfg`, `sub`, `rest`) when the full name makes the next line understandable.
|
|
28
|
+
Familiar small mathematical coordinates (`x`, `y`) remain appropriate.
|
|
29
|
+
- Preserve public field names, CLI flags and external API spelling. Improve a
|
|
30
|
+
local binding without renaming a persisted schema or an upstream property.
|
|
31
|
+
|
|
32
|
+
Use `const` unless reassignment is needed. Export named functions and types;
|
|
33
|
+
adapter definitions retain the default export required by their loader. Give
|
|
34
|
+
exported functions explicit return types. Use `unknown` with validation at input
|
|
35
|
+
boundaries rather than `any` or a cast that pretends an external value was checked.
|
|
36
|
+
|
|
37
|
+
## Functions and errors
|
|
38
|
+
|
|
39
|
+
A function should name one operation at one level of detail. Extract a named
|
|
40
|
+
predicate when a condition combines independently meaningful rules; retain the
|
|
41
|
+
short-circuit checks that protect access to malformed input. Extract filesystem
|
|
42
|
+
work from pure validation when each can be tested independently.
|
|
43
|
+
|
|
44
|
+
Throw errors with actionable domain context. Catch them where a caller can choose
|
|
45
|
+
recovery, report a failed case or return a command exit code. Preserve existing
|
|
46
|
+
error classifications, redaction and cancellation behavior during a refactor.
|
|
47
|
+
Comment on non-obvious reasons; use names to explain routine steps.
|
|
48
|
+
|
|
49
|
+
## Tests
|
|
50
|
+
|
|
51
|
+
Use the package's Vitest runner and its existing `test/` layout. Test observable
|
|
52
|
+
contracts: accepted evidence, rejected malformed/stale evidence, partial progress,
|
|
53
|
+
and filesystem boundaries. Keep fixtures local and deterministic. For a refactor,
|
|
54
|
+
keep a public-entrypoint integration test and add regression cases for the rules
|
|
55
|
+
being extracted. Avoid tests that only search for an implementation's wording.
|
|
56
|
+
|
|
57
|
+
Production TypeScript uses ESM imports with `.js` for local modules. Match the
|
|
58
|
+
existing NodeNext configuration. Documentation templates import actual core types
|
|
59
|
+
so API changes invalidate stale examples during `pnpm check`.
|
package/docs/cookbook.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Cookbook
|
|
2
|
+
|
|
3
|
+
## Add a page
|
|
4
|
+
|
|
5
|
+
Confirm the target and active token vocabulary. Start with the [Hello-card example](../examples/hello-card/README.md), then compose reviewed project recipes through [the build kit](build-kit.md). Inspect/apply/read back, export twice and obtain owner review. Adapt the project naming convention for page/state/device; retain IDs and ownership markers for recovery.
|
|
6
|
+
|
|
7
|
+
## Update library tokens
|
|
8
|
+
|
|
9
|
+
Run the configured adapter's snapshot/check commands, review the declaration diff, then inspect live reconciliation before applying. The [token adapter guide](marwes.md) covers ownership/adoption conflicts and measured component limits. A stale snapshot or intentional designer edit requires a declaration/ownership decision rather than a blind overwrite.
|
|
10
|
+
|
|
11
|
+
## Onboard a colleague
|
|
12
|
+
|
|
13
|
+
Share config and the owner-authorized file pin through Git. Follow [team onboarding](team.md) with separate accounts/keys and write coordination. Doctor must observe the intended file. Localhost Docker instances are not automatically shared deployments.
|
|
14
|
+
|
|
15
|
+
## Rotate a key
|
|
16
|
+
|
|
17
|
+
Use the provider's own key-management UI and the private `connect --replace` flow. Reconnect the intended editor, run doctor and retain the same public file pin. Keep key values out of chat, command arguments, commits and reports. File/OS credential references are documented with their implemented provider support; avoid inventing external command helpers.
|
|
18
|
+
|
|
19
|
+
## Move to another Penpot
|
|
20
|
+
|
|
21
|
+
Obtain owner authorization, create an independent archive and prove a scratch import before changing the project target. Confirm all new public file/page/component IDs and update explicit mappings. Start a new evidence lineage; hashes/approvals from another file do not transfer just because frame names match.
|
|
22
|
+
|
|
23
|
+
## Recover interrupted work
|
|
24
|
+
|
|
25
|
+
Inspect the recorded run and the actual pinned objects; follow [recovery](recovery.md) with unchanged source/parameters and explicit resume. Preserve third-party edits. For disaster recovery use [backup/restore](backup.md), not a structural snapshot: the [review snapshot](snapshots.md) intentionally lacks full native fidelity.
|