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>
This commit is contained in:
2026-07-22 21:49:06 +02:00
co-authored by Cursor
parent cdc32db30f
commit cfe10907be
8 changed files with 293 additions and 156 deletions
+24
View File
@@ -0,0 +1,24 @@
---
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, write `agents-docs/HANDOFF.md` and tell the user to start a **new chat** with `@agents-docs/HANDOFF.md`.
## 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
1. Overwrite `agents-docs/HANDOFF.md` — set `Status: active`; fill Goal, Completed, Changed files, Decisions, Failed approaches, Current issue, Next steps, Commands.
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 to paste in the new chat.
## New-chat bootstrap
If `agents-docs/HANDOFF.md` has `Status: active`, read it first and continue Next steps. Do not reload the whole monorepo or redo Completed work.
+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
+74 -79
View File
@@ -1,103 +1,98 @@
# AGENTS.md # 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 ## Session handoff (new chats)
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
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` 1. Write/update `agents-docs/HANDOFF.md` (`Status: active`) — see `.cursor/rules/handoff.mdc`.
- `agents-docs/AGENTS_ADRS.md` — contract for writing architecture decision records 2. Ask the user to start a **new chat** and attach `@agents-docs/HANDOFF.md`.
- `agents-docs/BUG_TRACKER.md` — Obsidian bug inbox location, allowed vault edits, and triage workflow
When working in a subdomain, also read its `CONTEXT.md` first: **At session start:** if `agents-docs/HANDOFF.md` has `Status: active`, read it first and continue Next steps. Do not redo Completed work.
- Product client (Angular 21): `toju-app/CONTEXT.md` ## Default work scope (token fence)
- Desktop shell (Electron main + preload): `electron/CONTEXT.md`
- Signaling server (Express + WebSocket): `server/CONTEXT.md` Unless the user **explicitly** expands scope, stay inside:
- End-to-end tests (Playwright): `e2e/CONTEXT.md`
- Marketing site (Angular 19): `website/CONTEXT.md` - `toju-app/` (primary)
- Application documentation (Docusaurus): `docs-site/CONTEXT.md` - `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) **Unit/spec green is support, not done.**
**Write tests before implementation code.**
When creating or changing anything: 1. Restate acceptance in one sentence.
1. STOP — do not write implementation first 2. Prove the behavior (user-visible path, focused test at the right level, or explicit manual check).
2. Write failing tests (RED) 3. Prefer a regression that fails if the asked behavior regresses.
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
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) For domain/logic: failing behavior-level test → minimal fix → green.
Before completing any task: Skip full red-green for docs/copy/agent text, formatting, trivial wiring already covered higher up.
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
### Type / build correctness (MANDATORY) **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.
Type checks live in build scripts:
- Product client (`toju-app/`): `npm run build` (Angular CLI runs `tsc` with strict settings) ### Lint / type correctness (scoped)
- Electron (`electron/`): `npm run build:electron` (invokes `tsc -p tsconfig.electron.json`)
- Server (`server/`): `cd server && npm run build` (invokes `tsc`)
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. `npm run lint` — fix errors; do not paste entire lint logs into the chat.
3. `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/`. 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.
## 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.
## Completion checklist ## Completion checklist
Before marking work complete: - [ ] Asked behavior proven (not only unit tests green)
- [ ] Stayed in scope (`toju-app` + targeted `electron` + CI) unless user expanded it
- [ ] Tests written before implementation - [ ] Appropriate targeted tests for logic changes
- [ ] All tests passing (`npm run test`, plus `npm run test:e2e` if behavior is user-visible) - [ ] Lint/build only for touched packages
- [ ] `npm run lint` passes - [ ] Docs only if contracts changed
- [ ] Affected package builds: `npm run build` / `npm run build:electron` / `cd server && npm run build` - [ ] Lesson + index entry if corrected this session
- [ ] Naming conventions followed (kebab-case files; domain `*.rules.ts` / `*.model.ts` / `*.component.ts` suffixes) - [ ] If the thread is long and work remains: write `HANDOFF.md` and ask user for a new chat
- [ ] Errors handled - [ ] PR when requesting merge (`Fixes #<n>` / `Relates to #<n>`)
- [ ] 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
+36 -72
View File
@@ -1,110 +1,74 @@
# Agent Workflow & Operating Instructions # 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.
- **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 ## Workflow Orchestration
### 1. Plan Mode Default ### 1. Plan only when ambiguity is real
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions) - Plan for unclear architecture or when the user asks.
- If something goes sideways, STOP and re-plan immediately — don't keep pushing - Skip plan mode for focused, in-scope edits with clear acceptance criteria.
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
### 2. Subagent Strategy ### 2. Subagents sparingly
- Use subagents liberally to keep the main context window clean - Default: one agent.
- Offload research, exploration, and parallel analysis to subagents - Subagents only for true parallel search inside allowed paths.
- For complex problems, throw more compute at it via subagents - Never spawn extra review agents unless the user asks.
- One task per subagent for focused execution
### 3. Self-Improvement Loop ### 3. Handoff / short sessions
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. Triggers: user says handoff / new chat; thread is long with more major work left; switching objectives.
**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. Action: overwrite `agents-docs/HANDOFF.md` with `Status: active` (Goal, Completed, Changed files, Decisions, Failed approaches, Current issue, Next steps, Commands). Then stop major work and ask the user to open a new chat.
**Triggers — record a lesson when any of these happen.** Don't wait for a formal request; these are the signals: New chat: if handoff is active, read it first; continue Next steps; do not redo Completed work.
- User says "no", "actually", "don't", "stop", "that's wrong", or "instead do X" ### 4. Self-Improvement Loop
- 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
If unsure whether it's worth recording: write it. Sharper is better than missing, and grooming the file is cheap. **At session start:** `LESSONS-INDEX.md` only; open matching lesson bodies by tag.
**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. Record a lesson + index line when corrected. Prefer fewer sharp rules (~20).
**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. CONTEXT.md upkeep
### 4. CONTEXT.md upkeep Default: `toju-app/CONTEXT.md`. Read `electron/CONTEXT.md` only when touching Electron. Other packages only when in scope.
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`. ### 6. ADR upkeep
**Triggers — capture vocabulary in the moment:** Only when hard-to-reverse + surprising + real trade-offs. Contract: `agents-docs/AGENTS_ADRS.md`.
- A previously-ambiguous domain term gets a clear resolution → add it (one-sentence definition, aliases to avoid). ### 7. Verification Before Done (behavior first)
- 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.
**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. Done = asked functionality works. Unit green ≠ done for product asks. Prefer in-scope proof; dont pull e2e/server “just to be sure.”
### 5. ADR upkeep ### 8. Demand Elegance (Balanced)
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. One pause for non-trivial design; skip for obvious fixes.
**Triggers — write an ADR only when all three apply:** ### 9. Autonomous Bug Fixing
- **Hard to reverse** (schema migration, framework swap, integration redesign). Fix with evidence in default scope. If root cause is clearly `server/` (or similar), say so and ask to expand scope.
- **Surprising without context** (future engineers will question the approach).
- **Result of genuine trade-offs** (real alternatives existed and you chose deliberately).
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.
**Supersede, don't delete.** Overturned decisions get a new ADR; the old one stays with a `Superseded by ADR-NNNN` note.
### 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
--- ---
## Pull Requests ## Pull Requests
This project hosts at Gitea (`git.azaaxin.com/myxelium/Toju`). Gitea PRs and issues use GitHub-style syntax. Gitea: `git.azaaxin.com/myxelium/Toju`. Branch `<type>/<short-description>`; PR with summary + test plan; `Fixes #<n>` / `Relates to #<n>`.
- 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
--- ---
## Core Principles ## Core Principles
- **Simplicity First:** Make every change as simple as possible. Impact minimal code. - **Simplicity First** · **No Laziness** · **Minimal Impact** · **Cheap Context** · **Default Scope Fence** · **Handoff Beats Fat Context**
- **No Laziness:** Find root causes. No temporary fixes. Senior developer standards.
- **Minimal Impact:** Changes should only touch what's necessary. Avoid introducing bugs.
+7 -2
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. 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. 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.
+29
View File
@@ -0,0 +1,29 @@
# Session Handoff
> **New chat:** attach `@agents-docs/HANDOFF.md` (and only the files listed under Changed files). Say: continue from this handoff; do not redo completed work. Stay in default scope unless Next steps expand it.
>
> **Agents cannot open a new chat** — write/update this file, then ask the user to start one.
**Status:** none
<!-- When Status is `active`, fill every section below. When done with a handoff cycle, set Status back to `none` or overwrite on the next handoff. -->
## 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/`.
+58
View File
@@ -0,0 +1,58 @@
# 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 before diagnosing a named ticket — `[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]`
- 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]`
- 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 HANDOFF.md and ask the user for a new chat — agents cannot open chats — `[workflow] [tokens] [handoff]`
+38 -3
View File
@@ -1,12 +1,12 @@
# Agent Lessons # 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 ## 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. **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,27 @@ Durable rules for AI agents working on this project. Read this file at session s
## Lessons ## Lessons
### 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 `agents-docs/HANDOFF.md` with `Status: active` and the eight sections, then ask the user to start a new chat attaching that file; never pretend you opened a fresh chat.
- **Why:** fat chat history dominates token burn on problem-solving models; a 510k handoff replaces 100k+ of mixed failure/tool output.
- **Example:** user: "handoff" → write HANDOFF → reply: "Start a new chat and attach `@agents-docs/HANDOFF.md`; say continue from handoff."
### 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] ### 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:`. - **Trigger:** Angular template lint suggests replacing `[src]` with `ngSrc` for a user-uploaded image rendered from `blob:` or `data:`.
@@ -53,6 +74,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. - **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). - **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] ### 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. - **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.