GitHub PR-style local diff viewer. Monorepo with three apps and one shared package.
# Development
pnpm run dev # Start all apps via Turbo
pnpm run build # Build all apps
pnpm run check-types # TypeScript check across all workspaces
pnpm run test # vitest across workspaces
pnpm --filter diffhub test -- lib/git.test.ts --reporter=dot # one file, quiet
# Quality
pnpm run lint # oxlint via Turbo
pnpm run lint:fix # oxlint --fix via Turbo
pnpm run format # oxfmt --write via Turbo
pnpm run check # ultracite check (lint + format)
pnpm run fix # ultracite fix (lint + format --fix)Run all commands from the monorepo root. Do not cd into individual apps for routine tasks.
diffhub/
├── apps/cli/ # Diff viewer + CLI npm package `diffhub` (see apps/cli/AGENTS.md)
├── apps/web/ # Marketing site (Next.js, deploys to Vercel)
├── apps/docs/ # Documentation MDX content (deploys to blode.md)
├── packages/diff-core/ # Shared viewer: streaming, CodeView wiring, themes, worker pool, chrome
├── turbo.json # Task pipelines
├── pnpm-workspace.yaml # Root workspace (pnpm workspaces)
└── package.json
apps/docs/ is pure MDX content with no package.json; it is not a pnpm workspace. Deploy with cd apps/docs && pnpm dlx blodemd push docs.
Each workspace has its own AGENTS.md with boundary rules specific to it: apps/cli/AGENTS.md, apps/web/AGENTS.md, apps/docs/AGENTS.md, packages/diff-core/AGENTS.md. Codex only reads AGENTS.md files from the repo root down to its current working directory, so an agent editing a package should read that package's AGENTS.md first, not just this root file.
- No inner lockfile:
apps/cli/package-lock.jsonmust not exist; only the rootpnpm-lock.yamlis used. If a lockfile appears there, delete it and runpnpm installfrom root. - Changesets gate PRs: CI runs
pnpm exec changeset status --since origin/main, so a PR that touches a workspace package (even its AGENTS.md) needspnpm exec changeset, orpnpm exec changeset add --emptywhen nothing ships. format:checkis not read-only in diff-core: its script isoxfmt ., which rewrites files. Usepnpm run checkfor a read-only format check.- CLI dev uses portless:
pnpm run devinapps/cliserves athttps://diffhub.localhost. The marketing site (apps/web) serves athttps://diffhub-web.localhost. - CLI uses standalone build:
bin/diffhub.mjsruns.next/standalone/apps/cli/server.js(notnext start). Build it withpnpm --filter diffhub run prepack, which runsnext buildand then copies.next/static/andpublic/into.next/standalone/.pnpm run buildalone leaves the CLI without static assets. - Env for dev: Set
DIFFHUB_REPOinapps/cli/.env.localto point at a real git repo when developing. Without it, the diff API defaults toprocess.cwd(). - Marketing site proxies docs:
apps/web/app/docs/[[...slug]]/route.tsproxies/docs/*tohttps://diffhub.blode.md/docs/*throughapps/web/lib/docs-proxy.ts, which rewrites the upstream's/_docs/_next/asset URLs to/diffhub/docs/_chunks/*so they stay inside the zone prefix blode.co forwards to us. This will 404 until the docs site is deployed via blodemd. - Docs assets track blode.md, not this repo:
UPSTREAM_ASSET_PREFIXindocs-proxy.tsis the platform's Next.jsassetPrefix. It has changed under us once, which left every docs page unstyled. Chunks are also served only from the apexblode.md; the tenant hostdiffhub.blode.mdemits those URLs but 404s on them. If the docs render with no CSS, diff the upstream HTML's asset paths againstUPSTREAM_ASSET_PREFIXfirst. - Docs
<head>metadata is rewritten too: the upstream pointsllms.txt,llms-full.txt, the manifest and the icons at root-absolute paths. Under the proxy those resolve to blode.co's own copies and answer 200, so nothing 404s and no crawler flags it, but a DiffHub docs page ends up advertising the personal site's files.ROOT_URL_REWRITESindocs-proxy.tsrepoints them. If the platform adds another root-absolute<head>link, it needs an entry there, and the target has to be a URL that already exists: pointing at a zone path that 404s is worse than the wrong-content 200 it replaced.
Three options, in order of preference:
/next-browser: Vercel's Next.js skill. Persistent Chromium with React DevTools; returns console, network, component tree, and PPR shell analysis as structured text. Install withnpx skills add vercel-labs/next-browser -g -a claude-code -y.apps/cli/.next/dev/logs/next-development.log:browserToTerminal: trueis set inapps/cli/next.config.ts, so Next.js forwards browser errors into this log.Readit whilenpm run devis running.mcp__claude-in-chrome__read_console_messages: for the published standalone build or any non-dev URL. Requiresportless trustfirst, or the extension silently fails onhttps://diffhub.localhostcerts.
CI (.github/workflows/ci.yml) runs changeset status, then:
pnpm run lint && pnpm run check-types && pnpm run test && pnpm run buildAll four pass on main as of 27 Sep 2026. pnpm run check does not: apps/cli/README.md and .captain/browser/report.md are unformatted, and CI does not run it, so judge it by the files you touched. For a viewer change, prove it in the real app:
pnpm --filter diffhub run prepack
node apps/cli/bin/diffhub.mjs serve --port 2099 --no-open --repo "$PWD"
curl -s localhost:2099/api/files # JSON file list; /api/diff streams text/plainThere is no pnpm run doctor, pnpm run verify, or feature map. Gap: nothing scripts the build, serve, and API or browser check above, so UI behaviour (scroll anchoring, comments, themes) is proven only by the unit tests and by hand; apps/web has a Playwright test:instant that CI does not run.
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the turbo package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run node -p "require.resolve('turbo/package.json')" from a workspace that depends on turbo.
Read docs/README.md inside that installed package first, then read the relevant pages from its docs/ directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
This block is written and re-added by turbo before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in crates/turborepo-cli/src/cli/agent_guidance.rs. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set "agentGuidance": false in the root turbo.json or turbo.jsonc to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation.