---
title: Development
description: Run the app, refresh demo data, build the docs, and check changes.
sidebar:
  order: 5
---

## Run the app

Install packages and start the self-contained demo:

```sh
corepack enable
corepack pnpm run setup
corepack pnpm run demo
```

Open `http://localhost:2299`. The demo serves the checked-in todo-list data
from `public/demo-diff-data.json`. It does not start a snapshot, coding agent,
or remote Git request. Vite refreshes the page as you edit the app or fixture.

`pnpm run dev` starts the same local Vite server.

## Develop live reviews

Run a local worktree review through the app without building production files:

```sh
pnpm run dev:live -- --repo /path/to/repo
```

The command starts the Git watcher, live JSON route, event stream, and Vite.
It picks the next free port when `2299` is busy and prints the ready URL. Stop
it with Ctrl-C; it stops its child processes and removes its temporary data.

Use a deterministic local agent sequence when you do not need Git, agent
credentials, or network access:

```sh
pnpm run dev:mock
```

The mock starts with delayed note generation, reports file progress, fails,
retries, then completes. It reads `public/demo-diff-data.json`; pass
`--fixture PATH` to use another local JSON fixture. Vite updates source edits,
and fixture changes update the browser through the event stream.

## Run checks

```sh
corepack pnpm run check
```

`corepack pnpm run check` is the product gate. It runs React and TypeScript
lint, builds the app, runs the Node unit and integration tests, checks and
builds the docs, then installs the packed package in a temporary folder and
runs its CLI. Each stage names its failure before the command stops.

Npm release verification skips docs and saves the tarball from that same
package smoke test. Publishing uses that verified tarball without rebuilding
it. PR checks and site deployment still check and build the docs.

Install Chromium once after setup, and again when Playwright changes:

```sh
corepack pnpm run test:browser:install
```

Pull requests run this same gate from `pnpm-lock.yaml` on Node 22.13.0 and
the current Node 24 line. The changed-code Fallow audit and the site deployment
remain separate checks; neither replaces the product gate.

`pnpm run setup` uses `pnpm install --frozen-lockfile`, so it installs only what `pnpm-lock.yaml`
pins. It is safe to run again and needs no ignored or generated files. Node
22.13.0 and pnpm 11.18.0 are pinned in `.nvmrc` and `package.json`; the
`corepack pnpm` commands use the pinned pnpm version.

For a shorter local loop, run one named lane:

```sh
corepack pnpm run lint
corepack pnpm run test:unit
corepack pnpm run test:integration
corepack pnpm run test:coverage
corepack pnpm run test:browser
corepack pnpm run test:platform
```

| Command | What it checks |
| --- | --- |
| `pnpm run test:unit` | Fast parser, provider, doctor, release, config, and path checks. It does not build the app. |
| `pnpm run test:integration` | The production build plus local Git, agent note, presenter, and server checks. |
| `pnpm run test:coverage` | Unit and integration tests once, with per-file coverage checks. |
| `pnpm run test:browser` | The review journey in Chromium. It does not download a browser. |
| `pnpm run test:platform` | Host command lookup and npm launcher rules with local fake tools. |
| `pnpm test` | Unit and integration tests with coverage, then browser and platform tests. |

Coverage wraps `test:core`, which runs the unit and integration commands. It
does not repeat the integration tests. The fast unit job also runs on its own
in CI to report failures early.

The coverage lane writes text, JSON summary, and LCOV reports under
`coverage/`. Each covered file must keep at least 80% statement and line
coverage, 60% branch coverage, and 90% function coverage. The check applies
the floor to each file.

Pull request jobs run a fast unit check, the combined core coverage check,
and browser tests from a clean Ubuntu checkout. They use local Git repos, fake coding agents, and
checked-in browser data. A weekly job runs the platform lane on macOS 15 and
Windows 2025. You can also start the **Test lanes** workflow by hand.

For the supported browser, screen, and device checks, see [Mobile
support](/mobile).

## Use a clean checkout

Each checkout owns its dependencies and live data. Use the same commands in
the main checkout, a linked worktree, and a Codex-managed worktree:

```sh
corepack enable
corepack pnpm run setup
corepack pnpm run check
```

Do not copy `node_modules`, `.cache/`, or a generated diff snapshot into a
worktree. `pnpm run setup` creates the dependency tree from the lock file. The
app falls back to checked-in demo data until you create live data locally.

### Linked Git worktree

Create and verify a linked worktree from the main checkout:

```sh
git worktree add -b feature/my-change ../diffsplain-my-change HEAD
cd ../diffsplain-my-change
corepack enable
corepack pnpm run setup
corepack pnpm run check
```

Before removal, inspect and commit, move, or discard its changes. Then remove
the worktree from the main checkout:

```sh
git -C /path/to/diffsplain status --short
git -C /path/to/diffsplain worktree remove ../diffsplain-my-change
```

Use `git worktree remove --force` only after you have dealt with changes.

### Codex-managed worktree

Choose **Worktree** when starting a Codex task, then run the same setup and
check commands above. Codex worktrees start from tracked files. This project
does not need `.worktreeinclude`, because setup does not depend on ignored
files. Use Codex handoff when you need the same branch in the main checkout.

### Codex cloud

Set the Codex cloud setup and maintenance scripts to:

```sh
corepack enable
corepack pnpm run setup
```

Then run this from the cloud task:

```sh
corepack pnpm run cloud:check
```

`cloud:check` runs the clean-checkout gate and the provider/browser tests.
Those tests use fake coding providers and a fake browser command. Real Codex,
Claude, Copilot, Cursor, OpenCode, GitHub, or browser login is optional and is
needed only for a live integration task. Without a configured default or
`--agent`, an interactive terminal lists usable agents in this order: Codex,
Claude, Copilot, Cursor, then OpenCode. You choose one. Non-interactive runs need a configured default,
`--agent NAME`, or `--no-agent`. Cursor Agent 2026.08.11 or newer uses
the signed-in Cursor CLI in the user's home. It still contacts the Cursor
service. Keep credentials in Codex environment settings, not checked-in rules
or scripts.

To test the linked-worktree path itself, run:

```sh
corepack pnpm run setup:smoke
```

It creates a temporary linked worktree, checks that no dependencies or live
snapshot were copied, runs setup, then removes the worktree. Commit or stash
changes to package install files first, so the test checks the same files.

## Trust repo automation

The checked-in Codex hook manifest runs no commands. This keeps a branch from
using that manifest to approve its own code. Review `.codex/`, `.agents/`,
`AGENTS.md`, and `skills-lock.json` at the commit you plan to use. After that
review, you may run the vendored UI check yourself:

```sh
node .agents/skills/impeccable/scripts/hook.mjs
```

Do not put this command back in the repo-owned hook manifest. Use a user-owned
hook outside the checkout if you want it to run on each edit.

`skills-lock.json` records the source, version, path, and content hash for
vendored skills. Pull requests that change a hook manifest, vendored automation,
agent instructions, or this lock file request the automation owner's review.
The `Automation trust review` check reads the pull request through the GitHub
API without checking out branch code. It passes after a maintainer adds the
`automation-reviewed` label.

## Codex tool profiles

Diffsplain keeps developer integrations separate from product summary agents.
The checked-in `.codex/config.toml` has no secrets or enabled integrations.
After you trust the checkout, copy the developer template to your Codex home:

```sh
cp .codex/developer.config.toml.example "$CODEX_HOME/developer.config.toml"
```

Use `codex --profile developer` for optional OpenAI developer docs MCP access
and the GitHub plugin. Docs tools can run without a prompt; GitHub tools ask
first.

Do not use a named profile alone as a no-tool boundary. Profiles merge MCP
servers and plugins from the base user config. For a manual no-tool Codex run,
replace those maps at runtime and skip user config:

```sh
codex exec --ephemeral --ignore-user-config --ignore-rules \
  --sandbox read-only \
  --config 'mcp_servers={}' \
  --config 'plugins={}' \
  --config 'web_search="disabled"'
```

`diffsplain` uses `snapshot-only` access for remote targets, exact ranges, and
`--no-checkout-access`. It runs agents in the temporary snapshot folder with
provider-specific snapshot safeguards, a small runtime-only environment, and
no credential variables. Authentication remains available to the client
itself.

For the current checkout, `--worktree`, and `--base REF` without `--head`, it
uses `checkout-read-only` access. The agent runs in the chosen repo with normal
user settings so it can inspect ignored files, Git history, and symlink
targets. The prompt forbids edits and mutating commands; approval stays with
the user. Codex keeps its read-only sandbox, Claude uses permission mode
`plan`, and Cursor uses Ask mode with its sandbox, trust, and workspace.
Copilot and OpenCode have no proven native read-only mode, so Diffsplain warns
before either gets checkout access.

## Measure the pipeline

Run the deterministic speed and note-quality gate locally:

```sh
pnpm run benchmark:gate
```

It measures build, summary, presentation transport, agent startup, and restart
separately. The presentation metric stops when the local transport is ready;
it is not a browser rendering measure. Agent startup covers the interval from
that ready snapshot to the first coding-agent request. The gate runs one
warm-up and five measured samples for both the working and held-out fixtures.
It compares each median with the limit in
`benchmarks/performance-baseline.json` and records all samples plus their
minimum, median, p95, and maximum.

The same gate scores fixed useful, incomplete, false, duplicate, and empty note
cases from `benchmarks/quality-fixtures.json`. Full and summary runs also score
the notes that the fixed fake provider produced for the working and held-out
pipelines. A failure names the fixture, speed measure, limit, or rubric item
that missed its target.

To run one speed measure, pass its name:

```sh
pnpm run benchmark:gate -- --case summary
```

The `Live provider canary` workflow runs one paid-provider sample each week and
on manual request. It has a ten-minute job limit, needs the
`OPENAI_API_KEY` repository secret, and does not run on pull requests. Run the
same bounded canary from an authenticated checkout with:

```sh
pnpm run benchmark:canary -- --runs 1 --reasoning minimal
```

## Measure live updates

Run the live-update benchmark by hand when changing event delivery or polling:

```sh
pnpm run benchmark:live-update -- --mode events
pnpm run benchmark:live-update -- --mode poll
```

These commands report timing samples. They do not enforce a speed threshold
and are not part of the test suite. Server and browser tests check live-update
behavior and recovery.

## Publish a release

Use the GitHub Actions `Release` workflow for normal releases. Run it from
`main` and enter one exact version such as `0.11.0` or `0.11.0-beta.1`. The input
must be canonical SemVer: ranges, `patch` or `minor` aliases, surrounding
whitespace, build metadata, and shell syntax are rejected.

Before the first run, configure the npm package's trusted publisher with this
exact tuple:

- provider: GitHub Actions
- organization or user: `itsjling`
- repository: `diffsplain`
- workflow filename: `release.yml`
- environment: `npm-publish`
- allowed action: `npm publish`

Create the protected `npm-publish` environment in GitHub. Allow this workflow's
`GITHUB_TOKEN` to update `main` and release tags under your branch and tag
rules. Configure any required environment reviewers. These npm and GitHub
settings are prerequisites; the repository does not prove that they exist.

The workflow prepares and verifies the release without repository write access
or an npm identity. It passes the tarball, verification receipt, release commit,
and tag to a separate protected job. Only that final job receives GitHub write
access and the npm OIDC identity. It independently checks the artifact, pushes
the exact version commit and tag atomically, and publishes the tarball with
package lifecycle scripts disabled. Stable versions use the `latest` npm tag
and prereleases use `next`. Releases are serialized so two workflow runs cannot
publish concurrently.

### Recover a workflow release

Rerun the failed publish job in the existing workflow run, or start a new run
with the same exact version after a failure. The publish-job rerun retains the
original verified artifact and dispatch commit, and is accepted when that
commit is the parent of the pushed release. Rerunning every job from an older
dispatch is rejected after `main` advances; start a new workflow run instead.
The workflow accepts only these states:

- **Create:** `main` still contains the previous package version, the requested
  tag and npm version do not exist, and local `main` matches `origin/main`.
- **Resume:** the requested package version and tag are already on the same
  commit at `origin/main`, but npm does not contain the version.
- **Complete:** Git is in the resume state and npm contains the version. The
  workflow rebuilds the verified tarball and succeeds only when its SHA-512 SRI
  matches npm's `dist.integrity`.

A moved branch, conflicting tag, local-only version commit, or mismatched npm
tarball is a mixed state and stops the workflow. Inspect and reconcile Git and
npm before rerunning. If the atomic push failed and left only a local commit and
tag in the runner, start a new run; if reproducing locally, delete or reconcile
those local refs without overwriting remote history.

### Publish locally as a fallback

Use local publication only when the GitHub workflow cannot be used. Start from
a clean `main` checkout, update the version, and create its commit and tag. Pass
the exact intended version to `pnpm version`:

```sh
corepack pnpm version 0.11.0
```

Do not push yet. Verify the tagged commit and build the release tarball:

```sh
corepack pnpm run release:verify
```

This command requires a clean tree and a `v<version>` tag on `HEAD`. It runs the
product gate without docs, installs and tests the exact tarball in a temporary consumer,
and records the commit and tarball hash in `.cache/`.

Sign in to npm when needed, then publish by naming the exact version again:

```sh
COREPACK_ENABLE_PROJECT_SPEC=0 corepack npm@11.5.1 login --auth-type=web
corepack pnpm run release:publish -- 0.11.0
```

The publish command checks the clean tree, tag, commit, tarball hash, npm login,
and registry before it publishes. Local publication does not use the workflow's
trusted-publisher identity or add npm provenance.

After npm confirms the version, push the version commit and tag:

```sh
git push origin main --follow-tags
```

If verification fails, fix the cause and run it again. If publication fails,
keep the same version and tag, fix the npm login or named error, and rerun only
`release:publish`. If npm accepted the package but the Git push failed, push the
existing commit and tag without publishing again.

Dependency security overrides live in `pnpm-workspace.yaml`. Their advisory
references and re-evaluation steps are recorded in
[the dependency override register](https://github.com/itsjling/diffsplain/blob/main/docs/dependency-overrides.md).
Run `pnpm audit` and `pnpm audit --prod` for current findings; docs builds use
committed content and do not handle user submissions.

## Repository security

For dependency updates, main branch rules, the release push identity, and
admin recovery, see the [repository security runbook](https://github.com/itsjling/diffsplain/blob/main/docs/repository-security.md).

## Refresh the demo

`site/todo-demo.js` holds the ten sample files used by the landing page and the
app demo. After changing it, rebuild the checked-in data:

```sh
node scripts/write-todo-demo.mjs
```

To build a live snapshot from another repo:

```sh
corepack pnpm run snapshot -- --repo /path/to/repo
```

The writer stores live data in the ignored `.cache/diff-data.json`. The local
server exposes it at `/diff-data.json`.

## Run the docs

The docs use [Blume](https://useblume.dev/) and live under `docs/`.

```sh
corepack pnpm run docs:dev
```

Build or preview the static output:

```sh
corepack pnpm run docs:build
corepack pnpm run docs:preview
```

Blume writes its output to `docs/dist/`. The Pages workflow publishes it under
`/diffsplain/docs`.

## Project paths

| Path | Purpose |
| --- | --- |
| `app/` | React review page. |
| `scripts/` | CLI, Git data writer, note writer, and local server. |
| `public/` | Checked-in app demo data. |
| `site/` | Static GitHub Pages landing page. |
| `docs/` | Blume product docs. |
| `data/` | Checked-in demo notes. |
