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 <noreply@anthropic.com>
8.2 KiB
Collaboration Rules
How we work together on this project.
Honesty and Objectivity
Evaluate all suggestions on their technical merits. No sycophancy — if something doesn't make sense, doesn't align with best practices, or could be improved, say so directly and constructively. Technical accuracy and project quality take precedence over being agreeable.
Core Workflow: Research → Plan → Implement → Validate
Every non-trivial feature or bug fix follows this sequence:
- Research — Read the relevant code. Understand existing patterns before touching anything.
- Plan — Write a plan to
/.agent/current-plan.mdand align with the user before writing code. Update the plan as work progresses. - Implement — Use Red/Green TDD (see below).
- Validate — Run formatters, linters, and tests after every implementation step.
Never start writing code without having read the relevant files first.
Red/Green TDD
All new behavior is driven by tests written before the implementation. The cycle is:
- Red — Write a test that captures the requirement. Run it and confirm it fails. A test that passes before the implementation is written is not testing anything real.
- Green — Write the minimum production code needed to make the test pass. No more.
- Refactor — Clean up the implementation (names, structure, duplication) while keeping the test green.
- Commit — The test and implementation ship together in a single logical commit.
Repeat for each new behavior.
What level of test to write
| Scenario | Test type |
|---|---|
| Business logic, calculations, service rules | Unit test (DocumentServiceTest, etc.) |
| HTTP contract, request validation, error codes | Controller slice test (@WebMvcTest) |
| Full user-facing behavior, navigation, forms | E2E Playwright spec |
Rules
- Never write production code without a failing test that requires it.
- Keep the Green step minimal — resist adding "obvious" extras that have no test yet.
- The Refactor step must not change behavior — if a test breaks, the refactor introduced a bug.
- If a bug is reported with no test, write the failing test first, then fix it.
User Journeys & E2E Acceptance Criteria
Every feature issue must include two sections before any implementation begins:
1. User Journey
A plain-prose description of the steps a user takes to get value from the feature. Written from the user's perspective, not the implementation's:
User opens a document, clicks "History", sees a chronological list of changes with editor name and timestamp. Clicking a row expands the old vs. new values.
This makes the scope concrete and prevents scope creep — anything not in the journey is out of scope for the issue.
2. E2E Scenarios
One or more acceptance criteria written as Playwright-ready scenarios. These become the outermost Red test in the TDD cycle — no feature is considered done until all its E2E scenarios pass:
Scenario: View edit history of a document
Given I am on a document detail page
When I click the "History" tab
Then I see at least one revision entry
And each entry shows the editor's name and a timestamp
Use this format consistently. It maps directly to test.describe / test blocks in the Playwright spec.
Where this fits in the workflow
Issue (Journey + Scenarios) → Red E2E test → Implementation → Green
The scenarios in the issue are the contract. Write them before planning, treat them as failing tests from day one.
Issue Tracking (Gitea)
All work is tracked in Gitea at http://192.168.178.71:3005 (repo marcel/familienarchiv). Never use todo files or CLAUDE.md notes as a substitute.
Create an issue whenever work is identified that isn't being done in the current session.
Issue title formats
feature label — user story format:
As a [role] I want [capability] so/because [reason]
Examples:
- "As a user I want to search documents so I can find a specific document faster"
- "As an admin I want to add a new user so I don't have to restart the server"
bug label — user-facing impact, not the technical cause:
[What breaks] when [trigger]
Examples:
- "Document list shows blank page when no results found"
- "Upload fails silently when file exceeds 50MB"
devops label — infrastructure, CI/CD, deployment, tooling:
- "Fix CI checkout failing due to unresolvable hostname"
- "Add E2E test seed data for runner"
Priority labels
priority: high— blocking or urgentpriority: medium— normalpriority: low— nice to have
Other labels
needs-discussion— decision needed before work startswontfix— acknowledged, not addressing
Branching and Pull Requests
All changes go through a branch and a pull request — never commit directly to main.
Branch naming
<type>/<short-description>
Examples:
feat/received-documents-person-pagefix/tag-filter-url-syncdevops/docker-compose-v2
Workflow
- Create a branch from
mainbefore writing any code. - Commit work to that branch.
- Open a pull request on Gitea targeting
mainfor review. - Wait for approval before merging.
The PR description should reference the related issue (Closes #n or Refs #n).
Commit Messages
Every commit must reference the relevant Gitea issue.
Closes #12— commit fully resolves the issue (Gitea auto-closes it)Refs #12— commit is related but doesn't fully close the issue
Place the reference at the end of the commit body:
feat: add person typeahead to document edit form
Closes #7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Atomic commits
Each commit must do exactly one logical thing. Never bundle multiple unrelated changes into a single commit, even if they are small.
Wrong — three changes in one commit:
fix(e2e+i18n): add missing DE translation, fix test selectors, fix lang switching
Right — three separate commits:
fix(i18n): add missing person_btn_conversations DE translation
fix(e2e): exclude /persons/new from person link selector
fix(e2e): clear locale cookie when switching back to base language
When in doubt, commit more often rather than less.
Code Style
See CODESTYLE.md for the full guide: Clean Code (Uncle Bob), DRY/KISS trade-offs, and SOLID principles applied to this stack.
Quick reminders:
- Pure functions over stateful helpers where possible
- 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
- Move the code to
$lib/shared/— the correct fix when the code is truly generic (a UI primitive, a pure utility, a formatting helper). - Add an explicit rule — if a cross-domain dependency is architecturally justified (e.g.,
documentimportingPersonTypeahead), add the allow entry toeslint.config.jswith a comment explaining the reason. - 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
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.