From 9cb44fc70c9aa936a7b949f5ee7fd0911b99ada1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 5 May 2026 17:39:13 +0200 Subject: [PATCH] docs: add boundary violation fixture and document rule in COLLABORATING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds src/lib/tag/__fixtures__/cross-domain.fixture.ts — a permanent fixture that demonstrates the boundaries rule firing on a tag → person import. The fixture is excluded from npm run lint via --ignore-pattern; run npm run lint:boundary-demo to see it produce an error (exit 1). Documents the full allow-list, the escape hatches ($lib/shared/ move, explicit rule entry, eslint-disable-next-line), and the verify command in COLLABORATING.md. Refs #410 Co-Authored-By: Claude Sonnet 4.6 --- COLLABORATING.md | 37 +++++++++++++++++++ .../tag/__fixtures__/cross-domain.fixture.ts | 7 ++++ 2 files changed, 44 insertions(+) create mode 100644 frontend/src/lib/tag/__fixtures__/cross-domain.fixture.ts diff --git a/COLLABORATING.md b/COLLABORATING.md index 04a8c019..dc1478fe 100644 --- a/COLLABORATING.md +++ b/COLLABORATING.md @@ -185,3 +185,40 @@ Quick reminders: - No premature abstractions — KISS beats DRY - No backwards-compatibility shims for code that has no callers - Validate at system boundaries only (user input, external APIs) + +## Frontend Domain Boundaries + +The frontend mirrors the backend's package-by-domain structure. Each Tier-1 folder under `src/lib/` is a domain with a hard import boundary: + +``` +document person tag user geschichte notification ocr +activity conversation shared +``` + +The `boundaries/dependencies` ESLint rule enforces this. The full allow-list lives in `frontend/eslint.config.js`. The rule fires at error severity and blocks `npm run lint`. + +### Allowed cross-domain imports + +| From | May import from | +|---|---| +| `document` | `shared`, `person`, `tag`, `ocr`, `activity`, `conversation` | +| `geschichte` | `shared`, `person`, `document` | +| `ocr` | `shared`, `document` | +| `activity` | `shared`, `notification` | +| `person`, `tag`, `user`, `notification`, `conversation` | `shared` only | +| `shared` | `shared` only | +| `routes` | any domain | + +### When you need to cross a boundary + +1. **Move the code to `$lib/shared/`** — the correct fix when the code is truly generic (a UI primitive, a pure utility, a formatting helper). +2. **Add an explicit rule** — if a cross-domain dependency is architecturally justified (e.g., `document` importing `PersonTypeahead`), add the allow entry to `eslint.config.js` with a comment explaining the reason. +3. **Use `// eslint-disable-next-line boundaries/dependencies`** — last resort, only for cases where neither option is practical. Leave a comment explaining why. + +### Verifying the rule works + +```bash +npm run lint:boundary-demo # exits 1 — shows the rule firing on a deliberate tag→person violation +``` + +The fixture lives at `src/lib/tag/__fixtures__/cross-domain.fixture.ts` and is excluded from `npm run lint` via `--ignore-pattern`. diff --git a/frontend/src/lib/tag/__fixtures__/cross-domain.fixture.ts b/frontend/src/lib/tag/__fixtures__/cross-domain.fixture.ts new file mode 100644 index 00000000..502b3c9f --- /dev/null +++ b/frontend/src/lib/tag/__fixtures__/cross-domain.fixture.ts @@ -0,0 +1,7 @@ +// Deliberate boundary violation: tag domain importing from person domain. +// This file exists to prove the boundaries/dependencies rule fires. +// It is excluded from `npm run lint` via --ignore-pattern and must NEVER be imported by production code. +// Run `npm run lint:boundary-demo` to see ESLint report the violation (exit 1). + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { getInitials } from '$lib/person/personFormat';