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
41 changes: 41 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,47 @@ API in-process and delegates page routes to React Router v8 SSR.

**Docs**: `docs/README.md` is the map — `docs/operate/` (deploy, upgrade, configure) · `docs/develop/` (architecture, testing, design system) · `docs/reference/` (API, database, roles, deployment modes) · `docs/concepts/` (how the engine works) · `docs/integrations/` (external services) · `docs/compliance/` (data handling). Docs here cover the **engine**: deploying it, operating it, changing it, integrating it. Using the product day to day is documented at <https://inspectorhub.io/docs>, which serves self-hosted and hosted deployments alike.

**This checkout is a fork.** `origin` is `NCHILLC/OpenInspection` (private), `upstream` is `InspectorHub/OpenInspection`. What has been taken from upstream, skipped, or deliberately diverged on is recorded in [`docs/develop/fork-log.md`](docs/develop/fork-log.md) — read it before merging upstream or before "fixing" behaviour that looks like drift. It is linked from here rather than from `docs/README.md` on purpose: that map is upstream's, and a fork-only row in it would conflict on every merge.

## Check upstream BEFORE debugging anything

**Before investigating any error, bug, failing test or red gate: look for the
fix upstream first.** This fork sits 250+ commits behind a fast-moving upstream,
so the odds that a failure has already been diagnosed and fixed there are high,
and re-deriving a fix costs far more than the lookup does. Not optional, and not
a judgement call about how obvious the symptom looks.

```bash
git fetch upstream
git log main..upstream/main --oneline --no-merges -i --grep='<symptom keyword>'
git log main..upstream/main --oneline --no-merges -- <file you suspect>
```

A hit: cherry-pick or port it, and record the decision in
[`docs/develop/fork-log.md`](docs/develop/fork-log.md). A miss is worth knowing
too — it means the bug is this fork's own, and once fixed it belongs in the fork
log as a deviation. Either way the lookup takes seconds and its answer changes
what you do next.

**Reading CI requires the `gh` CLI, not the GitHub MCP connector.** The
connector's token has no Checks permission: `pull_request_read` does expose a
`get_check_runs` method, but it and `get_status` both return
`403 Resource not accessible by integration` (verified 2026-09-07). Confirm `gh`
before reporting that CI results are unavailable — do not repeat the limitation
from memory without checking:

```bash
gh auth status # expect: Logged in to github.com account NCHILLC
gh pr checks <n> --repo NCHILLC/OpenInspection
gh run view --job <id> --log-failed
```

Installed via `winget install --id GitHub.cli`. It may be missing from an
existing shell's PATH if that shell started before the install — call it as
`"/c/Program Files/GitHub CLI/gh.exe"` rather than concluding it is absent. A
timed-out job archives no log, so `--log-failed` returns EMPTY for one; use
`gh run view --job <id>` and read the annotation instead.

## Commands

```bash
Expand Down
128 changes: 128 additions & 0 deletions docs/develop/fork-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Fork log

`NCHILLC/OpenInspection` (`origin`) is a private fork of
`InspectorHub/OpenInspection` (`upstream`). This file records what has been
taken from upstream, what has been deliberately skipped, and where the two have
diverged on purpose.

It exists because the alternative is re-deriving those answers from a diff every
time upstream moves. Without it, a fix that upstream already made gets
rewritten here, and a deviation this fork chose gets "fixed" back to upstream's
behaviour by someone who assumed it was drift.

**This is a decision log, not a changelog.** Only entries where a judgement was
made belong here. A commit range taken wholesale with nothing to say about it
does not need a line.

## Refreshing the numbers

```bash
git fetch upstream
git rev-list --left-right --count upstream/main...main # upstream-only, fork-only
git log -1 --format='%h %ad %s' --date=short $(git merge-base upstream/main main)
git cherry upstream/main main # '-' marks fork commits upstream already has
```

`git cherry` is the reason the PR template forbids squashing: a squash rewrites
the branch's commits, and `git cherry` then reports landed work as unmerged.

## The upstream mirror

`upstream-track` is a branch on `origin` holding **exactly** `upstream/main` —
all of upstream's commits, none of this fork's. It exists so upstream can be
run, deployed and diffed without touching the working line.

Two rules, and they are the whole contract:

- **Never commit to it.** A commit here turns the mirror into a third divergent
line, which is the one thing it exists to prevent.
- **Fast-forward only.** If a refresh ever refuses to fast-forward, something
has been committed to it — fix that rather than forcing the update.

```bash
git fetch upstream
git push origin upstream/main:refs/heads/upstream-track # refresh the mirror
git switch upstream-track && git merge --ff-only upstream/main # or locally
```

It tracks `origin/upstream-track`, **not** `upstream/main`, and that is not an
oversight to correct: tracking upstream would make a stray `git push` on this
branch aim at somebody else's repository. Refresh with the explicit commands
above.

CI does not run on it: the `push` trigger is scoped to `main`, deliberately.
Upstream tests its own commits, and a mirror burning runner minutes to confirm
that is waste.

When an upstream scope in the table below is decided as **take**, this branch is
what you merge from.

## Snapshot — 2026-09-07

| | |
|---|---|
| Merge base | `f1198ffa`, 2026-08-29 (upstream PR #324) |
| Upstream-only commits | 254 |
| Fork-only commits | 47 |
| Fork commits upstream already has | 0 — nothing has been contributed back |

Divergence is nine days old and upstream is moving fast: 254 commits in that
window. The triage below is by **scope**, not by commit, because 254 individual
decisions is not a thing anyone will finish.

## Upstream work, by scope

| Scope | Commits | Decision |
|---|---|---|
| `statutory` | 74 | **undecided — the big one.** A whole subsystem: statutory form definitions, readiness checks, publishing, an editor surface and the first statutory E2E. Nothing in the fork depends on it. Taking it means carrying a subsystem the fork has no requirement for; skipping it means every future upstream merge conflicts against a version of the editor that assumes it exists. |
| `editor` | 14 | **undecided — see collision zone below.** |
| `gates` | 13 | recommend taking. Gate and lint work is exactly what the fork wants more of, and it rarely conflicts. |
| `marketplace` | 9 | undecided. Likely on the removal list. |
| `perf` | 8 | recommend taking. |
| `mcp` | 6 | undecided. Named on the eval's removal list. |
| `auth` | 6 | recommend taking — security-path changes are the worst class to re-derive. |
| `templates` | 5 | undecided; overlaps fork template work. |
| everything else | ~40 across `ui`, `tenancy`, `settings`, `routing`, `deps`, `ci`, `cron`, … | case by case |

Counts are `--no-merges` and exclude 22 merge commits.

## Collision zone: `app/components/editor`

The one area both sides are actively changing, and the reason a blind merge will
hurt.

- **Fork**: 20 of its 47 commits are scoped `editor`, touching 60 files under
`app/components/editor`. The subject is *field capture on a phone* — camera,
offline photo queueing, shutter gestures, mobile layout, a way out of the
unsaved-changes blocker.
- **Upstream**: 14 `editor` commits, nearly all of them *statutory-driven* —
statutory groups as slots, item nesting, sub-item CRUD, revision display.

Same directory, different concerns, so most conflicts will be textual rather
than semantic. Two exceptions worth checking before assuming that:

- `fix(editor): the Inspection Details overview rendered nothing on a phone` —
upstream fixing a mobile bug this fork may have already solved differently.
- `refactor(editor): template item CRUD delegates to the shared tree ops` — a
structural refactor under files the fork has rewritten.

## Fork deviations

What this fork changed and intends to keep. Add a line when a change is a
deliberate departure rather than work upstream simply has not done yet.

| Area | What | Why it diverges |
|---|---|---|
| `editor` (20 commits) | Field capture on a phone: camera, offline defect photos, shutter gesture handling, mobile layouts | Upstream's field surface is responsive web; this fork treats crawlspace capture as a core requirement — see the capture-first decision in `architecture.md` |
| `ratings` (4) | "Not inspected" reasons, Safety-Major level | — |
| `intake` (3) | Graded defects filed by position, severity preserved on import | — |
| CI | `verify` also runs on pushes to `main` | Fork commits directly to `main`; upstream works through PRs |

## Rules

- One line per decision, citing a commit or range (`git log a..b`), never a
migration number or a PR number from the other repository without its owner
(`InspectorHub/OpenInspection#324`).
- Record skips as explicitly as takes. "We looked and said no" and "we never
looked" are the same silence otherwise.
- Update the snapshot when you fetch, not when you remember.
Loading