diffx is a Go server with a React/Vite web application embedded into the release binary. Node and pnpm are build and test dependencies only.
Install the latest development changes from main with Go 1.25 or later:
go install github.com/flexdinesh/diffx/cmd/diffx@main- mise 2026.8.6 or later
- C compiler for Go race tests
- Git
- Bash (included with Git for Windows)
Mise manages Go 1.25, Node from .node-version, and pnpm from package.json.
After cloning, trust the project config, install tools, then install dependencies,
hooks, and Playwright Chromium:
mise trust
mise install
mise run setupmise run activates project tools without shell activation. For direct pnpm
commands, activate mise in your shell or use mise exec -- pnpm <script>.
List available tasks with mise tasks. Keep personal overrides in
mise.local.toml, which is ignored by Git.
| Purpose | Command |
|---|---|
| Start Vite HMR with a managed Go fixture server | mise run dev or pnpm dev:web |
| Start a standalone Go fixture server with built web assets | mise run dev:server |
| Generate the TypeScript API types | mise run generate |
| Build the web application | mise run web:build |
Build the dependency-free CLI at dist/diffx |
mise run build |
| Build and locally install the CLI | mise run install |
| Build agent plugin adapters | mise run plugins:build |
| Start the container server with persistent SQLite | mise run docker:up |
| Stop the container server, preserving SQLite | mise run docker:down |
| Install Playwright Chromium | pnpm test:browser:install |
| Run web unit and browser tests | pnpm test:web |
| Run Go tests | mise run test:go |
| Run boundary and shared-rule contracts | mise run test:contracts |
| Check production package dependencies | mise run check:boundaries |
| Check frontend design-token use | mise run check:design |
| Run distribution API conformance tests | mise run test:conformance |
| Run TypeScript checks | pnpm typecheck |
| Run JavaScript linting | pnpm lint |
| Format supported files | pnpm format |
| Run every repository check | mise run check |
| Run all pre-push checks, including Go race tests | pnpm check:push |
| Run lightweight CI checks | mise run check:ci |
Set DIFFX_TEST_PORT for a separate browser-test server (default 4173), e.g. DIFFX_TEST_PORT=4183 mise exec -- pnpm --filter @diffx/web test:browser.
mise run setup enables the Husky pre-push hook. Every push runs
mise run check:push: all repository suites, boundary contracts and Go race tests, then rejects
uncommitted generated API types or embedded assets. mise run setup installs
Playwright Chromium; reinstall it after upgrading Playwright.
The hook needs mise on PATH; mise activates project tools. Checks run on your
local platform.
Both CI and release verification run mise run check:ci: static checks, web and
CLI builds, generated-file consistency, and the distribution API smoke test.
Local mise run check adds dependency/design guards, behavioral contracts,
JavaScript unit/browser suites, release-tool tests and all Go tests; pre-push
adds Go race tests. Keep these full suites and guards local. Chromium is installed
by local setup, not CI/release workflows. CI does not run a native OS test matrix.
GitHub's ci-required ruleset requires the lightweight checks status on an
up-to-date main change, including administrators; it is configured in GitHub,
not by mise. PR evidence records full local validation.
mise run install embeds the current web build and installs diffx into
$GOBIN, or $GOPATH/bin when GOBIN is unset. Ensure that directory is on
PATH.
The production web build is committed under internal/webui/dist so installs
from tags and main contain the complete application. Run mise run web:stage and
commit asset changes after modifying the frontend.
Development uses test/fixtures/sample.diff with in-memory review state. The
Vite server starts and stops its own Go fixture process, so frontend development
does not need a real repository or persisted data.
Start the fixture server with built web assets:
mise run dev:server -- --no-browserdev --fixture collects its fixture before starting in the foreground and does
not use personal lifecycle discovery. sync collects only the selected checkout
against the default branch merge base, plus working changes. Hooks collect only
the triggering checkout. Use
--base HEAD for working changes only, and --branch for explicit object-only
recovery. sync submits to one configured destination and exits.
Redirected stdin (git diff | diffx) starts a foreground local process with
a fixed patch and ignores remote config. diffx [PATH] likewise collects
once, defaults to the current directory, and opens a fixed checkout snapshot.
Neither mode watches Git or automatically replaces the selected snapshot.
A path argument or
--path cannot be combined with redirected stdin.
CollectPatch preserves the supplied patch without Git lookup;
its absolute submission directory is provenance only, not repository identity.
Use isolated
runtime directories (DIFFX_RUNTIME_DIR) and in-memory state for lifecycle tests; fixture processes
must not register inputs in the personal service. DIFFX_EXIT_ON_STDIN_CLOSE
is a foreground development-process lifecycle hook.
Stop running processes before upgrading. Current schema 9 databases survive
restart; incompatible schemas are refused without resetting data. To start
fresh, back up the state directory while stopped and explicitly choose a new
database path. In-memory state does not survive restart. Server retentionDays defaults to seven; fresh
submissions apply the current setting, while exact retries preserve expiry.
For isolated production-user tests, start diffx-server with a temporary
persistent database. Initial admin credentials live in <database>.admin-token;
startup reports its path. Stop the server before user create --name NAME --state DB, save its printed token and restart. Users share one database, while credentials
scope REST, MCP and events. Do not provision against the running personal service.
Build both binaries with mise run build, then run server mode directly:
DIFFX_HOST=127.0.0.1 DIFFX_PORT=7981 DIFFX_STATE=./test-data/state.db ./dist/diffx-serverOr use the checked-in Compose setup:
mise run docker:up
docker compose logs server
docker compose cp server:/data/state.db.admin-token ./admin-token
mise run docker:downThe same server binary handles authentication, durable admission, workers and
queries in both setups. The image uses committed embedded assets; run
mise run web:stage before rebuilding it after UI changes.
| Setting | Binary default | Container default | Flag |
|---|---|---|---|
DIFFX_HOST |
127.0.0.1 |
0.0.0.0 |
--listen HOST:PORT |
DIFFX_PORT |
7981 |
7981 |
--listen HOST:PORT |
DIFFX_STATE |
data/state.db |
/data/state.db |
--state PATH |
DIFFX_ACCOUNT |
admin |
admin |
--account NAME |
DIFFX_RETENTION_DAYS |
7 |
7 |
--retention-days DAYS |
DIFFX_TOKEN |
Generated for persistent state | Generated for persistent state | None |
DIFFX_TOKEN supplies initial bootstrap credentials only; existing credentials
are never replaced. Omit it to generate a private <database>.admin-token file.
memory and :memory: disable persistence and require an explicit token.
Precedence is defaults → explicit JSON file → env vars → explicit flags. Use
--config-file FILE or DIFFX_CONFIG_PATH to select a server config. The file
supports host, port, state, account and retentionDays. It must exist and
can be mounted read-only; startup never creates it or a config lock. Personal
CLI config, DIFFX_RUNTIME_DIR, producer URL settings and external web assets
do not configure server mode. Restart to apply changes.
Compose keeps the SQLite database, sidecar files, ownership lock and generated
credential in the diffx-data volume. The root filesystem is read-only; the
image prepares /data for UID/GID 10001. If using a bind mount instead, make
the directory writable by that identity. Keep database files inside /data.
mise run docker:down preserves the volume; docker compose down --volumes
explicitly deletes it. Stop the server and back up the whole directory before
starting with a fresh database; a volume is not a backup.
Set DIFFX_PORT=7982 mise run docker:up for a different published and internal
port. Compose also accepts DIFFX_ACCOUNT and DIFFX_RETENTION_DAYS overrides.
For other settings, use image env overrides or a Compose override file.
Only one server may own a SQLite database; competing starts fail rather than
share ownership. SIGTERM stops HTTP and workers before releasing storage;
unfinished admitted jobs recover from the durable queue after leases expire.
This setup targets one server with SQLite. PostgreSQL, multiple replicas, browser login and integrations remain separate future changes.
The production build generates API types, builds the web application, stages the
Vite output under internal/webui, and embeds those assets into the Go binary.
The resulting dist/diffx and dist/diffx-server executables have no
Node runtime dependency. Git is required only for producer-side checkout
collection. Local/remote server queries read SQLite and never invoke Git.
The OpenAPI contract in packages/api/openapi.yaml is the frontend/server
boundary. After changing it, run:
mise run generateFollow DESIGN.md. Semantic tokens in apps/web/src/tokens.css
map into Tailwind and shadcn theme roles. Reuse primitives from
apps/web/src/components/ui; keep authored CSS for specialized layout, dynamic
geometry, and Pierre's measured rendering boundary.
App.tsx composes page sections. AppProvider composes appearance, sidebar,
and workspace owners. Consumers subscribe through domain hooks for diff source,
navigation, draft, review, reviewed files, and collapse state. One draft persists
across scopes. Context selection uses the top-bar popup picker (also Cmd/Ctrl+K)
and scopes every request. The “Switch review” picker first selects a repository
or Piped, then an immutable observation. Repository observations retain branch,
worktree, source/run, comparison, and collection-time metadata. Submission/session
associations remain separate from original snapshot metadata; catalog API filters
include harness, sessionId, sessionName and legacy runId. Global search is labeled
“Search reviews” and includes repository, branch, worktree, hostname, and source/run
labels. Piped history uses “Search Piped”, with collection timestamps newest first
and no repository filters. The header identifies these reviews with a terminal
icon, “Piped”, and collection time, without repository, branch, worktree, or directory.
The optional API Context.source distinguishes local and stdin independently
of its identity. Catalog presentation masks repository identity and stale state
on stdin observations. Repository
freshness, host, branch, worktree, and status filters never exclude Piped imports.
Catalog reads query stored metadata only; ingestion events, reconnect, and tab
visibility reload the catalog without replacing the selected observation.
Opening a review never discovers worktrees or reads Git. Repository results default to
Latest snapshots with changes. All / Latest / Stale filters freshness; the
right-aligned All checkbox also includes unavailable, empty, and unknown-status
snapshots. A newer collection supersedes older snapshots for the same owner,
source, repository, checkout, branch and comparison policy; arrival order breaks collection-time
ties. Freshness uses a durable stream head, including deduplicated collections and
observations outside the current search or page. Expiry and pruning leave that head
intact, so older retained snapshots stay stale. A newer collection of previous
content makes its existing review latest again; its original snapshot metadata stays fixed.
Changed observations come first, ordered by last submission time; counts
describe their fixed snapshots. Host, Branch, and Worktree filters apply before
repository grouping. Unavailable entries are disabled and skipped during keyboard
navigation. Filter selections persist while navigating and reopening the picker.
Switching disposes the previous workspace's request ownership and resets transient
state.
The header's delete action removes the selected snapshot, all
its stored diff scopes, comments, and reviewed-file marks after confirmation.
Deletion preserves stream freshness history; older snapshots remain stale.
It leaves Git files untouched, and a new collection can create a review again.
The footer shows the selected scope's actual baseline. Its Diff comparison popover
explains the calculation and exposes the recorded base ref, base commit, merge base,
and HEAD with full commit IDs. These facts are persisted in observation metadata
and returned by GET /api/v2/contexts/{contextId} as observation.comparison
and observation.head; reading them never resolves current Git refs.
DiffWorkspace.tsx owns Pierre rendering,
versions, worker options, and measured geometry. use-review.ts and
use-reviewed-files.ts own their REST requests and recovery; reads cannot settle
across writes or owner disposal. Keep section-only state within its component.
See architecture.md for repository boundaries and dependency rules.
Shared producer orchestration in cmd/diffx resolves config → environment →
flags once, pins retries to destination/database identity and serves manual/hook
delivery. Hooks use session-scoped acknowledgements plus server reconciliation;
their private pending data retains a separate seven-day expiry. REST and MCP
compose reviewservice; global /mcp provides catalog discovery and stored
diff/patch retrieval with explicit context IDs.
See plugins.md for native harness installation, asynchronous
checkout sync, configuration precedence and hook diagnostics. Normal builds and
checks compile the adapters; host SDK dependencies are development types only.
Pi and OpenCode's self-contained dist/index.js artifacts are committed so a
clone is ready to install without a build. After changing adapter source, run
mise run plugins:build and commit the regenerated artifacts. Codex and Claude
load their checked-in hook definitions through the root marketplace manifests.