diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 7d8a4d3..c9fb988 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -59,8 +59,11 @@ representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at [INSERT CONTACT -METHOD]. All complaints will be reviewed and investigated promptly and fairly. +reported to the community leaders responsible for enforcement by tagging +[James Robb](https://github.com/jamesrweb) or +[Eugene Dyko](https://github.com/yevdyko) directly, or via GitHub's built-in +report feature for sensitive cases. All complaints will be reviewed and +investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index ba16cab..330250d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -12,7 +12,7 @@ Please fill out this template to help us address your issue as quickly as possib - [ ] πŸ“ Documentation Issue - [ ] πŸ€” Question - [ ] 🧹 Chore -- [ ] ❓Other +- [ ] ❓ Other ## Description diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 20c9be1..c766e3e 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -48,5 +48,5 @@ check: ## Code of Conduct Please note that this project adheres to a -[Code of Conduct](https://github.com/p5-wrapper/react/blob/master/CODE_OF_CONDUCT.md). +[Code of Conduct](https://github.com/p5-wrapper/react/blob/main/.github/CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms. diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 8826da1..9bd750d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -58,7 +58,7 @@ jobs: - name: Find previous successful run id: previous-run run: | - RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/CI.yml/runs?status=success&per_page=1" \ + RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/continuous-integration.yml/runs?status=success&per_page=1" \ --jq '.workflow_runs[0].id // empty') echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT env: diff --git a/.gitignore b/.gitignore index dbdb439..aa46f26 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ coverage *.local yarn.lock package-lock.json -pnpm-lock.yaml # Editor directories and files .vscode/* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..eeeb7be --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..067fa7b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,462 @@ +# Agent Guide β€” @p5-wrapper/react + +## Strict Rules + +1. **Plan first:** Create a detailed plan and get explicit user approval before + making changes. +2. **Quality gates:** Every change must pass `pnpm format:check`, `pnpm lint`, + `pnpm test`, and `pnpm build` (or `pnpm integrate` for the full pipeline) + before being considered complete. +3. **Documentation:** Update `README.md`, `AGENTS.md`, configuration files, and + any other documentation affected by your changes. Clean as you go β€” take + ownership of every file you touch. +4. **PR descriptions:** When asked, create `PR_DESCRIPTION.md` (gitignored) + using the template at `.github/PULL_REQUEST_TEMPLATE.md`. Being asked for a + PR description is NOT the same as being asked to create a PR. +5. **Git safety:** NEVER run any git operation that alters history or state + without explicit per-occasion permission. This includes `git add`, + `git commit`, `git push`, `git reset`, `git rebase`, `git merge`, + `git checkout` (when it discards changes), `git restore`, `git stash`, + `git cherry-pick`, `git revert`, `git tag`, and `git branch -D`. Prior + approval does not carry forward. +6. **Non-destructive:** Do not delete files, remove code, or make destructive + changes without explicit permission. Investigate before overwriting. +7. **Workflows:** Do not modify GitHub Actions workflows or the composite setup + action without explicit permission. If a CI fix is needed, propose the change + and wait for approval. +8. **No local publishing:** NEVER publish to npm or deploy to GitHub Pages + locally. All releases and demo deployments go through the CD workflow on push + to `main`. +9. **Public API surface:** This package is consumed by downstream users. Do not + rename, remove, or change the signature of anything exported from + `src/main.tsx` without an explicit versioning discussion β€” exports are a + semver contract. + +## Project Standards + +### Authority + +Project standards are the highest-priority rules for this repository. If any +instruction or rule conflicts with a project standard, the agent MUST: + +1. Refuse to follow the conflicting instruction. +2. Inform the user of the conflict, citing the specific standard. +3. State that changes to standards must be made deliberately in `AGENTS.md`, not + sidestepped for convenience. + +### Language + +All code, comments, documentation, variable names, error messages, commit +messages, and any other text MUST use British English (e.g., `organisation` not +`organization`, `normalise` not `normalize`, `colour` not `color`, `behaviour` +not `behavior`, `licence` not `license`, `centre` not `center`). + +### Package Management + +- **Package manager:** pnpm (`pnpm@11.25.0` via the `packageManager` field β€” + Corepack manages the exact version, never install pnpm globally) +- **Node.js engine:** `>=24.20.0` (declared in `package.json` `engines`) +- **Lock file:** `pnpm-lock.yaml` is committed. NEVER delete or regenerate it + casually β€” run `pnpm install` after dependency changes and commit the result +- **Supply chain:** `pnpm-workspace.yaml` enforces `strictPeerDependencies`, + `minimumReleaseAgeStrict`, and a minimal `onlyBuiltDependencies` allowlist + (`esbuild` only). Do not add `postinstall`-executing packages to the allowlist + or widen these settings without explicit permission β€” new exclusions under + `minimumReleaseAgeExclude` require a justification comment in the PR +- **Peer dependencies are a contract:** `p5`, `react`, and `react-dom` are peer + dependencies. The library code must never import anything beyond these at + runtime β€” `microdiff` and `react-error-boundary` are the only runtime + dependencies + +### Formatting and Linting + +- **Prettier** is the formatter (this project does not use Biome β€” do not + introduce it). Config lives at `config/prettier/prettier.json`, key rules: + `printWidth: 80`, `arrowParens: "avoid"`, `trailingComma: "none"`, + `proseWrap: "always"` (all Markdown prose is hard-wrapped at 80 columns), + imports sorted by `@trivago/prettier-plugin-sort-imports` +- **ESLint** is the linter. Config lives at `config/eslint/eslint.config.ts` and + extends `eslint` recommended, `typescript-eslint` strict and stylistic, and + `eslint-plugin-react-compiler` recommended with project-aware TypeScript + parsing. Type-aware linting runs via `jiti` β€” keep the config a `.ts` file +- **React Compiler:** The React Compiler ESLint rules are enabled. Components + must follow the Rules of React strictly β€” no manual memoisation where the + compiler can handle it, no mutating props or render-time side effects. Do not + disable these rules +- **No comments:** Do not add comments to source files. The code should be + self-documenting. The only permitted exceptions are `@ts-expect-error` / + `@ts-ignore` suppressions with a `@see` reference (see + `src/utils/createP5CanvasInstance.ts` for the existing pattern) and JSDoc on + exported contracts where a URL reference adds value (see + `src/contracts/P5CanvasInstanceRef.ts`) +- **No comments rule does not apply to:** this file, `README.md`, workflow + files, and config files with existing comments + +### TypeScript + +- **Strict mode:** `strict` and `noImplicitAny` are on in `tsconfig.json`. No + tsconfig option may be weakened +- **Path aliases:** `@/*`, `@components/*`, `@utils/*`, `@constants/*`, + `@contracts/*` map into `src/`. These are declared twice β€” in `tsconfig.json` + `paths` and `config/vite/common.ts` β€” and MUST be kept in sync when changed +- **Import style:** Use `import { type Foo }` inline type imports, matching the + existing code. Imports of contracts across the alias boundaries follow the + sorted import order enforced by Prettier +- **Type assertions:** Avoid `as` casts in library code. The one existing + `@ts-expect-error` in `createP5CanvasInstance.ts` documents a known p5 + upstream type inference issue β€” do not remove it without verifying against the + referenced p5 PR +- **Version pinned to 6.0.3:** `typescript` is an exact pin + (`"typescript": "6.0.3"`, no caret), deliberately held back from v7. + typescript-eslint does not currently support TypeScript 7 β€” its + [supported range](https://typescript-eslint.io/packages/parser) is + `>=4.8.4 <6.1.0`, and the Go-based TypeScript 7 is not yet feature compatible + with the JavaScript-based v6 API that typescript-eslint builds on. Upstream + support is tracked in + [typescript-eslint#10940](https://github.com/typescript-eslint/typescript-eslint/issues/10940) + and cannot land before TypeScript 7.1.x at the earliest. Do not bump past + `6.0.x` or widen the pin without confirming upstream support. Do NOT run + TypeScript 7 side by side with v6 as an interim measure β€” we wait for full + feature parity before migrating, no half measures + +### Quality Gates + +Every change must pass before being considered complete: + +- `pnpm format:check` β€” formatting +- `pnpm lint` β€” linting +- `pnpm test` β€” testing +- `pnpm build` β€” type checking (`tsc --noEmit`) plus component and demo builds + +`pnpm integrate` runs format check β†’ lint β†’ test β†’ build in one command and is +the closest local mirror of CI. + +### Git Safety + +NEVER run any git operation that alters history or state without explicit +per-occasion permission from the user. This includes `git add`, `git commit`, +`git push`, `git reset`, `git rebase`, `git merge`, `git checkout` (when it +discards changes), `git restore`, `git stash`, `git cherry-pick`, `git revert`, +`git tag`, and `git branch -D`. Prior approval does not carry forward β€” each +occasion requires fresh permission. + +NEVER use `git clean`, `git checkout -- `, `git reset --hard`, or any +other command that discards uncommitted work. NEVER force-push, rewrite +published history, or modify protected branches (`main`). Investigate before +overwriting β€” if a change would delete files, remove code, or alter state, +propose it first and wait for approval. + +Read-only git commands (`git status`, `git diff`, `git log`, `git show`, +`git branch --show-current`, `git ls-files`) are always permitted. + +### Scope of Operation + +NEVER operate outside the project root unless explicitly instructed to do so by +the user. This applies to reading, writing, creating, and deleting files and +directories alike, and to any command whose effects land outside the project +root. Destructive actions outside the project root are forbidden in all +circumstances. + +**The one exception:** Experiments and scratch work belong in the `/tmp` +directory β€” and only when the user has asked for them or given permission. +Anything created there is still subject to the same non-destructive rules: do +not delete, overwrite, or modify anything in `/tmp` that the agent did not +create itself. + +### Obligation to Fix + +If the agent encounters a pre-existing issue β€” one not caused by the current +changes β€” that will affect CI, CD, or published package consumers, the agent +MUST fix it. This is NOT optional. The agent must not ignore, skip, or defer +such issues regardless of whether they were introduced by the agent's own +changes. A broken pipeline or a broken published package is the agent's +responsibility if the agent is aware of it. + +### Planning + +ALWAYS create a detailed plan and obtain explicit user approval before making +project changes. Do not begin implementation until the plan is approved. + +### Code Philosophy + +- **Functional and declarative:** The codebase is small, pure, and composable by + design. Components are function components; utilities are pure functions that + take arguments and return values β€” they never reach for globals or hidden + state. Keep it this way +- **Types as the public contract:** The `src/contracts/` directory is the type + contract between the library and its consumers. Every exported type is public + API via `src/main.tsx`. Generic defaults flow through `SketchProps` β€” + understand the generic chain (`Sketch` β†’ `P5CanvasInstance` β†’ + `Updater` β†’ `P5CanvasProps`) before touching any of them +- **One contract per file, one export per file:** Contracts live in + `src/contracts/`, one file per contract, named after the export. Utilities + live in `src/utils/`, one file per function, named after the function. Follow + this pattern for anything new +- **No hidden dependencies:** The component tree is deliberately layered β€” + `P5Canvas` (memoisation) β†’ `P5CanvasGuard` (validation + error boundary + + suspense) β†’ `P5CanvasWithSketch` (lifecycle). Responsibilities stay in their + layer; utils never import components; contracts never import utils +- **Imperative p5, declarative React:** p5 instances are imperative and mutable + by nature. The bridge is contained entirely in `P5CanvasWithSketch` and the + `src/utils/` lifecycle functions (`createP5CanvasInstance`, + `updateP5CanvasInstance`, `removeP5CanvasInstance`). Do not leak imperative p5 + patterns into the React layer above +- **Lazy boundaries:** `P5CanvasGuard` and `react-error-boundary` are lazily + imported so consumers who never trigger them never pay the bundle cost. Any + new heavy dependency must follow the same `React.lazy` pattern + +### Testing + +- **Test first:** Tests for new behaviour are written before or alongside the + implementation, never as an afterthought. Every utility in `src/utils/` has a + corresponding test file in `tests/utils/`; every component in + `src/components/` has one in `tests/components/`. Keep this 1:1 mapping +- **Black-box testing:** Test the rendered output and observable behaviour + (`data-testid` hooks: `canvas-container`, `loading`, `error`), not internal + implementation details +- **Environment:** Vitest with `happy-dom`, `vitest-canvas-mock` for the canvas + API, and `@testing-library/react`. `p5.disableFriendlyErrors = true` is set in + `tests/setup.ts` to stop p5's DOM scanning from causing unhandled rejections β€” + do not remove it. `afterEach` cleanup is also mandatory +- **Structure:** Tests mirror the `src/` directory structure + (`tests/components/`, `tests/constants/`, `tests/utils/`, plus + `tests/exports.test.tsx` guarding the public API surface). New `src/` files + must add the matching test file +- **Coverage:** CI runs `pnpm test:coverage` and comments coverage deltas on + PRs. Do not reduce coverage of existing code +- **Skipped tests:** Two loading-UI tests are currently `it.skip`-ped due to + suspense timing behaviour. Do not delete them; fixing them is a valid task + +### PR Descriptions + +When asked to generate a PR description, create a `PR_DESCRIPTION.md` file in +the project root (this file is gitignored and must never be committed). Follow +the PR template at `.github/PULL_REQUEST_TEMPLATE.md` exactly β€” copy the entire +template, do not remove any sections or HTML comments, and fill in each section +based on actual changes. + +**Important:** Being asked to generate a PR description is NOT the same as being +asked to create a PR. Only create an actual pull request when explicitly told to +do so. + +**Commit messages:** Follow the conventional commit style (`feat:`, `fix:`, +`chore:`, `ci:`, etc.). Emoji prefixes are NOT used for human-authored commits β€” +they only appear on automated Dependabot commits (`🧹 chore(deps)` and +`πŸ”§ ci(deps)`). + +**No co-authored commits:** Agents MUST NOT add `Co-authored-by` trailers or any +other attribution that signs off a commit on the agent's behalf. Only humans can +legally certify a contribution β€” the human submitter reviews the AI-generated +code, takes full responsibility for it, and adds any certification trailers +themselves. Following the rules the Linux kernel team enforce for AI coding +assistants, an agent's role in a commit ends at the message body β€” no +`Signed-off-by`, no `Co-authored-by`, no other trailers or sign-offs. See [AI +Coding Assistants β€” The Linux Kernel documentation] +(https://docs.kernel.org/process/coding-assistants.html), integrated into this +ruleset on 2026-09-03. + +**Assisted-by attribution:** Where attribution for AI assistance is wanted, use +an `Assisted-by: LLM` trailer in the commit message body rather than a co-author +or sign-off trailer. It records that the contribution was produced with AI +assistance without certifying or authoring it. This mirrors the kernel's +`Assisted-by: LLM [TOOL1] [TOOL2]` format β€” optionally list specialised analysis +tools after `LLM`, but never list basic development tools (git, compilers, +editors, linters). Only add the trailer when the user has asked for AI +attribution; the default is no trailer at all. + +### Documentation Maintenance + +Always update documentation, configuration files, and related files as you go. +Documentation must never be out of date. If a change affects `README.md`, +`AGENTS.md`, configuration files, or any other documentation, update them in the +same change. Clean as you go β€” take ownership of every file you touch. + +If formatting, linting, or other tooling fixes issues in files you did not +originally author, do not revert those fixes. CI would break again. Accept +responsibility for the state of the codebase after your changes, not just the +lines you intended to change. + +## Project Overview + +**Purpose:** A React wrapper component (`P5Canvas`) for integrating +[p5.js](https://p5js.org/) sketches into React applications. It is published to +npm as `@p5-wrapper/react` and consumed by downstream projects, including the +sibling `@p5-wrapper/next` package. + +**Key characteristics:** + +- **Library + demo in one repo:** The npm library builds from `src/` while the + GitHub Pages demo builds from `demo/`. Both share one Vite config that + switches on mode +- **p5 instance mode:** Sketches run in p5 instance mode (not global mode). The + wrapper owns the p5 lifecycle β€” instances are created when a sketch is + provided, destroyed and recreated when the `sketch` prop changes, and removed + on unmount +- **React 19:** Requires `react >= 19` and `react-dom >= 19`. React Compiler + lint rules are active +- **Version 5:** The public API is `P5Canvas` (renamed from `ReactP5Wrapper` in + v4) plus the exported types and `CanvasContainerClassName` + +## Architecture + +### Component Layering + +``` +P5Canvas (src/components/P5Canvas.tsx) + React.memo + propsAreEqual (microdiff deep comparison) + └─ P5CanvasGuard (lazy) + sketch validation β†’ fallback UI or: + ErrorBoundary (lazy react-error-boundary) + Suspense + └─ P5CanvasWithSketch + refs: canvasContainerRef, p5CanvasInstanceRef, updaterRef + effects: create/update instance on sketch change, + updateWithProps + updater on props change, + remove instance on unmount + renders: div.canvas-container with children +``` + +Why the layers exist: + +- `P5Canvas` only handles memoisation β€” a deep `microdiff` comparison so p5 is + not needlessly recreated +- `P5CanvasGuard` handles absence (missing `sketch` β†’ `fallback`), errors + (`error` render prop β†’ error boundary), and async loading (`loading` β†’ + suspense) +- `P5CanvasWithSketch` is the only place that touches the p5 instance lifecycle + via the utils + +### Public API + +Everything exported from `src/main.tsx` is public API and semver-protected: + +- `P5Canvas` β€” the component +- `CanvasContainerClassName` β€” CSS class of the container div + (`"canvas-container"`) +- Types: `P5CanvasInstance`, `P5CanvasProps`, `Sketch`, `SketchProps`, `Updater` + +### File Organisation + +``` +. +β”œβ”€β”€ CLAUDE.md # Symlink to AGENTS.md β€” agents edit AGENTS.md only +β”œβ”€β”€ AGENTS.md # This document +β”œβ”€β”€ package.json # Scripts, engines, exports map, dependencies +β”œβ”€β”€ pnpm-workspace.yaml # Supply-chain settings (strict peers, release age) +β”œβ”€β”€ pnpm-lock.yaml # Committed lock file +β”œβ”€β”€ tsconfig.json # Strict TypeScript config + path aliases +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ main.tsx # Public API barrel β€” the library entry point +β”‚ β”œβ”€β”€ components/ # React components (one per file, default or named exports) +β”‚ β”‚ β”œβ”€β”€ P5Canvas.tsx +β”‚ β”‚ β”œβ”€β”€ P5CanvasGuard.tsx +β”‚ β”‚ └── P5CanvasWithSketch.tsx +β”‚ β”œβ”€β”€ constants/ # Exported constants +β”‚ β”œβ”€β”€ contracts/ # Public-facing types (one contract per file) +β”‚ └── utils/ # Pure functions (one function per file) +β”œβ”€β”€ tests/ # Vitest tests mirroring src/ structure +β”‚ β”œβ”€β”€ components/ +β”‚ β”œβ”€β”€ constants/ +β”‚ β”œβ”€β”€ utils/ +β”‚ β”œβ”€β”€ exports.test.tsx # Guards the public API surface +β”‚ └── setup.ts # Test bootstrap (canvas mock, cleanup) +β”œβ”€β”€ config/ +β”‚ β”œβ”€β”€ eslint/eslint.config.ts +β”‚ β”œβ”€β”€ prettier/prettier.json +β”‚ └── vite/ +β”‚ β”œβ”€β”€ vite.config.ts # Mode switch: lib (test + library) vs demo +β”‚ β”œβ”€β”€ common.ts # Shared path aliases +β”‚ β”œβ”€β”€ library.ts # Library build + Vitest config +β”‚ └── demo.ts # GitHub Pages demo build +β”œβ”€β”€ demo/ # Demo app (GitHub Pages) β€” plain JSX, no build type checking +β”‚ β”œβ”€β”€ app.jsx +β”‚ β”œβ”€β”€ index.html +β”‚ └── sketches/ # Example sketches +β”œβ”€β”€ dist/ # Build output (gitignored) +└── .github/ + β”œβ”€β”€ actions/setup/ # Composite action: pnpm + Node + frozen install + └── workflows/ # CI, CD, CodeQL +``` + +### Build Pipeline + +- `pnpm build` = clean `dist` β†’ `tsc --noEmit` (type check) β†’ library build + (`dist/component`, ESM + CJS via Vite library mode, types bundled by + `vite-plugin-dts` with `bundleTypes`) β†’ demo build (`dist/demo`) +- `package.json` `exports` maps `types` β†’ `main.d.ts`, `import` β†’ ESM, `require` + β†’ CJS. The `files` field only ships `README.md` and `dist/component/*` +- The library entry filenames are pinned in `config/vite/library.ts` (`main.mjs` + / `main.cjs`) to match the `package.json` `exports` map β€” the `.mjs`/`.cjs` + extensions self-describe the module format, which is required because + `"type": "module"` would otherwise treat a `.js` CJS entry as ESM and break + `require()` for Node CJS consumers (this was broken in the published 5.0.4). + Vite's `[format]` placeholder also changed from `esm` to `es` in Vite 8, so + the names must never come from the placeholder again. If you change one side, + change both in the same commit +- The library externals are `react`, `react/jsx-runtime`, `react-dom`, `p5` β€” + keep Rollup externals, TypeScript expectations, and peer dependencies in + agreement + +## Commands + +| Command | Action | +| :--------------------- | :--------------------------------------------------------- | +| `pnpm install` | Install dependencies (updates lockfile β€” commit the diff). | +| `pnpm dev` | Run the Vite dev server with the demo app. | +| `pnpm format` | Format all files with Prettier. | +| `pnpm format:check` | Check formatting without writing. | +| `pnpm lint` | Run ESLint (type-aware). | +| `pnpm lint:fix` | Run ESLint with autofix. | +| `pnpm test` | Run the Vitest suite. | +| `pnpm test:coverage` | Run tests with coverage (what CI runs). | +| `pnpm test:watch` | Watch mode. | +| `pnpm build:component` | Type check + build the npm library. | +| `pnpm build:demo` | Build the demo app only. | +| `pnpm build` | Clean and build component + demo. | +| `pnpm integrate` | format:check β†’ lint β†’ test β†’ build (CI mirror). | + +## CI/CD + +- **CI** (`continuous-integration.yml`): Runs on all PRs to `main` and + `workflow_dispatch`. Jobs: `format`, `lint`, `test` (with coverage artifact + and clover coverage delta comment), `build`, and `npm-dry-run` (validates the + npm publish would succeed). CI concurrency cancels in-progress runs. Uses the + `./.github/actions/setup` composite action with + `pnpm install --frozen-lockfile` +- **CD** (`continuous-deployment.yml`): Runs on push to `main` and + `workflow_dispatch`. Jobs: `gh-pages` (builds and deploys the demo to GitHub + Pages) and `npm` (builds, tests, and publishes the package with provenance). + CD concurrency does NOT cancel in-progress runs β€” never interrupt an in-flight + publish +- **CodeQL** (`CODEQL.yml`): Security analysis on PRs and pushes to `main` +- **Dependabot:** Monthly for npm (production and development groups) and GitHub + Actions. Semver-major updates are ignored by config β€” they are handled + manually on dedicated branches (e.g. the pnpm 11 / Vite 8 migration) +- **Permissions:** Workflows declare `permissions: {}` at the top and grant + minimal per-job permissions. Keep it this way + +## Guardrails + +- **Never publish or deploy locally.** npm publishing requires the `NPM_TOKEN` + secret and runs only in CD +- **Never weaken the build contract:** the `exports` map, `files` field, ESM + + CJS dual output, and bundled types are what downstream consumers depend on +- **Never introduce a runtime dependency** beyond `microdiff` and + `react-error-boundary` without discussion β€” bundle size is a feature of this + library +- **Never disable or skip tests, lint rules, or type checks** to make a change + pass. Fix the code, not the gate +- **Vite native config loader warning:** Vite currently warns that + `config/vite/vite.config.ts` uses `__dirname` and extension-less imports + unsupported by `configLoader: 'native'`. This is known and can be suppressed + with `VITE_CONFIG_NATIVE_IGNORE_WARNING=true`. Fixing the config is a valid + separate task, not a drive-by change + +## Future Topics + +- **Skipped loading-UI tests:** The two `it.skip` suspense tests in + `tests/components/P5Canvas.test.tsx` need a reliable strategy before being + re-enabled +- **Vite native config loader:** Migrate `vite.config.ts` off `__dirname` and + extension-less imports to clear the `configLoader: 'native'` warning +- **Semver-major dependency bumps:** Dependabot ignores them; they are done + deliberately on dedicated branches (e.g. the pnpm 11 / Vite 8 migration) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index a69322d..78ae065 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,8 @@ your project.
TypeScript setup -Install the p5 type definitions as a dev dependency: - -```shell -[npm|yarn|pnpm] [install|add] -D @types/p5 -``` +`p5` ships its own type definitions, so no extra `@types` package is needed β€” +TypeScript picks the types up from the `p5` package automatically.
@@ -75,10 +72,10 @@ To run the examples locally: git clone git@github.com:/react.git cd react pnpm install -pnpm preview +pnpm dev ``` -Then open `http://localhost:3001` in a browser. +Then open `http://localhost:5173` in a browser. ## Usage @@ -497,10 +494,10 @@ function ErrorChild() { function ErrorUI(error: unknown) { if (error instanceof Error) { - return

An error occured: {error.message}

; + return

An error occurred: {error.message}

; } - return

An unknown error occured: {String(error)}

; + return

An unknown error occurred: {String(error)}

; } function sketch(p5: P5CanvasInstance) { @@ -663,11 +660,56 @@ instead of `p5.Vector.random2D()`. ## Development -The source code for the component is in the `src` directory. +The source code for the component is in the `src` directory and the demo +application is in the `demo` directory. You will need +[Node.js](https://nodejs.org) and [pnpm](https://pnpm.io) β€” the exact versions +are pinned in [`package.json`](package.json). To build, watch and serve the examples (which also watches the component source): ```sh -pnpm preview +pnpm dev ``` + +### Contributing + +Before opening a pull request, make sure your changes pass every quality gate: + +```sh +pnpm integrate +``` + +This runs the same checks as CI β€” formatting, linting, tests, and builds. Pull +requests are reviewed against the +[pull request template](.github/PULL_REQUEST_TEMPLATE.md) and must keep the +public API in `src/main.tsx` backwards compatible. Commits follow the +[conventional commit](https://www.conventionalcommits.org) style (`feat:`, +`fix:`, `chore:`, etc.) β€” emoji prefixes are only used by automated pull +requests. + +The `typescript` dependency is pinned to `6.0.3` on purpose: +[typescript-eslint](https://typescript-eslint.io) does not support TypeScript 7 +yet β€” the Go-based v7 is not feature compatible with the v6 API it builds on, +and upstream support cannot land before TypeScript 7.1.x. It is tracked in +[typescript-eslint#10940](https://github.com/typescript-eslint/typescript-eslint/issues/10940). +Please do not widen the pin in pull requests β€” we wait for full feature parity +before migrating to v7, no half measures. + +#### AI-assisted contributions + +Contributions developed with the assistance of an AI agent are welcome, but you +β€” the developer β€” are always the ultimately responsible individual for your +contributions. AI agents cannot author or certify them. When AI assistance is +used: + +- Commit with `--signoff` (`git commit -s`) so that you certify the + [Developer Certificate of Origin](https://developercertificate.org/) for the + work yourself +- Optionally credit the agent with an `Assisted-by: LLM` trailer in the commit + message, as + [recommended by the Linux kernel team](https://docs.kernel.org/process/coding-assistants.html) +- Ensure all work adheres to the [MIT licence](LICENSE) + +See [`AGENTS.md`](AGENTS.md) for the full set of rules agents follow in this +repository. diff --git a/config/vite/demo.ts b/config/vite/demo.ts index 1e28753..9597d2e 100644 --- a/config/vite/demo.ts +++ b/config/vite/demo.ts @@ -9,7 +9,7 @@ export function demo(root: string): UserConfig { plugins: [react()], preview: { open: true }, build: { - chunkSizeWarningLimit: 1200, + chunkSizeWarningLimit: 1250, emptyOutDir: false, rollupOptions: { output: { diff --git a/config/vite/library.ts b/config/vite/library.ts index 82e342a..76e293f 100644 --- a/config/vite/library.ts +++ b/config/vite/library.ts @@ -19,14 +19,13 @@ export function library(root: string): UserConfig { lib: { entry: resolve(root, "src", "main.tsx"), name: "P5Canvas", - fileName: "P5Canvas", + fileName: format => (format === "es" ? "main.mjs" : "main.cjs"), formats: ["es", "cjs"] }, rollupOptions: { external: ["react", "react/jsx-runtime", "react-dom", "p5"], output: { assetFileNames: "assets/[name][extname]", - entryFileNames: "[name].[format].js", dir: dist, globals: { p5: "p5", diff --git a/package.json b/package.json index 590600e..946dd98 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "packageManager": "pnpm@11.25.0", "engines": { - "node": ">=24.14.0" + "node": ">=24.20.0" }, "homepage": "https://github.com/P5-wrapper/react", "license": "MIT", @@ -14,13 +14,13 @@ "dist/component/*" ], "types": "./dist/component/main.d.ts", - "main": "./dist/component/main.cjs.js", - "module": "./dist/component/main.esm.js", + "main": "./dist/component/main.cjs", + "module": "./dist/component/main.mjs", "exports": { ".": { "types": "./dist/component/main.d.ts", - "import": "./dist/component/main.esm.js", - "require": "./dist/component/main.cjs.js" + "import": "./dist/component/main.mjs", + "require": "./dist/component/main.cjs" } }, "scripts": { @@ -33,7 +33,7 @@ "integrate": "pnpm format:check && pnpm lint && pnpm test && pnpm build", "lint": "eslint --config config/eslint/eslint.config.ts", "lint:fix": "pnpm lint --fix", - "prettier": "prettier --config config/prettier/prettier.json --ignore-path .gitignore", + "prettier": "prettier --config config/prettier/prettier.json --ignore-path .gitignore --ignore-path .prettierignore", "test": "vitest run --silent --config config/vite/vite.config.ts", "test:coverage": "vitest run --silent --coverage --config config/vite/vite.config.ts", "test:watch": "vitest watch --config config/vite/vite.config.ts" @@ -121,7 +121,7 @@ "react": "19.2.8", "react-dom": "19.2.8", "rimraf": "^6.1.3", - "typescript": "^6.0.3", + "typescript": "6.0.3", "typescript-eslint": "^8.69.0", "vite": "^8.2.2", "vite-plugin-dts": "^5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 272920e..2d01513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,7 +106,7 @@ importers: specifier: ^6.1.3 version: 6.1.3 typescript: - specifier: ^6.0.3 + specifier: 6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.69.0 @@ -1129,8 +1129,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.419: - resolution: {integrity: sha512-nHMPn8x4yCxCI0iSnL+LlHL5sUoUfjLXkcRIagZ4GBdrfFLFaiLNvzJWbJqZhFT9IAhw5tUSNlhggWN+otvp/A==} + electron-to-chromium@1.5.420: + resolution: {integrity: sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==} email-addresses@5.0.0: resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} @@ -3542,7 +3542,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.11.20 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.419 + electron-to-chromium: 1.5.420 node-releases: 2.0.54 update-browserslist-db: 1.3.2(browserslist@4.28.8) @@ -3674,7 +3674,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.419: {} + electron-to-chromium@1.5.420: {} email-addresses@5.0.0: {}