Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Behavior owner and resulting change:
- Contracts/invariants affected; local/remote implications:
- Validation results; remaining risks:
- Intentional architecture/design changes and rationale (if any):
- UI evidence: affected themes/layouts, keyboard use, Pierre geometry (if applicable):
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,27 @@ Go CLI/server in `cmd/` and `internal/`; embedded React/Vite app in `apps/web/`.
- Use pnpm. Install dependencies with `pnpm install --frozen-lockfile`.
- Use mise tasks or pnpm scripts for project commands; toolchain and orchestration live in `mise.toml`.
- Run relevant checks for changes; `mise run check` runs all checks.
- Keep full suites and architecture/design guards local through the pre-push hook; keep CI/release verification lightweight.
- After frontend changes, run `mise run web:stage` and commit `internal/webui/dist`.
- See `docs/development.md` and `docs/release.md` for workflows.

## Design and architecture

- Before editing, read `docs/architecture.md` and relevant `docs/system.md` sections.
- For frontend changes, also read `DESIGN.md` and frontend conventions in `docs/development.md`.
- Identify the behavior owner, affected contracts, and applicable invariants before choosing an implementation.
- Extend the owning module; encapsulate behavior behind its public contract.
- Share semantic rules, not merely similar code. Avoid speculative abstractions.
- Compose local/remote policies at entry points; keep mode branches out of domain operations.
- Define interfaces beside consumers; expose only required operations.
- Keep transports thin. Services own application rules; storage owns atomic persistence.
- Preserve ownership, authorization, identity, retry, cancellation, and durability contracts.
- Reuse UI tokens and primitives; preserve Pierre's rendering and measured geometry.
- Verify affected behavior at boundaries, including both modes when shared behavior changes.
- Update canonical docs and checks with intentional design changes; explain the changed requirement.
- Never weaken a check solely to accommodate a violating implementation.
- Before completion, review the diff against applicable design rules; report validation and remaining risks.

## Git etiquette

- Before pushing to any remote, run all CI checks locally with `mise run check`; resolve failures.
Expand Down
26 changes: 26 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,32 @@ Before adding a value or visual pattern:
7. Are keyboard focus, contrast, labels, and hit areas correct?
8. Does this follow `DESIGN.md`? If a new role is necessary, update it here.

## Enforcement

`mise run check:design` scans CSS, TypeScript and TSX under `apps/web/src`,
including new nested files, static utilities, inline style objects, SVG colors
and Pierre's `unsafeCSS`. Local `mise run check` and pre-push run the guard
and its contracts. It rejects literal palettes outside `tokens.css` definitions,
independent typography, literal radii/elevation, and arbitrary padding/margin/gap
lengths. Existing ordinary Tailwind numeric spacing utilities remain allowed;
their relationship to the spacing scale needs review. Compose authored values
from the canonical tokens. Zero, automatic spacing
and 1px optical insets remain valid; dimensions, breakpoints and dynamic measured
geometry require review rather than a blanket literal-value ban.

The guard has exact exceptions for status circles, SVG monogram units, icon line
boxes, compact comment counts, the overlapping sidebar resizer, and Pierre's
line-surface mixes/inset strokes. Existing generated tabs' 3px inset and active
shadow, and the dialog title's unit line-height, are bounded legacy exceptions;
do not copy them into new controls. Changes to exceptions need a concrete role,
rationale and contract test in the same PR. Do not exempt entire components.

Static checks cannot prove responsive behavior, accessibility, visual hierarchy
or correctness of styles assembled at runtime. Use existing browser tests and
review affected themes, narrow layouts and keyboard interaction. Changes to
Pierre geometry also require selection, annotation, expansion, wrapping and
scrolling checks. Record this evidence in the PR template.

## Audit baseline and consolidation

This audit records the starting points, not permission to reuse legacy values.
Expand Down
11 changes: 8 additions & 3 deletions apps/web/test/browser/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ test("renders and filters a piped diff", async ({ page }) => {
test("file navigation scrolls to and retriggers a destination cue", async ({
page,
}) => {
await page.clock.install({ time: new Date("2026-01-01T00:00:00Z") });
await page.setViewportSize({ width: 1280, height: 420 });
await page.goto("/");

Expand All @@ -227,8 +228,11 @@ test("file navigation scrolls to and retriggers a destination cue", async ({
.filter({ hasText: "src/components/Badge.tsx" });
const header = target.locator("[data-diffs-header]");
await expect(header).toContainText("src/components/Badge.tsx");
// Host round trips must not consume the cue's one-second lifetime.
await page.clock.pauseAt(new Date("2026-01-01T01:00:00Z"));

await file.click();
await page.clock.runFor(100);
await expect(file).toHaveAttribute("aria-current", "true");
await expect(
target.locator("[data-diffs-header][data-navigation-target]"),
Expand All @@ -244,12 +248,13 @@ test("file navigation scrolls to and retriggers a destination cue", async ({
})
.toBeLessThanOrEqual(1);

await page.waitForTimeout(700);
await page.clock.runFor(700);
await file.click();
await page.waitForTimeout(400);
await page.clock.runFor(400);
const cue = target.locator("[data-diffs-header][data-navigation-target]");
await expect(cue).toHaveCount(1);
await expect(cue).toHaveCount(0, { timeout: 1_000 });
await page.clock.runFor(700);
await expect(cue).toHaveCount(0);
});

test("file frames preserve measured gutters, gaps, and virtual geometry", async ({
Expand Down
39 changes: 36 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ personal lifecycle discovery.
- `httpapi`, `mcpapi`: transport parsing, projection and error mapping.
- `serverapp`: transport/task composition; `daemon`: local lifecycle;
`remoteserver`: remote authentication and worker composition.
- `config`, `processlock`, `browser`, `version`: configuration, process ownership,
browser launch and build metadata; `processmetrics`: server process metrics;
`webui`: embedded assets. `testsupport` is test-only.

Interfaces live beside consumers and require the needed operation. Optional
interface assertions must not change application behavior. Storage contracts
Expand Down Expand Up @@ -76,9 +79,39 @@ describe atomic application operations, not independently committed CRUD steps.
## Contracts and evolution

`mise run test:contracts` runs production dependency checks, storage/lifecycle/
queue/transport boundaries, shared Go/TypeScript comment examples and real HTTP
schema validation. Run locally or wherever useful; pre-push invokes it.
CI configuration does not enforce this suite.
queue/transport boundaries, frontend token guards, shared Go/TypeScript comment
examples and real HTTP schema validation. Local `mise run check` and pre-push
run these checks alongside the web unit/browser and plugin/shared suites.
`test:contracts:rules` reuses built binaries so the full local check does not
rebuild or repeat the distribution smoke test and JavaScript suites. CI and
release verification retain static checks, builds, generated-file consistency
and the distribution API smoke test; full suites and guards stay local.

`mise run check:boundaries` classifies every package under `cmd/` and `internal/`
using `tools/check-boundaries`. Unclassified packages, forbidden dependencies and
production imports of `testsupport` fail. Process execution and SQL access stay
with their explicit owners. The checker inspects production imports for the
current build platform; tests may import adapters to exercise their contracts.

## Making changes

Before implementation, identify the behavior's owner, consuming contract and
invariants from this document and [system.md](system.md). Put shared rules in
their existing owner and compose delivery/lifecycle differences at entry points.
For shared changes, verify both compositions through existing boundary tests;
add a regression test when coverage is missing. Check semantic outcomes rather
than private implementation shape.

The PR template records ownership, contract/mode implications, validation and
intentional design changes. Change a boundary policy only for a concrete new
responsibility, updating its rationale and tests in the same PR. Passing checks
does not establish cohesive modules, correct DRY abstractions or visual quality;
those remain review responsibilities. `main` requires the GitHub Actions `checks`
status through the `ci-required` ruleset. That status covers lightweight CI;
architecture/design guards and full-suite validation are enforced locally by
pre-push and recorded in the PR evidence.

## Adapter and schema evolution

New adapters must preserve ownership, atomic publication, retention, replay,
lease fencing and commit-before-ack recovery. External brokers require atomic
Expand Down
11 changes: 9 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ List available tasks with `mise tasks`. Keep personal overrides in
| 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` |
Expand All @@ -67,8 +69,13 @@ 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.
Unit, browser, release-tool, and Go
race suites run locally before pushing. CI does not run a native OS test matrix.
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 `servediff` into
`$GOBIN`, or `$GOPATH/bin` when `GOBIN` is unset. Ensure that directory is on
Expand Down
30 changes: 20 additions & 10 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,29 +126,39 @@ run = [
description = "Run all repository checks and test suites"
run = [
{ task = "check:ci" },
{ task = "test:contracts:rules" },
"pnpm test",
"pnpm test:release",
{ task = "test:go" },
]

[tasks."test:contracts"]
description = "Run architecture, boundary, shared-rule and real HTTP contracts locally"
description = "Build and run architecture, boundary, shared-rule and real HTTP contracts"
depends = ["build"]
run = [{ task = "test:contracts:rules" }, "pnpm test:conformance"]

[tasks."check:boundaries"]
description = "Reject unclassified packages and forbidden production dependencies"
run = "go run ./tools/check-boundaries"

[tasks."check:design"]
description = "Check frontend design-token use"
run = "node tools/check-design.ts"

[tasks."test:contracts:rules"]
description = "Run dependency, design-token, behavioral and shared-rule contracts without rebuilding"
run = [
"go run ./tools/check-boundaries",
"go test ./internal/review ./internal/session ./internal/contextservice ./internal/reviewservice ./internal/reviewstore ./internal/controlapi ./internal/ingestion ./internal/ingestionqueue ./internal/submission ./internal/serverapp ./internal/remoteserver ./test/conformance",
{ tasks = [
"check:boundaries",
"check:design",
] },
"go test ./tools/check-boundaries ./internal/review ./internal/session ./internal/contextservice ./internal/reviewservice ./internal/reviewstore ./internal/controlapi ./internal/ingestion ./internal/ingestionqueue ./internal/submission ./internal/serverapp ./internal/remoteserver ./test/conformance",
"node --test test/contracts/*.test.ts",
"pnpm test:conformance",
]

[tasks."check:push"]
description = "Full pre-push checks, race tests, and generated-file consistency"
run = [
{ task = "check" },
{ task = "test:contracts" },
{ task = "test:race" },
{ task = "check:generated" },
]
run = [{ task = "check" }, { task = "test:race" }, { task = "check:generated" }]

[tasks."release:publish"]
description = "Publish the tagged GitHub release with GoReleaser"
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"check": "mise run check"
},
"devDependencies": {
"@babel/parser": "^7.29.8",
"@earendil-works/pi-coding-agent": "catalog:",
"@opencode/plugin": "catalog:",
"@types/node": "catalog:",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion test/conformance/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"compilerOptions": {
"paths": { "@servediff/shared": ["../../packages/shared/src/index.ts"] }
},
"include": ["server.test.ts", "../contracts/*.test.ts"]
"include": [
"server.test.ts",
"../contracts/*.test.ts",
"../../tools/check-design.ts"
]
}
Loading
Loading