Compare commits

..
4 Commits
Author SHA1 Message Date
myxeliumandCursor d658ab0827 chore: interview before fixes and keep HANDOFF.md tiny
Queue Release Build / prepare (push) Successful in 1m3s
Deploy Web Apps / deploy (push) Successful in 12m28s
Queue Release Build / build-windows (push) Successful in 32m9s
Queue Release Build / build-linux (push) Successful in 54m12s
Queue Release Build / build-android (push) Successful in 21m27s
Queue Release Build / finalize (push) Successful in 5m11s
Require a short user interview before implementing bugs/features, and overwrite or clear HANDOFF.md so it never accumulates session history.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 21:49:06 +02:00
myxeliumandCursor 34c32ba64a chore: cheap named-bug path and prefer lint:fix
Wire fix-bug prompts to a single Obsidian note plus default scope, and require npm run lint:fix instead of hand-fixing auto-fixable lint/format.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 21:49:06 +02:00
myxeliumandCursor cfe10907be chore: cut agent token burn with scoped bootstrap and handoffs
Slim always-on agent docs, default to toju-app plus targeted electron, and add HANDOFF.md plus a Cursor rule so long chats reset cleanly without agents opening new chats.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 21:49:06 +02:00
myxelium cdc32db30f Merge pull request 'Fix wonkyness' (#18) from fix-bug into main
Queue Release Build / prepare (push) Successful in 47s
Deploy Web Apps / deploy (push) Successful in 11m41s
Queue Release Build / build-windows (push) Successful in 30m20s
Queue Release Build / build-linux (push) Successful in 52m25s
Queue Release Build / build-android (push) Successful in 21m43s
Queue Release Build / finalize (push) Successful in 2m41s
Reviewed-on: #18
2026-07-14 09:28:24 +00:00
10 changed files with 407 additions and 222 deletions
+37
View File
@@ -0,0 +1,37 @@
---
description: Short-chat handoff to cut token burn; agents cannot open new chats
alwaysApply: true
---
# Handoff (token control)
Agents **cannot** create a new Cursor chat. When a handoff is needed, **overwrite** `agents-docs/HANDOFF.md` and tell the user to start a **new chat** with `@agents-docs/HANDOFF.md`.
## File size rule (mandatory)
- **Never append** to `HANDOFF.md`. Always replace the entire file.
- One active handoff only — no history stack in this file.
- Keep sections short (bullets, paths, not log dumps).
## When to hand off
- User says "handoff", "new chat", or "wrap up this session"
- Thread is long (many tool rounds, large pastes, repeated failed approaches) and more major work remains
- Switching to a clearly separate objective
## What to do (new handoff)
1. **Overwrite** `agents-docs/HANDOFF.md` completely — `Status: active`; fill Goal, Completed, Changed files, Decisions, Failed approaches, Current issue, Next steps, Commands (brief).
2. Stop major new work in this chat after writing the handoff (unless the user says continue here).
3. Tell the user the one-liner for the new chat.
## When the task is finished
After the user-approved work is done (or they abandon the handoff objective):
1. **Clear** `agents-docs/HANDOFF.md`: set `Status: none` and empty all section bodies (restore the short template — do not leave old Completed/Next steps lying around).
2. Do this in the same turn you claim done, so the next chat does not reload stale handoff context.
## New-chat bootstrap
If `Status: active`, read the handoff first and continue Next steps. Do not reload the whole monorepo or redo Completed work. When that work is finished → **clear** the file as above.
+20
View File
@@ -0,0 +1,20 @@
---
description: Interview the user before implementing fixes; no guessing
alwaysApply: true
---
# Interview before implement
Before writing or changing product code for a bug/feature ask:
1. Read the ask (and the one Obsidian bug note if named). Do **not** start a large codebase rewrite yet.
2. Reply with a **short interview** (bullets only):
- What you think the bug/goal is (12 sentences)
- Whats missing / unclear
- Choices (A/B/C) with a **recommended** default
- Proposed scope (files/areas you will touch; what you will not)
- How you will prove done
3. **Stop and wait** for the users answers. Do not implement until they approve or choose.
4. Then implement exactly what they chose — no silent extra scope.
Skip the interview only when the user says e.g. “just fix it”, “no interview”, or the handoff already records approved Next steps/decisions and they said continue.
+27
View File
@@ -0,0 +1,27 @@
# Reduce indexing / accidental bulk reads. Scope policy lives in AGENTS.md.
# Do NOT list electron/, server/, e2e/, website/, or docs-site/ here —
# those stay reachable when the user explicitly expands scope.
node_modules/
**/node_modules/
dist/
dist-electron/
dist-server/
.angular/
coverage/
test-results/
e2e/playwright-report/
docs-site/.docusaurus/
docs-site/build/
*.sqlite
package-lock.json
# Media / binary bulk
images/
**/*.png
**/*.jpg
**/*.jpeg
**/*.webp
**/*.gif
**/*.mp4
**/*.wasm
+94 -79
View File
@@ -1,103 +1,118 @@
# AGENTS.md
Read these files at the start of every session before doing any work:
Keep this file small. Detail lives in linked docs — load those **only when the task needs them**.
1. `agents-docs/AGENT_WORKFLOW.md` — workflow and operating rules
2. `agents-docs/LESSONS.md` — durable rules learned from past corrections; apply any that match this session's work
3. `agents-docs/AGENTS_FEATURES.md` — when and how to update feature docs
4. `agents-docs/FEATURES.md` — feature index
5. `agents-docs/ENGINEERING.md` — engineering standards
6. `agents-docs/CONTEXT-MAP.md` — index of bounded contexts in this repo
## Session handoff (new chats)
Reference on-demand (when the workflow triggers them — see `agents-docs/AGENT_WORKFLOW.md` §§ 45):
Agents **cannot** open a new Cursor chat. To reset context:
- `agents-docs/AGENTS_CONTEXT.md` — contract for updating `CONTEXT.md` / `CONTEXT-MAP.md`
- `agents-docs/AGENTS_ADRS.md` — contract for writing architecture decision records
- `agents-docs/BUG_TRACKER.md` — Obsidian bug inbox location, allowed vault edits, and triage workflow
1. **Overwrite** `agents-docs/HANDOFF.md` entirely (`Status: active`) — never append. See `.cursor/rules/handoff.mdc`.
2. Ask the user to start a **new chat** and attach `@agents-docs/HANDOFF.md`.
When working in a subdomain, also read its `CONTEXT.md` first:
**At session start:** if `Status: active`, read it first and continue Next steps. Do not redo Completed work.
- Product client (Angular 21): `toju-app/CONTEXT.md`
- Desktop shell (Electron main + preload): `electron/CONTEXT.md`
- Signaling server (Express + WebSocket): `server/CONTEXT.md`
- End-to-end tests (Playwright): `e2e/CONTEXT.md`
- Marketing site (Angular 19): `website/CONTEXT.md`
- Application documentation (Docusaurus): `docs-site/CONTEXT.md`
**When the task is finished:** clear the handoff — set `Status: none` and empty all sections (same short template). Never leave a growing archive in `HANDOFF.md`.
## Named Obsidian bugs
When the user says `fix bug "…"`, follow `agents-docs/BUG_TRACKER.md` § Named fix: open **that one** vault note, **interview before implementing**, then fix in default scope and set `status: Resolved`. Do not scan the whole bug inbox.
## Interview before implement (user in control)
Before changing product code for a bug or feature:
1. Read the ask / named bug note (cheap orientation only — not a monorepo dig).
2. Post a **short interview**: your understanding, gaps, A/B/C choices with a recommended default, proposed scope, how youll prove done.
3. **Wait** for the users choices. Do not guess past ambiguity.
4. Implement only what they approved.
Skip only if they say “just fix it” / “no interview”, or an active handoff already has approved decisions and they said continue. See `.cursor/rules/interview-before-fix.mdc`.
## Default work scope (token fence)
Unless the user **explicitly** expands scope, stay inside:
- `toju-app/` (primary)
- `electron/`**targeted only** (desktop shell is coupled to the client; see below)
- CI: `.gitea/workflows/`
- Agent/docs as needed: this file, `agents-docs/` (index-first / handoff), `toju-app/CONTEXT.md`; `electron/CONTEXT.md` only when touching Electron
### Electron — relevant files only
`electron/` is in default scope because the renderer talks to it via preload/IPC/local DB. Do **not** browse the whole tree.
When a `toju-app` change needs the desktop bridge:
1. Start from the renderer call site (`window.api` / Electron bridge usage).
2. Open only the matching surface: usually `electron/preload.ts`, then the specific handler under `electron/ipc/`, `electron/cqrs*`, or the one module/entity/migration involved.
3. Prefer ripgrep with path `electron/` + a concrete symbol over listing directories.
4. Skip unrelated areas (`electron/api/` docs server, `game-detection/`, `update/`, other migrations, etc.) unless the bug points there.
**Still out of scope by default** (do not search/read/edit unless the user names them):
- `server/`, `e2e/`, `website/`, `docs-site/`
- Root noise: `dist*/`, `node_modules/`, `images/`, `project-files/`, `test-results/`
If the root cause looks like `server/` or e2e-only, **ask once** instead of exploring those trees.
Search with path filters. Prefer `toju-app/src/app/domains/<name>/` over repo-wide greps.
## Session start (cheap bootstrap)
1. Skim this file.
2. If handoff `Status: active` → read `agents-docs/HANDOFF.md`.
3. Open `agents-docs/LESSONS-INDEX.md` only — match tags; open matching bodies in `LESSONS.md`.
4. Read `toju-app/CONTEXT.md` for client work; `electron/CONTEXT.md` only if this task touches Electron.
5. Other docs **on demand** only.
**Models:** use the latest problem-solving model the user selected. Save tokens with **scope, handoffs, and short chats** — not by silently downgrading model quality.
**Do not auto-read:** `ENGINEERING.md`, `AGENTS_FEATURES.md`, `FEATURES.md`, `CONTEXT-MAP.md`, full `AGENT_WORKFLOW.md`, feature docs, ADRs — unless needed.
On-demand: `agents-docs/AGENT_WORKFLOW.md`, `AGENTS_FEATURES.md`, `FEATURES.md`, `ENGINEERING.md`, `AGENTS_CONTEXT.md`, `AGENTS_ADRS.md`, `BUG_TRACKER.md`.
---
MetoYou (also called Toju) is a desktop-first, P2P Discord-style chat application managed as an npm-workspaces monorepo. It bundles an Angular 21 product client, an Electron 39 desktop shell with TypeORM + sql.js for local persistence, a small Node/TypeScript Express signaling server with WebSocket-based realtime, a Playwright end-to-end suite, an Angular 19 marketing site, and a Docusaurus app/plugin documentation site that ships inside the Electron build. Voice and screen-share are WebRTC, with RNNoise denoising via a WASM audio worklet.
MetoYou / Toju: desktop-first P2P chat. Default surface: Angular client (`toju-app/`) + targeted Electron bridge + CI.
## CRITICAL — Non-negotiable rules for all agents
## CRITICAL — Done means the asked behavior works
### Test-Driven Development (MANDATORY)
**Write tests before implementation code.**
**Unit/spec green is support, not done.**
When creating or changing anything:
1. STOP — do not write implementation first
2. Write failing tests (RED)
3. Run tests and confirm failure (`npm run test` for the product client; `npm run test:e2e` for end-to-end; place spec files colocated with source, suffix `.spec.ts`)
4. Write minimal code to pass tests (GREEN)
5. Refactor while keeping tests green
1. Restate acceptance in one sentence.
2. Prove the behavior (user-visible path, focused test at the right level, or explicit manual check).
3. Prefer a regression that fails if the asked behavior regresses.
This applies to all code — Angular components and services, NgRx effects/reducers, Electron IPC handlers, server CQRS handlers, websocket message handlers, plugin runtime, and domain logic. If the code lives in a package without a configured test runner (server, website, docs-site), surface that gap before adding logic there.
### Test-backed development (balanced)
### Lint correctness (MANDATORY)
Before completing any task:
1. Run `npm run lint` from the repo root (ESLint 9 flat config in `eslint.config.js` covers every package)
2. Fix all errors
3. Do not consider work complete until it exits with code 0
For domain/logic: failing behavior-level test → minimal fix → green.
Skip full red-green for docs/copy/agent text, formatting, trivial wiring already covered higher up.
### Type / build correctness (MANDATORY)
Type checks live in build scripts:
**Do not:** ship implementation-shaped mocks as the feature; stop at unit-green for product asks; run full monorepo / full e2e on every tiny change — targeted specs first.
- Product client (`toju-app/`): `npm run build` (Angular CLI runs `tsc` with strict settings)
- Electron (`electron/`): `npm run build:electron` (invokes `tsc -p tsconfig.electron.json`)
- Server (`server/`): `cd server && npm run build` (invokes `tsc`)
### Lint / type correctness (scoped)
If your change touches one of these packages, run the corresponding build and ensure it exits 0 before marking work complete.
1. Targeted Vitest under `toju-app/` (and colocated Electron specs only if you changed those files).
2. Auto-fix style first: `npm run lint:fix` from repo root (`format` + `sort:props` + `eslint . --fix`). Do **not** hand-edit formatting/import-sort/eslint-fixable issues — re-run `lint:fix`. Then confirm clean with `npm run lint` only if you need a no-write check; prefer trusting `lint:fix` exit 0.
3. Do not paste entire lint logs into the chat — fix via `lint:fix` / minimal code changes for non-auto issues.
4. `npm run build` when client types/templates could break; `npm run build:electron` only if you changed Electron sources.
## Most important rule
Do **not** run `cd server && npm run build` or `npm run test:e2e` unless scope expanded or the bug is proven there.
After any change that affects API contracts, schemas, invariants, workflows, or major behavior: update the relevant `agents-docs/features/<slug>.md` as part of the same task — not as a follow-up. New feature area → create `agents-docs/features/<slug>.md` and add an entry to `agents-docs/FEATURES.md` (alphabetical).
### Feature docs
The product client already maintains per-domain READMEs under `toju-app/src/app/domains/<name>/README.md`. When the change is fully internal to one of those bounded contexts and its surface stays the same, the domain README is the right place to update; cross-context contracts (websocket envelopes, IPC channels, server routes, plugin manifests) belong in `agents-docs/features/`.
## Structure of further instructions
- **Agent workflow & operating rules:** `agents-docs/AGENT_WORKFLOW.md`
- **Agent lessons (durable cross-session rules):** `agents-docs/LESSONS.md`
- **Engineering standards:** `agents-docs/ENGINEERING.md`
- **Feature documentation contract:** `agents-docs/AGENTS_FEATURES.md`
- **CONTEXT documentation contract:** `agents-docs/AGENTS_CONTEXT.md`
- **ADR contract:** `agents-docs/AGENTS_ADRS.md`
- **Feature index:** `agents-docs/FEATURES.md`
- **Feature docs:** `agents-docs/features/`
- **Architecture decisions:** `agents-docs/adr/`
- **Context map:** `agents-docs/CONTEXT-MAP.md`
- **Obsidian bug tracker:** `agents-docs/BUG_TRACKER.md`
- **Product-client domain:** `toju-app/CONTEXT.md`
- **Desktop-shell domain:** `electron/CONTEXT.md`
- **Server domain:** `server/CONTEXT.md`
- **E2E suite domain:** `e2e/CONTEXT.md`
- **Marketing-site domain:** `website/CONTEXT.md`
- **App-docs domain:** `docs-site/CONTEXT.md`
Keep this file minimal. Do not duplicate detailed rules here.
Internal domain changes → `toju-app/src/app/domains/<name>/README.md`.
IPC/preload/WS contract changes → `agents-docs/features/<slug>.md` when that contract actually changed.
## Completion checklist
Before marking work complete:
- [ ] Tests written before implementation
- [ ] All tests passing (`npm run test`, plus `npm run test:e2e` if behavior is user-visible)
- [ ] `npm run lint` passes
- [ ] Affected package builds: `npm run build` / `npm run build:electron` / `cd server && npm run build`
- [ ] Naming conventions followed (kebab-case files; domain `*.rules.ts` / `*.model.ts` / `*.component.ts` suffixes)
- [ ] Errors handled
- [ ] Feature docs updated if contract/schema/invariant changed (see `agents-docs/AGENTS_FEATURES.md`)
- [ ] `CONTEXT.md` updated if a domain term was resolved or introduced (see `agents-docs/AGENTS_CONTEXT.md`)
- [ ] ADR written if a hard-to-reverse decision was made (see `agents-docs/AGENTS_ADRS.md`)
- [ ] Lesson recorded in `agents-docs/LESSONS.md` if this session produced a correction, revert, or hidden constraint (see triggers in `agents-docs/AGENT_WORKFLOW.md`)
- [ ] PR opened with summary and linked issues (`Fixes #<n>` / `Relates to #<n>`)
- [ ] Gitea Workflows checks passing
- [ ] Interview completed (or user opted out); implemented only approved choices
- [ ] Asked behavior proven (not only unit tests green)
- [ ] Stayed in scope (`toju-app` + targeted `electron` + CI) unless user expanded it
- [ ] Appropriate targeted tests for logic changes
- [ ] Lint via `npm run lint:fix` (not hand-fixed style); build only touched packages
- [ ] Docs only if contracts changed
- [ ] Lesson + index entry if corrected this session
- [ ] If the thread is long and work remains: overwrite `HANDOFF.md` and ask user for a new chat
- [ ] If work from an active handoff is finished: clear `HANDOFF.md` to `Status: none` (empty sections)
- [ ] PR when requesting merge (`Fixes #<n>` / `Relates to #<n>`)
+42 -69
View File
@@ -1,110 +1,83 @@
# Agent Workflow & Operating Instructions
These rules apply to **all AI agents** working on this project, regardless of platform or model.
These rules apply to **all AI agents** working on this project.
Read this file at the start of every session.
**Token budget (mandatory):**
- Default scope: **`toju-app/` + targeted `electron/` + `.gitea/workflows/`** — see `/AGENTS.md`. No `server/`, `e2e/`, `website/`, `docs-site/` unless the user expands scope.
- Electron: follow the renderer → preload → one handler path; never dump the whole `electron/` tree.
- Prefer path-scoped search; one focused agent; no Bugbot / security / best-of-N unless asked.
- **Handoff > fat chats:** agents cannot open new chats. Write `agents-docs/HANDOFF.md` and ask the user to start a new chat with that file attached.
- **Named bugs:** `fix bug "…"` → one Obsidian note (`BUG_TRACKER.md` § Named fix), then default repo scope — not the whole vault or monorepo.
- **Models:** keep the users latest problem-solving model. Cut cost with scope + handoffs, not weaker models.
Do **not** re-read this whole file every turn after the first skim.
---
## Workflow Orchestration
### 1. Plan Mode Default
### 1. Interview before implement (default)
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately — don't keep pushing
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
- For bugs/features: short interview first (understanding, gaps, choices + recommended default, scope, proof) — then wait. See `/AGENTS.md` and `.cursor/rules/interview-before-fix.mdc`.
- Do not guess past ambiguity; the user controls the implementation choices.
- Skip only when the user opts out (“just fix it”) or an approved handoff already decided.
### 2. Subagent Strategy
### 2. Plan mode
- Use subagents liberally to keep the main context window clean
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution
- Use plan mode when architecture is unclear or the user asks — the interview often replaces a heavy plan for normal bug fixes.
- Skip long planning essays; prefer bullet choices.
### 3. Self-Improvement Loop
### 3. Subagents sparingly
The goal is a small, sharp file of project-specific rules in `agents-docs/LESSONS.md` that future sessions read and apply. The format of a lesson is defined at the top of `agents-docs/LESSONS.md` — read it before writing one.
- Default: one agent.
- Subagents only for true parallel search inside allowed paths.
- Never spawn extra review agents unless the user asks.
**Read at session start.** Open `agents-docs/LESSONS.md` and apply any rules that match the work you're about to do. This is non-optional; the file exists so the same mistake isn't made twice.
### 4. Handoff / short sessions
**Triggers — record a lesson when any of these happen.** Don't wait for a formal request; these are the signals:
Triggers: user says handoff / new chat; thread is long with more major work left; switching objectives.
- User says "no", "actually", "don't", "stop", "that's wrong", or "instead do X"
- User reverts, rewrites, or asks you to redo your edit
- User re-prompts you with the same or similar instruction (signal that the first attempt missed something)
- User points out a hidden constraint, past incident, or convention you didn't know
- Code review (human or `/review`) surfaces an issue caused by your approach
- You catch yourself about to do the same thing the project has been corrected on before
Action: **overwrite** (never append) `agents-docs/HANDOFF.md` with `Status: active` and short sections. Then stop major work and ask the user to open a new chat.
If unsure whether it's worth recording: write it. Sharper is better than missing, and grooming the file is cheap.
New chat: if handoff is active, read it first; continue Next steps; do not redo Completed work. If Next steps still need choices, re-interview — dont invent them.
**Write before reporting done.** A session that produced a correction must produce a lesson — record it in the same turn the work is completed, not "later". The `AGENTS.md` completion checklist has a line for this; don't tick the box without it.
**When finished:** clear `HANDOFF.md` to `Status: none` with empty sections so the file stays tiny for the next session.
**Groom periodically.** When `agents-docs/LESSONS.md` passes ~20 entries, propose consolidations to the user — merge duplicates, delete rules that no longer apply, shorten anything vague.
### 5. Self-Improvement Loop
### 4. CONTEXT.md upkeep
**At session start:** `LESSONS-INDEX.md` only; open matching lesson bodies by tag.
Read `CONTEXT.md` (or `agents-docs/CONTEXT-MAP.md` → per-subdomain `CONTEXT.md`) when working in a subdomain. Use its vocabulary verbatim **where defined** in code, tests, issues, and commits. If a needed term isn't in the glossary, treat it as a trigger (see below) rather than silently inventing a synonym; the full contract lives in `agents-docs/AGENTS_CONTEXT.md`.
Record a lesson + index line when corrected. Prefer fewer sharp rules (~20).
**Triggers — capture vocabulary in the moment:**
### 6. CONTEXT.md upkeep
- A previously-ambiguous domain term gets a clear resolution → add it (one-sentence definition, aliases to avoid).
- User corrects your terminology → record the correct term; mark the wrong one as an alias to avoid.
- A new feature introduces a concept absent from the glossary → add it before claiming the feature done.
- You catch yourself inventing a synonym because the right term isn't there → flag the gap; don't silently coin a new term.
Default: `toju-app/CONTEXT.md`. Read `electron/CONTEXT.md` only when touching Electron. Other packages only when in scope.
**Write before reporting done.** Update the relevant `CONTEXT.md` in the same turn the trigger fires. Append-only — add new entries, don't reshuffle existing ones. The format is documented at the top of each `CONTEXT.md`. See `agents-docs/AGENTS_CONTEXT.md` for the full contract.
### 7. ADR upkeep
### 5. ADR upkeep
Only when hard-to-reverse + surprising + real trade-offs. Contract: `agents-docs/AGENTS_ADRS.md`.
Read `agents-docs/adr/` when about to change anything that crosses an existing decision boundary. If your work would contradict an ADR, surface it explicitly — never silently override.
### 8. Verification Before Done (behavior first)
**Triggers — write an ADR only when all three apply:**
Done = asked functionality works **as the user confirmed in the interview**. Unit green ≠ done for product asks.
- **Hard to reverse** (schema migration, framework swap, integration redesign).
- **Surprising without context** (future engineers will question the approach).
- **Result of genuine trade-offs** (real alternatives existed and you chose deliberately).
### 9. Demand Elegance (Balanced)
If all three apply: write the ADR in the same turn as the decision. Next number (4-digit zero-padded), kebab-case slug, Nygard short form — see `agents-docs/adr/0001-record-architectural-decisions.md` for the canonical example and `agents-docs/AGENTS_ADRS.md` for the contract. If any of the three is missing: don't write one.
One pause for non-trivial design; skip for obvious fixes once the user has chosen a direction.
**Supersede, don't delete.** Overturned decisions get a new ADR; the old one stays with a `Superseded by ADR-NNNN` note.
### 10. Bug fixing (after interview)
### 6. Verification Before Done
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself: "Would a staff engineer approve this?"
- Run tests, check logs, demonstrate correctness
### 7. Demand Elegance (Balanced)
- For non-trivial changes: pause and ask "is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes — don't over-engineer
- Challenge your own work before presenting it
### 8. Autonomous Bug Fixing
- When given a bug report: just fix it. Don't ask for hand-holding
- Point at logs, errors, failing tests — then resolve them
- Zero context switching required from the user
Implement the approved plan with evidence in default scope. If root cause is clearly outside scope, say so and ask to expand — dont silently crawl.
---
## Pull Requests
This project hosts at Gitea (`git.azaaxin.com/myxelium/Toju`). Gitea PRs and issues use GitHub-style syntax.
- Create a feature branch for every change: `<type>/<short-description>` (e.g. `feat/add-retry-logic`, `fix/null-pointer-webhook`) — `<type>` should match the Conventional Commits prefix (`feat`, `fix`, `chore`, `docs`, `perf`, `refactor`, `test`)
- Open the PR via the Gitea web UI (or `tea pulls create` if `tea` CLI is installed) — include a summary and a test plan
- Link issues in the PR body with `Fixes #<number>` for auto-close or `Relates to #<number>` for reference (Gitea honors the same keywords as GitHub)
- After merge, delete the feature branch
Gitea: `git.azaaxin.com/myxelium/Toju`. Branch `<type>/<short-description>`; PR with summary + test plan; `Fixes #<n>` / `Relates to #<n>`.
---
## Core Principles
- **Simplicity First:** Make every change as simple as possible. Impact minimal code.
- **No Laziness:** Find root causes. No temporary fixes. Senior developer standards.
- **Minimal Impact:** Changes should only touch what's necessary. Avoid introducing bugs.
- **Simplicity First** · **No Laziness** · **Minimal Impact** · **Cheap Context** · **Default Scope Fence** · **Handoff Beats Fat Context** · **Interview Before Guessing**
+25 -55
View File
@@ -1,49 +1,49 @@
# Obsidian Bug Tracker — Agent Contract
User-maintained bug reports live outside the repo. Read this file when asked to triage, investigate, or work from the bug backlog.
User-maintained bug reports live outside the repo. Use this when the user names a bug or asks to triage the backlog.
**Overrides** `agents-docs/AGENT_WORKFLOW.md` §8 (Autonomous Bug Fixing) unless the user explicitly asks you to fix a bug in code.
**Inbox:** `/home/ludde/Nextcloud/Obsidian Vault/Log/Bugs/`
**Attachments:** `…/Bugs/attachments/<Bug title>/`
**Dashboard / template:** `…/Log/Create bug.md`, `…/Log/Templates/Bug Report.md`
---
## Location
## Named fix (default — cheap path)
| Item | Path |
|------|------|
| Bug inbox | `/home/ludde/Nextcloud/Obsidian Vault/Log/Bugs/` |
| Attachments | `…/Bugs/attachments/<Bug title>/` |
| Dashboard | `/home/ludde/Nextcloud/Obsidian Vault/Log/Create bug.md` |
| Template | `/home/ludde/Nextcloud/Obsidian Vault/Log/Templates/Bug Report.md` |
When the user says e.g. `fix bug "Images and files in chat doesn't load"`:
1. **Resolve one note** under `Log/Bugs/` whose title matches (usually `Bug - <title>.md`). Do not list or read the whole inbox.
2. **Read that note** (Description, Steps, Expected/Actual, Logs). Read attachments only under that bugs attachment folder if referenced.
3. **Interview before implement** (see `/AGENTS.md` and `.cursor/rules/interview-before-fix.mdc`): restate the bug, list gaps, present choices with a recommended default, propose scope and proof of done — then **wait** for the user. Do not start coding until they answer (unless they said “just fix it”).
4. Acceptance criteria = the notes Expected Result **plus** the users interview answers.
5. **Fix in default repo scope** (`toju-app/` + targeted `electron/` + CI) per `AGENTS.md`. Do not crawl `server/` / `e2e/` / other packages unless the note or user clearly requires it — then ask once.
6. Prove the asked behavior (not only unit-green). Prefer a regression that encodes the notes failure mode.
7. When done: set that notes frontmatter `status` to `Resolved` (or `Closed` if the user prefers). Do not rewrite Description / Investigation / Resolution unless asked.
8. Long thread + more work left → write `agents-docs/HANDOFF.md` and ask for a new chat.
Do **not** re-read `BUG_TRACKER.md` every turn after the first use. Do **not** load the stale “open bugs” snapshot as truth — the vault files are source of truth.
---
## Allowed actions on vault files
## Backlog triage only
Unless the user explicitly asks for more:
1. **Change `status`** in a bug note's YAML frontmatter (`Open``Resolved` or `Closed`).
2. **Move files** (e.g. reorganize notes or attachments when instructed).
Do **not** edit other vault fields or sections (`Investigation`, `Resolution`, description, etc.) unless the user asks.
If the user asks to list/triage open bugs (not a named fix): `ls` / glob `Log/Bugs/*.md`, filter `status: Open`, summarize titles — still dont open every body until they pick one.
---
## Allowed reads (unrestricted)
## Vault edit policy
To understand and solve bugs you may read freely:
Unless the user asks for more:
- All bug notes and attachments under `Log/Bugs/`
- The full MetoYou repo (code, tests, logs, docs)
- Runtime output, test results, and debug artifacts
- **Allowed write:** frontmatter `status` (`Open``Resolved` / `Closed`); move files if they specify a convention.
- **Do not edit:** other frontmatter fields, Description, Steps, Investigation, Resolution, etc.
Investigation findings belong in chat or in repo changes — not in the vault — unless the user asks you to update the note.
Investigation findings go in chat or the repo — not the vault — unless asked.
---
## Bug note format
Each note is Markdown with YAML frontmatter:
```yaml
---
title: Bug - …
@@ -57,34 +57,4 @@ tags: [bug]
---
```
Body sections: **Description**, **Steps to Reproduce**, **Expected Result**, **Actual Result**, **Logs / Screenshots**, **Investigation**, **Resolution**.
The dashboard (`Create bug.md`) uses Dataview; keep `type: bug` and `status` accurate so counts stay correct.
---
## Workflow
1. List open bugs: `Glob` or `ls` on `…/Log/Bugs/*.md`, filter `status: Open`.
2. Read the note and any linked attachments.
3. Investigate in the repo (read-only toward the vault).
4. Report findings to the user.
5. Only when told to fix: implement in repo (TDD, lint, build per `AGENTS.md`).
6. When a bug is done: update vault `status` to `Resolved` or `Closed` (and move files if the user specifies a convention).
---
## Open bugs (snapshot 2026-06-10)
| Title | Priority | Environment |
|-------|----------|-------------|
| Attachments gets syncronized corrupt | Critical | All major clients |
| Chats doesn't sync for multi client users | High | All |
| No android app icon | High | Android |
| No login screen mobile phone on startup | High | Android, Android Browser |
| Fresh users have the server list in dashboard completely empty until anything searched | High | — |
| Video attachment on android gets sent in the message bubble above with no preview image | High | Android |
| Local files should be remembered by client | High | — |
| Emojis should be user bound not client bound | Medium | All |
Re-scan the folder at session start; this table is not auto-updated.
Body: **Description**, **Steps to Reproduce**, **Expected Result**, **Actual Result**, **Logs / Screenshots**, **Investigation**, **Resolution**.
+15 -10
View File
@@ -45,9 +45,14 @@ The server package does not currently have a test runner script — there is one
E2E tests exercise the real Electron app against the real signaling server. The `.agents/skills/playwright-e2e/SKILL.md` describes the convention this repo uses for E2E test design — read it before adding new tests.
### TDD discipline
### Test-backed development (balanced)
Write the failing test first. Run it, watch it fail, then write the smallest code that makes it pass. This rule is non-negotiable (see `/AGENTS.md` § CRITICAL).
See `/AGENTS.md` § CRITICAL for the full policy. Summary:
- **Done** = the asked behavior works, proven at the right level. Green unit tests alone are not done when the ask was product behavior.
- Prefer red-green for domain/logic and bug invariants; encode the **user-facing or cross-boundary** outcome, not only implementation details.
- Skip full red-green for docs/copy/agent-instruction text, pure formatting, and trivial wiring already covered by a higher-level test you will run.
- Run **targeted** specs while iterating (`cd toju-app && npx vitest run <path>`); broaden to full suite / e2e when risk is cross-cutting or before merge.
Integration / cross-package work that needs a real database can rely on Electron's TypeORM + sql.js setup (in-memory by default) — no Testcontainers required.
@@ -191,8 +196,8 @@ cd server && npm run build # server tsc
npm run build:all # all of the above
# --- lint / format ---
npm run lint # eslint .
npm run lint:fix # format + sort:props + eslint --fix
npm run lint # eslint . (check only)
npm run lint:fix # preferred: format + sort:props + eslint --fix
npm run format # prettier on Angular HTML templates only
npm run format:check # prettier --check on HTML templates
@@ -209,16 +214,16 @@ npm run migration:revert # roll back last
Before marking work complete:
- [ ] Tests written before implementation
- [ ] All tests passing (`npm run test`, plus `npm run test:e2e` if behavior is user-visible)
- [ ] `npm run lint` passes
- [ ] Affected package builds: `npm run build` / `npm run build:electron` / `cd server && npm run build`
- [ ] Tests written before implementation (balanced — see `/AGENTS.md`)
- [ ] Asked behavior proven; targeted tests pass (`npm run test:e2e` only if in scope / user-visible and needed)
- [ ] `npm run lint:fix` passes (do not hand-fix auto-fixable lint/format)
- [ ] Affected package builds: `npm run build` / `npm run build:electron` when those packages were touched
- [ ] Naming conventions followed
- [ ] Errors handled
- [ ] Security considered (no secrets in code, no plaintext token logging, no IPC handler accepting arbitrary file paths)
- [ ] Feature docs updated if contract/schema/invariant changed (see `agents-docs/AGENTS_FEATURES.md`)
- [ ] `CONTEXT.md` updated if a domain term was resolved or introduced (see `agents-docs/AGENTS_CONTEXT.md`)
- [ ] ADR written if a hard-to-reverse decision was made (see `agents-docs/AGENTS_ADRS.md`)
- [ ] Lesson recorded in `agents-docs/LESSONS.md` if this session produced a correction, revert, or hidden constraint (see triggers in `agents-docs/AGENT_WORKFLOW.md`)
- [ ] PR opened with summary and linked issues
- [ ] Lesson recorded in `agents-docs/LESSONS.md` + `LESSONS-INDEX.md` if this session produced a correction
- [ ] PR opened with summary and linked issues when requesting merge
- [ ] Gitea Workflows checks passing
+29
View File
@@ -0,0 +1,29 @@
# Session Handoff
> **New chat:** attach `@agents-docs/HANDOFF.md` (and only files under Changed files). Say: continue from this handoff; do not redo completed work.
>
> **Agents cannot open a new chat** — overwrite this file, then ask the user to start one.
>
> **Keep this file tiny:** always **overwrite** the whole file (never append). When the task is finished, **clear** back to `Status: none` and empty sections (see below).
**Status:** none
## Goal
## Completed
## Changed files
## Decisions (this session only)
## Failed approaches
## Current issue / blockers
## Next steps (exact)
## Commands to continue
## Scope note
Default: `toju-app/` + targeted `electron/` + `.gitea/workflows/`. Out of scope unless listed here: `server/`, `e2e/`, `website/`, `docs-site/`.
+60
View File
@@ -0,0 +1,60 @@
# Agent Lessons — Index
**Session start:** read this file only. Match tags to the task. Open the matching lesson body in `agents-docs/LESSONS.md` — do **not** load every lesson.
When adding a lesson: append the full entry near the top of `LESSONS.md` (under `## Lessons`) **and** add one bullet here.
Tags help grepping: `rg '\\[attachments\\]' agents-docs/LESSONS-INDEX.md`
## Index
- Keep `NgOptimizedImage` off runtime blob and data URLs — `[angular] [images]`
- Read the exact Obsidian bug note for `fix bug "…"` (one note only) — `[workflow] [bugs]`
- Run `npm run i18n:sync` after editing any `public/i18n/catalog/*.json` file — `[i18n] [testing]`
- Match direct-call recipients against every local identity alias, exactly like DMs already do — `[direct-call] [identity]`
- Resolve outbound direct-call recipient ids to the peer's connected signal identity — `[direct-call] [identity] [signaling]`
- Canonicalize direct conversation ids across cross-signal actor aliases — `[direct-message] [identity]`
- Decide attachment receive admission once at request time; never re-gate size in the chunk handler — `[attachments]`
- Re-queue attachment auto-downloads on every message/room binding event; never trust one transport's ordering — `[attachments] [realtime]`
- Scope per-user UI state by user id, not by the client database — `[persistence] [multi-user] [custom-emoji]`
- Don't strand signed-out mobile users on a logged-out dashboard — `[auth] [mobile] [routing]`
- "Shared from your device" must gate on local bytes, not uploader user id — `[attachments] [multi-device]`
- Generate Android brand icons from the source mark; guard against stock Capacitor placeholders — `[mobile] [android] [assets]`
- Bind chat attachments to a pre-allocated message id, never by matching content — `[attachments] [chat] [mobile]`
- Attachment file persistence must be platform-agnostic, not Electron-only — `[attachments] [persistence] [mobile]`
- Never count duplicate chunks toward transfer progress, and never finalize on byte counters — `[attachments] [webrtc]`
- Don't bump E2E timeouts for sync flakes - gate on presence and read server logs — `[testing] [realtime]`
- When renaming an Angular route, sweep every navigate/url-match/doc reference — `[routing]`
- Server discovery must fan out across all endpoints and self-heal on 404 — never hardcode a host capability blocklist — `[server-directory]`
- Server registration needs `ownerPublicKey: oderId || id`, and must not be fire-and-forget — `[server-directory] [rooms]`
- Identify must fall back to the legacy session token, not only the new credential store — `[realtime] [authentication]`
- Keep the per-signal-URL identify credential resolvable from the store — `[realtime] [authentication]`
- Store clientInstanceId in sessionStorage not localStorage — `[realtime] [multi-device]`
- Revalidate IndexedDB scope without reinitializing on every read — `[persistence] [performance]`
- Restore local user scope before protected writes — `[authentication] [persistence]`
- Persisted local user state still requires a session token — `[authentication] [signaling]`
- Declare MODIFY_AUDIO_SETTINGS for Android WebRTC mic capture — `[mobile] [android]`
- Do not override Tailwind with box-sizing inherit — `[mobile] [css]`
- Use the app-shell servers rail for mobile discovery pages — `[mobile] [layout]`
- Defer attachment blob hydration on Electron startup — `[attachments] [electron]`
- Lazy-load Capacitor modules on Electron/desktop — `[mobile] [electron]`
- Use the upgrade transaction during IndexedDB schema migrations — `[persistence] [browser]`
- Wait for authenticateUser storage prep before post-login navigation — `[authentication] [browser]`
- Use dense arrays for chunked transfer buffers — `[custom-emoji] [webrtc]`
- Route custom emoji right-click through the native context menu — `[custom-emoji] [ux]`
- Separate known emoji assets from saved library — `[custom-emoji] [ux]`
- Chunk custom emoji assets over data channels — `[custom-emoji] [webrtc]`
- Re-clear visible notification channels after recompute — `[notifications] [startup]`
- Disambiguate nested chat cards — `[chat] [ui]`
- Use terminal Vitest when the test tool hangs — `[testing]`
- Do not add fake chrome around screenshots — `[website] [design]`
- Verify lint exits 0 before claiming done — `[verification]`
- Prefer `npm run lint:fix` over hand-fixing lint/format — `[verification] [lint] [tokens]`
- Use blob URLs for inline attachment previews — `[attachments] [electron]`
- Resolve Electron drag-and-drop file paths with webUtils — `[attachments] [electron]`
- Preserve uploader local attachment paths across sync — `[attachments] [persistence]`
- Interview before coding; dont guess the fix — `[workflow] [bugs] [tokens]`
- Prove the asked behavior; unit-green is not done — `[verification] [testing] [workflow]`
- Default to `toju-app/` + targeted `electron/` + CI; do not crawl the monorepo — `[workflow] [tokens] [scope]`
- Write/overwrite HANDOFF.md for new chats; clear it when the task finishes — `[workflow] [tokens] [handoff]`
+58 -9
View File
@@ -1,12 +1,12 @@
# Agent Lessons
Durable rules for AI agents working on this project. Read this file at session start. Append to it when this session produces a correction worth remembering.
Durable rules for AI agents working on this project.
## How to use this file
**At session start:** scan the rules below. If any match the work you're about to do, apply them.
**At session start:** read `agents-docs/LESSONS-INDEX.md` only. Open lesson bodies here **only** for tags that match the task. Do not load this entire file into context by default.
**During the session:** if the user corrects you, reverts your edit, or re-prompts with the same instruction — that is a signal to record a lesson before closing the task. See the trigger list in `agents-docs/AGENT_WORKFLOW.md`.
**During the session:** if the user corrects you, reverts your edit, or re-prompts with the same instruction — record a lesson here **and** add a one-line entry to `LESSONS-INDEX.md` before closing the task. See triggers in `agents-docs/AGENT_WORKFLOW.md`.
**Format of a lesson:** every entry uses the four-slot template below. Brevity matters — if you can't state the rule in one sentence, the lesson isn't sharp enough yet.
@@ -25,6 +25,34 @@ Durable rules for AI agents working on this project. Read this file at session s
## Lessons
### Interview before coding; dont guess the fix [workflow] [bugs] [tokens]
- **Trigger:** about to edit product code for a bug/feature after reading the ask or Obsidian note, while acceptance, approach, or scope is still ambiguous or has real alternatives.
- **Rule:** send a short interview (understanding, gaps, A/B/C + recommended default, proposed scope, proof of done), wait for the users choices, then implement only that — skip only if they said “just fix it” / “no interview.”
- **Why:** unprompted guesses cause wrong fixes and expensive back-and-forth; one clarifying turn costs less than a wrong implementation thread.
- **Example:** `fix bug "Images and files in chat doesn't load"` → read the note → ask whether the failure is channel-switch blank vs cold reload vs both before touching attachment services.
### Default to `toju-app/` + targeted `electron/` + CI; do not crawl the monorepo [workflow] [tokens] [scope]
- **Trigger:** about to browse all of `electron/`, or to `grep`/`Read` under `server/`, `e2e/`, `website/`, or `docs-site/` on a normal product bug without the user naming those packages.
- **Rule:** stay in `toju-app/`, `.gitea/workflows/`, and **only the Electron files on the renderer→preload→handler path**; if the fix looks like `server/`/e2e, ask once instead of exploring those trees.
- **Why:** monorepo-wide (and whole-`electron/`) exploration multiplies context on expensive problem-solving models without fixing the asked client bug.
- **Example:** attachment disk restore → `toju-app` persistence service + `electron/preload.ts` + the one IPC/file helper involved — not every file under `electron/migrations/` or `electron/api/`.
### Write HANDOFF.md and ask the user for a new chat — agents cannot open chats [workflow] [tokens] [handoff]
- **Trigger:** the thread is long, the user says "handoff"/"new chat", or a new major objective starts while more work remains.
- **Rule:** **overwrite** (never append) `agents-docs/HANDOFF.md` with `Status: active` and short sections, ask the user to start a new chat with that file; when the handoff task is finished, **clear** the file to `Status: none` with empty sections.
- **Why:** fat chat history dominates token burn; an appending handoff file becomes a second fat archive that every new chat reloads.
- **Example:** user: "handoff" → replace HANDOFF → reply: "Start a new chat and attach `@agents-docs/HANDOFF.md`." Later when done → reset HANDOFF to empty `Status: none`.
### Prove the asked behavior; unit-green is not done [verification] [testing] [workflow]
- **Trigger:** about to report a task finished because colocated Vitest specs (or a narrow mocked unit) are green, while the users ask was a product behavior, UI flow, or bug they can still reproduce.
- **Rule:** treat acceptance as “the asked functionality works” — prove it with a user-visible path, focused e2e, or an explicit manual check; keep unit tests as support, never as the sole done signal.
- **Why:** agents optimized for TDD often stop at implementation-shaped tests that pass while the real feature/bug remains broken, which wastes follow-up turns and burns tokens on false completion.
- **Example:** for “DM reply doesnt show for the caller,” a passing `DirectMessageService` mock test is insufficient until the cross-signal conversation identity path is exercised (e2e or a behavior-level regression that fails on the old fork-thread bug).
### Keep `NgOptimizedImage` off runtime blob and data URLs [angular] [images]
- **Trigger:** Angular template lint suggests replacing `[src]` with `ngSrc` for a user-uploaded image rendered from `blob:` or `data:`.
@@ -34,10 +62,10 @@ Durable rules for AI agents working on this project. Read this file at session s
### Read the exact Obsidian bug note before diagnosing a named ticket [workflow] [bugs]
- **Trigger:** The user names a `Bug - …` ticket, but the worktree already contains plausible changes or a similarly named resolved ticket.
- **Rule:** Resolve the exact note under `Log/Bugs/`, read every reported variant and reproduction step, and only then decide which code changes and status update belong to that ticket.
- **Why:** attachment reload-host changes looked related to “Images and files in chat doesn't load” but came from a separate resolved ticket and did not cover the reported channel-switch state regression.
- **Example:** read `/home/ludde/Nextcloud/Obsidian Vault/Log/Bugs/Bug - Images and files in chat doesn't load.md` before implementing or committing its fix.
- **Trigger:** The user says `fix bug "…"`, names a `Bug - …` ticket, or the worktree already contains plausible changes / a similarly named resolved ticket.
- **Rule:** Resolve and read **only** that note under `Log/Bugs/` (and its attachment folder if needed); use Expected Result as acceptance; fix in default scope; do not list the whole inbox or treat `BUG_TRACKER.md`'s snapshot table as live.
- **Why:** attachment reload-host changes looked related to “Images and files in chat doesn't load” but came from a separate resolved ticket and did not cover the reported channel-switch state regression; inbox-wide reads also burn tokens for no gain.
- **Example:** `fix bug "Images and files in chat doesn't load"` read `/home/ludde/Nextcloud/Obsidian Vault/Log/Bugs/Bug - Images and files in chat doesn't load.md` only, then implement against its Steps/Expected.
### Run `npm run i18n:sync` after editing any `public/i18n/catalog/*.json` file [i18n] [testing]
@@ -53,6 +81,20 @@ Durable rules for AI agents working on this project. Read this file at session s
- **Why:** the failure only reproduces when caller and callee have different home signal servers, which no same-server e2e covers; and when one identity-alias bug is fixed in a domain, grep for the same `=== currentUserId` pattern in sibling domains that share the transport — the direct-call domain reused `PeerDeliveryService` but kept the naive check for another month.
- **Example:** `direct-call-participant-identity.rules.ts#directCallPayloadIncludesAnyId` / `normalizeDirectCallPayloadSelfAliases`; regression e2e `e2e/tests/voice/dm-header-call-ring.spec.ts` registers Bob on a secondary signal server, meets in a primary-signal room, and asserts the DM-header call rings Bob's incoming-call modal (fails on old code, passes after).
### Resolve outbound direct-call recipient ids to the peer's connected signal identity [direct-call] [identity] [signaling]
- **Trigger:** cross-signal direct calls still failed after the inbound alias fix — the caller joined voice and showed "In voice" while the callee never rang. `PeerDeliveryService.resolveSignalingPeerId` returned null when the stored peer id was a home id but presence/route was registered under the provisioned actor id, so `sendRawMessage` was never called; even when attempted, the server relays only when `targetUserId` exactly matches the callee's connected `oderId`.
- **Rule:** outbound DM/call delivery must collect every recipient alias (`peer-delivery-identity.rules.ts#collectRecipientDeliveryCandidateIds`), pick the routable id with `pickRoutableRecipientId`, always attempt signaling send (broadcast fallback when no single route works), and surface `call.errors.recipientUnreachable` to the caller when delivery cannot succeed — never leave the caller in a silent "In voice" state.
- **Why:** inbound and outbound identity bugs are independent; fixing admission on the callee does not help if the ring never leaves the caller or hits the wrong `targetUserId` on the wire.
- **Example:** `PeerDeliveryService.sendViaSignaling` + `DirectCallService.resolveRoutableRecipientId`; e2e `e2e/tests/voice/dm-header-call-ring.spec.ts` (callee-home room, people-search call).
### Canonicalize direct conversation ids across cross-signal actor aliases [direct-message] [identity]
- **Trigger:** cross-signal DMs delivered to the callee but the caller never saw replies — User1 had two DM rows for User2, replies landed in the actor-id thread, and DM routes could throw "Cannot use direct messages without a current user" when `openConversation` ran before hydration.
- **Rule:** incoming DM traffic must resolve to the existing home-id conversation for the same peer (`direct-message-conversation-identity.rules.ts`), merge duplicate alias threads on load/receive, queue inbound events until `currentUser` hydrates, and guard route-driven `openConversation` calls when no owner id is available yet.
- **Why:** recipient-alias admission alone is insufficient — if `message.conversationId` still carries a foreign actor id, the client forks a second local thread and the original chat stays empty.
- **Example:** `resolveDirectConversationId` + `mergeAliasDirectConversations` in `DirectMessageService.handleIncomingMessage`; `appendCrossSignalActorAliases` + `pickDeliveryTargetIds` when a foreign-home recipient is addressed by home id but only their shared-room actor id is routed (debug pattern: `c84813bb…` home id vs `e79a95e5…` actor on `signal.toju.app`).
### Decide attachment receive admission once at request time; never re-gate size in the chunk handler [attachments]
- **Trigger:** "Sending files between users doesn't really work" — a browser user clicked Request on a 1050 MB generic file, the request gate (`canReceiveAttachment`) admitted it for in-memory receive, the sender streamed chunks, but `handleFileChunk` still had a leftover hard `size > MAX_AUTO_SAVE_SIZE_BYTES` rejection on the in-memory path, so every chunk was dropped, no ack was ever sent, the sender's `waitForAck` timed out, and the GUI never changed.
@@ -291,12 +333,19 @@ Durable rules for AI agents working on this project. Read this file at session s
- **Why:** duplicated chrome makes CTA/product previews look broken, and bottom-aligned large headings can cover accompanying text on the marketing site.
- **Example:** `website/src/app/pages/home/home.component.html` should render the screenshot directly; `host-section` should use top-aligned heading and `.host-section-copy` columns.
### Prefer `npm run lint:fix` over hand-fixing lint/format [verification] [lint] [tokens]
- **Trigger:** about to manually re-indent, reorder imports, or tweak Prettier/ESLint-fixable style after seeing lint failures.
- **Rule:** from repo root run `npm run lint:fix` (`format` + `sort:props` + `eslint . --fix`); only hand-edit remaining non-fixable errors.
- **Why:** manual style fixes burn turns and tokens and often miss what the project script already auto-corrects.
- **Example:** after code changes → `npm run lint:fix` → if exit 0, do not also rewrite imports by hand.
### Verify lint exits 0 before claiming done [verification]
- **Trigger:** about to report a task as complete after running tests but skipping ESLint.
- **Rule:** run `npm run lint` from the repo root and confirm exit code 0 before any "done" claim.
- **Rule:** run `npm run lint:fix` from the repo root (or `npm run lint` after fixes) and confirm exit code 0 before any "done" claim.
- **Why:** `npm run test` only runs the toju-app Vitest suite — it doesn't cover the server, Electron, or website packages. ESLint (flat config in `eslint.config.js`) is the universal check across every package; type-style violations slip through tests and break Gitea Workflows for the next agent.
- **Example:** `npm run lint && echo OK` — only claim done after seeing `OK`. For Electron type errors specifically, also confirm `npm run build:electron` succeeds (it invokes `tsc -p tsconfig.electron.json`).
- **Example:** `npm run lint:fix && echo OK` — only claim done after seeing `OK`. For Electron type errors specifically, also confirm `npm run build:electron` succeeds (it invokes `tsc -p tsconfig.electron.json`).
### Use blob URLs for inline attachment previews [attachments] [electron]