penpot/.serena/memories/workflow/creating-prs.md
Dr. Dominik Jain caacd482af
✨ Add composable test framework plugin (applied to component tests, runs in CI) (#10679)
* ✨ Add plugin with composable test framework and component tests

The plugin provides a framework for writing composable tests against
the Plugin API, and applies it to systematic end-to-end testing of
component semantics.

The framework's core ideas: a test is written once as a composition of
operations over a starting configuration; choice points among the
operations (optional steps, alternatives) expand the composition into
a full sweep of test variants, so a single case definition yields
broad combinatorial coverage; and the operations drive the real Plugin
API with real change propagation, testing the full production
implementation.

The initial application is a suite of component test cases covering
synchronization, overrides, swap slots and variants — the
TypeScript/e2e continuation of the ClojureScript composable test suite
(frontend_tests.composable_tests). Several cases originate from
reproducing real defects (e.g. #10109 and the swap-slot corruptions).

Tests run from an interactive panel in Penpot: cases are listed with
plain-language descriptions, tests can be run selectively, results
stream in live, and every checkbox carries a stable DOM id — so the
panel can equally be driven programmatically (the basis for running
the suite in CI), as documented in the plugin's README.

Lives at plugins/apps/composable-test-suite as a regular member of the
plugins workspace (init script, start:plugin:composable-test-suite,
shared dev port 4202, covered by build:plugins via the new
./apps/*-test-suite filter).

Related to #10584.

AI-assisted-by: claude-fable-5

* ✨ Run the composable test suite headlessly in CI

Adds a headless run mode for the composable test suite, following the
plugin-api-test-suite's CI architecture, and a workflow that runs it as
a per-PR gate.

An in-sandbox entry (src/ci/headless.ts) runs the suite without the
panel UI — the framework's runner was UI-free by construction, so no
refactoring was needed — and streams each result through console
markers, addressed by the same composite identifiers the panel uses
(e.g. MainEditSyncs-2), with durations and, on failure, the error and
the applied-steps transcript. It is built as a single self-executing
bundle and evaluated directly inside a real Penpot plugin sandbox by
the driver (ci/run-ci.ts), so no plugin dev server or port is involved.

The driver needs no backend and no login: it serves the prebuilt
frontend bundle via the frontend e2e static server and intercepts every
backend RPC with Playwright fixtures. The mocked backend is not a
limitation for this suite — everything it asserts is frontend store
logic executed in memory — which the full run confirms: all 48 tests
behave identically to the interactive panel, including variants and
swap slots, with the single (currently expected) failure of
MainEditSyncs-2 reproducing bug #10109 under the mock.

TEST_FILTER selects tests by identifier substring; CI_TIMEOUT_MS bounds
the run. The mock harness mirrors the frontend e2e harness (see the
provenance note in the driver).

Related to #10584.

AI-assisted-by: claude-fable-5

* 📚 Restructure the composable-tests memory around both suites

Present the composable component tests top-down: the shared framework
principles upfront, then the two implementations — the ClojureScript
suite in the frontend test tree and the TypeScript suite in the plugin,
which tests fully end-to-end with a slightly more elaborate set of
abstractions — and the plugin's headless CI run, pointing to the
plugin's README for operational details. Also records this session's
additions (geometry operations, case N, the CI harness).

AI-assisted-by: claude-fable-5

* 📎 Refine the PR-description conventions in the creating-prs memory

Encourage digestible descriptions: bullet items over prose (grouped by
area with bold lead-ins for larger PRs) and no manual line wraps, since
the rendered markdown adapts to the viewport. Also drop the outdated
'MCP' from the standard Note line.

AI-assisted-by: claude-fable-5

* 🔧 Set Prettier endOfLine to auto in plugins workspace

Prettier defaults to endOfLine "lf", which is incompatible with
checkouts on Windows that use core.autocrlf=true

* 🐛 Fix problems with suite

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-07-24 09:59:18 +02:00

3.3 KiB

Creating Pull Requests

PR only on explicit request. Branch: issue/feature-specific; fallback <type>/<short-description> (fix/..., feat/..., refactor/..., docs/..., chore/..., perf/...).

Target Branch

Auto-detect the base branch with scripts/detect-target-branch:

TARGET=$(scripts/detect-target-branch)

This outputs staging or develop by walking the local commit graph (pure local, no remote/network). Do not ask the user for the target branch unless the tool fails.

Metadata

Always add the PR to the Main project (--project "Main") unless the user explicitly requests a different project.

Title Format

PR titles follow commit title conventions:

:emoji: Subject line (imperative, capitalized, no period, <=70 chars)

See mem:workflow/creating-commits for emoji codes. Squash merge uses the PR title as the final commit subject, so title format matters.

Description Body

Include concise sections covering:

  • what changed and why;
  • related GitHub issues or Taiga stories (Fixes #NNNN, Relates to #NNNN, Taiga #NNNN);
  • screenshots or recordings for UI-visible changes;
  • testing performed and residual risk;
  • breaking changes or migration notes, if any.

PR descriptions follow this structure:

**Note:** This PR was created with AI assistance as part of the Penpot self-improvement initiative.

## What

<the problem or feature and its user-facing impact — short bullet items where there is more than one point>

## Why

<root cause or motivation — a short paragraph or bullets>

## How

<high-level approach and key decisions — bullet items, grouped by area (bold lead-ins) for larger PRs>

The "Note:" line is required at the top. Adjust if this is a manual (non-AI) PR.

Writing Principles

  • Write for humans. The diff shows what changed. The description explains why.
  • Be concise. Focus on reasoning: What was the problem? Why did it happen? How did you solve it?
  • Prefer bullets over paragraphs. Short bullet items, grouped by area with bold lead-ins where helpful, are far easier to digest than prose; keep any remaining paragraph to a few sentences.
  • No manual line wraps. Markdown renders adapting to the viewport; hard-wrapped lines degrade rendering. One line per paragraph or bullet, however long.
  • Skip the obvious. Don't explain what git diff already shows.

What NOT to Include

  • ❌ List of files changed (visible in diff)
  • ❌ Testing steps (CI handles this)
  • ❌ Screenshots unless UI-visible
  • ❌ Migration notes unless breaking changes
  • ❌ Regression fixes introduced during the PR (they're part of the development process, not the feature)

Before Opening

  • Follow mem:workflow/creating-commits for commits
  • Run the focused tests/lints appropriate to touched modules.
  • Do not force-push during review unless the maintainer workflow explicitly asks for it.
  • When the user says the code is already pushed, trust that — do not verify remote branch existence via git ls-remote or git fetch.

Creating the PR

cat > /tmp/pr-body.md << 'PR_BODY'
<body content here>
PR_BODY

TARGET=$(scripts/detect-target-branch)

gh pr create \
  --repo penpot/penpot \
  --base "$TARGET" \
  --head <branch> \
  --title "<title>" \
  --project "Main" \
  --body-file /tmp/pr-body.md

rm -f /tmp/pr-body.md