AGENTS.md
Rama main · réplica de lectura del repositorio original.
# AI Agent Instructions for Inkweld
Inkweld is a collaborative creative writing platform. This file holds the rules
that apply to every change. Notes for one area live in `AGENTS.md` files next
to the code (`frontend/`, `backend/`, and folders below them); **before
changing code in an area, read the `AGENTS.md` files on the path to it** if your
tool hasn't loaded them already. Longer design notes are in the developer docs
(`docs/site/docs/developer/`). The full map is at the end.
## Stack
- **Frontend** (`frontend/`): Angular, standalone components, Vitest +
Playwright, Transloco i18n. Rules: `frontend/AGENTS.md`.
- **Backend** (`backend/`): Hono on Bun (also Node and Cloudflare Workers),
Drizzle ORM on SQLite/D1, LevelDB per project (Durable Objects on Workers).
Rules: `backend/AGENTS.md`.
- **Real-time**: Yjs CRDTs over WebSocket, offline-first with IndexedDB.
- **Shared packages** (`packages/`): `inkweld-async`, `inkweld-canvas`,
`inkweld-presence`, `inkweld-project-library`, `inkweld-prosemirror`.
## Rules for every change
1. **Bun installs only.** Run `bun install` from the repo root. Never run
`npm install` / `npm ci` inside `frontend/`, `backend/` or `docs/site/`
(a `preinstall` guard errors). `npm test`, `npm run lint`,
`npm run e2e` are fine.
2. **Tests.** Every change needs test coverage and all tests must pass.
Frontend: `npm test` (Vitest), **never `bun test`**, never Jasmine.
Backend: `bun test`.
3. **Verify before declaring done**: `bun run verify` runs type checking,
lint, the unit tests (packages, frontend, backend) and a docs build. It
passes only when the last line reads `--- verify (fast) passed in <N>s ---`.
Use `bun run verify:e2e` or `bun run verify:full` when e2e tests or the
docs screenshots are affected.
4. **Never disable a lint rule** without asking the user first; fix the issue
instead. Document any approved exception in a comment.
5. **No summary markdown files** (FIXES.md, STATUS.md, …) unless asked;
summarize in chat. Keep `AGENTS.md` files, READMEs and docs up to date when
behaviour changes.
6. **Never edit `frontend/src/api-client/`.** After an API change, regenerate:
`cd backend && bun run generate:openapi && bun run generate:angular-client`
(needs a Java runtime). CI's "OpenAPI Spec + Client Freshness" job fails if
`backend/openapi.json` or the client are stale.
7. **Never push to `production`** unless asked: it deploys inkweld.app. It only
fast-forwards to a commit already on `main`
(`git push origin origin/main:production`). `main` deploys
preview.inkweld.app. Deploy steps live in
`deploy-cloudflare-reusable.yml`; see
`docs/site/docs/admin-guide/ci-cd.md`.
## Commands
```bash
bun install # from the repo root
npm run dev # frontend :4200 + backend :8333
npm test # all unit tests
npm run lint # npm run lint:fix to auto-fix
bun run format # bun fmt; format:check only reports
npm run typecheck # every TS project, via `bun check`
bun run verify # the pre-completion check
bun run deploy # interactive deployment wizard
```
API docs (Swagger UI) are served at <http://localhost:8333/api>.
**Type checking** uses `bun check` (Bun ≥ 1.4.3), a typescript-go port that
reports the same errors as `tsc`. It is the only check on specs, e2e and the
backend: Vitest, Playwright and `bun test` strip types without checking them,
and `ng build` covers only the app sources. It follows TypeScript 7, so an
option TS 7 removed (e.g. `baseUrl`) is an error even though `tsc` 6 accepts
it. `frontend/tsconfig.json` only references the real projects
(`"files": []`), so check it with `bun check -b`.
## Code style
- Prettier and ESLint configs are in each package. `bun fmt` formats (it
reads `.prettierrc*`); ESLint does not check formatting, so run
`bun run format` and `npm run lint:fix` before committing
(`bun run format:check` is what verify and CI run). TypeScript strict
mode; small, focused functions; comment complex logic; follow the
surrounding patterns.
- **Awaiting in loops**: SonarCloud flags `await` in a loop (S9382) and
`async` functions with no `await` (S7503). Don't disable the rules.
Independent iterations: `await Promise.all(items.map(...))`, or
`mapWithConcurrency` (`backend/src/utils/concurrency.ts`) /
`forEachConcurrent` (`@inkweld/async`) when the fan-out must be bounded.
Work that must run in order (ordered writes, IndexedDB transactions on one
database, rate-limited calls, progress, early exit): `@inkweld/async`'s
`forEachSequential`, `mapSequential`, `firstResultSequential`,
`forEachPage`, `chunk`, with a one-line comment saying why it is
sequential. An `async` function that never awaits: drop `async` and return
`Promise.resolve(...)` / `Promise.reject(...)`; if the body can throw
synchronously and callers rely on a rejection, keep `async` and
`return await` the promise.
## Releases and versioning
release-please cuts releases (`.github/workflows/release-please.yml`,
`release-please-config.json`); the process is under "Releasing" in
`docs/site/docs/admin-guide/ci-cd.md`.
- **PR titles are the changelog.** PRs are squash-merged, so the Conventional
Commit title decides the bump (`fix` patch, `feat` minor, `!` major; during
the 1.0 beta every release is the next `1.0.0-beta.N`) and the CHANGELOG
line. `docs`/`refactor`/`test`/`build`/`ci`/`chore`/`style` are hidden and
never cause a release; Renovate uses `chore(deps)`. `feat`/`fix` titles
also show up as highlights in the in-app update dialog
(`frontend/scripts/write-sw-app-data.ts` writes them into `ngsw.json`
`appData`), so write them for users.
- **Scopes come from `.github/commit-scopes.json`.** A scope is optional; when
used it must be on that list (CI checks it), and each one has the emoji
`release-please.yml` adds to its release-note entries
(`scripts/add-scope-emoji.mjs`). Add a new scope there in the PR that
needs it.
- **Never bump versions by hand.** release-please updates the three
`package.json` files and every line marked `// x-release-please-version`
(`backend/src/config/env.ts`, `frontend/src/environments/environment*.ts`).
A new hardcoded app version needs that marker and an `extra-files` entry.
`MIN_CLIENT_VERSION` and the OpenAPI `info.version` are compatibility/API
versions, not the app version.
- `CHANGELOG.md` is rendered in-app (About → Changelog) by `ChangelogService`,
which parses `## [X.Y.Z] - date` and release-please's
`## [X.Y.Z](compare-url) (date)` headings. Don't add a `## [Unreleased]`
section: release-please inserts new entries above the first `## [` heading.
- **First-time contributors are thanked in the GitHub release only.** After
release-please creates a release, `release-please.yml` runs
`scripts/thank-new-contributors.mjs`, which appends GitHub's generated
`## New Contributors` section to the release notes. It is best effort
(`continue-on-error`) and never touches `CHANGELOG.md`.
- Tags pushed by release-please don't trigger workflows (GITHUB_TOKEN), so it
calls `release.yml` (multi-arch Docker image) via `workflow_call`.
- **Upgrade path commitment.** From 1.0.0-beta.1 on, every release must
upgrade automatically from any earlier one (README "Status"): forward
Drizzle migrations only, local IndexedDB/Yjs data upgraded in place, and
archives at every earlier `ARCHIVE_VERSION` still importable. A change that
would make users export, reset or migrate by hand needs an automatic
migration instead.
- Versions can carry a pre-release suffix (`1.0.0-beta.1`);
`version-compatibility.service.ts` compares only `MAJOR.MINOR.PATCH`.
## Gotchas that apply anywhere
- **Yjs Document ID Trailing Slash**: the frontend uses
`username:slug:elements`, the backend/MCP `username:slug:elements/`.
**This is not a bug; do not "fix" it.** y-websocket normalizes them. If you
see it while debugging sync, look elsewhere.
- **Local document databases are profile-scoped**: IndexedDB names carry
`storageContext.prefixDocumentId(docId)`; WebSocket ids stay bare.
(`frontend/src/app/services/core/AGENTS.md`)
- **AI kill switch**: every new endpoint that sends content to an AI provider
must be gated with `requireAiEnabled` and listed in
`backend/test/ai-kill-switch.test.ts`. Frontend AI features gate on the
user's AI preference too. (`backend/AGENTS.md`, `frontend/AGENTS.md`)
- **Backend routes that edit a Yjs document** use `openEditableDocument()`,
never `yjsService` directly: `yjsService` is Bun-only and crashes on
Workers. (`backend/AGENTS.md`)
- **Storage quotas**: every server write that adds project storage calls
`quotaService.assertCanStore()`; Yjs editing/sync is never blocked.
(`backend/AGENTS.md`)
- **Bulk sync is conservative**: the planner may cause extra syncs, never a
missed edit. Never use a Durable Object's `snapshot` key as a revision
token. (`frontend/src/app/services/sync/AGENTS.md`,
`backend/src/durable-objects/AGENTS.md`)
- **Never construct the ProseMirror `Schema` in `packages/inkweld-prosemirror`**;
it breaks typing in the editor. (`frontend/src/app/components/element-ref/AGENTS.md`)
- **The built `index.html` must contain no inline script**
(`inlineCritical: false` stays) and `script-src` never gets
`'unsafe-inline'`. (`docs/site/docs/developer/legal-pages-and-custom-html.md`)
- **The frontend must work under a path prefix** (Home Assistant ingress):
relative asset URLs, `appUrl()` / `appOrigin()` for navigations.
- **Ingress headers are trusted by TCP peer only**, never by
`X-Forwarded-For`. (`docs/site/docs/developer/home-assistant-ingress.md`)
- **Passkey timestamps are seconds**; the frontend multiplies by 1000. Passkey
ceremonies use `rpFromRequest(c)`. (`docs/site/docs/developer/passkeys.md`)
- **Wrangler is pinned to exactly 4.116.0 with a local patch.** Don't change it
to a range. (`backend/AGENTS.md`)
- **Pull requests from forks get no secrets.** A `pull_request` job that reads
one other than `GITHUB_TOKEN` must skip forks, with
`github.event.pull_request.head.repo.full_name == github.repository`; never
use `pull_request_target` for it. (`docs/site/docs/admin-guide/ci-cd.md`)
- **Reserved usernames** (`privacy`, `terms`, `share`, `public`, `delete-account`) back
real routes; frontend ones stay above the `:username` route.
- **Mirrored lists** must stay in step: `schemaContentHash` (frontend and
backend `utils/schema-hash.ts`, same golden hash), the background preset
ids (`background-presets.ts` ↔ `BACKGROUND_PRESET_IDS`), the tag icons and
colours (tag editor dialog ↔ MCP `tag-store.ts`) and the rule for new
usernames (backend `schemas/auth.schemas.ts` ↔ frontend
`utils/username-rules.ts`; `backend/AGENTS.md`).
- **Project archive format changes** bump `ARCHIVE_VERSION`
(`frontend/AGENTS.md`).
- **No hardcoded user-facing English** in the frontend; use Transloco keys.
## Where the details live
| Topic | Location |
|---|---|
| Angular conventions, tests, e2e, i18n, frontend-wide rules | `frontend/AGENTS.md` |
| Hono conventions, migrations, AI kill switch, quotas, serving `index.html`, canvas rendering, auth, rename, share links, sync manifest | `backend/AGENTS.md` |
| Durable Object storage and revision tokens | `backend/src/durable-objects/AGENTS.md` |
| Profile-scoped storage, server profile ids, profile upgrades, project rename, APP_MODE, app backgrounds, update dialog | `frontend/src/app/services/core/AGENTS.md` |
| Starting offline, bulk sync planner | `frontend/src/app/services/sync/AGENTS.md` |
| Canvas Yjs model | `frontend/src/app/services/canvas/AGENTS.md` |
| Where the session token is sent, passkeys and policy acceptance (frontend) | `frontend/src/app/services/auth/AGENTS.md` |
| WebMCP browser tools | `frontend/src/app/services/webmcp/AGENTS.md` |
| EPUB output, published file share links | `frontend/src/app/services/publish/AGENTS.md` |
| Per-element schema copies, identity images | `frontend/src/app/services/worldbuilding/AGENTS.md` |
| Template (schema) editor | `frontend/src/app/components/worldbuilding/AGENTS.md` |
| Editor schema and tables | `frontend/src/app/components/element-ref/AGENTS.md` |
| Markdown input rules | `frontend/src/app/components/editor-input-rules/AGENTS.md` |
| Conversations (speech-bubble documents) | `frontend/src/app/components/conversation/AGENTS.md` |
| Shared ProseMirror specs | `packages/inkweld-prosemirror/AGENTS.md` |
| Architecture, real-time layer, bulk sync design | `docs/site/docs/developer/architecture.md` |
| Passkeys (key files, data flow, env vars) | `docs/site/docs/developer/passkeys.md` |
| Legal pages, custom HTML, CSP, service worker hash | `docs/site/docs/developer/legal-pages-and-custom-html.md` |
| Account deletion | `docs/site/docs/developer/account-deletion.md` |
| Home Assistant ingress | `docs/site/docs/developer/home-assistant-ingress.md` |
| Project archives | `docs/site/docs/developer/project-archives.md` |
| Cloud Sync | `docs/site/docs/developer/cloud-sync.md` |
| CI/CD, hosted deploys, wrangler pin history | `docs/site/docs/admin-guide/ci-cd.md` |
| Sync capacity (admin view) | `docs/site/docs/admin-guide/sync-capacity.md` |
| E2E practices | `frontend/e2e/BEST_PRACTICES.md` |
| Getting started | `docs/site/docs/getting-started.md` |
If you hit a pattern or trap that isn't covered, add it to the `AGENTS.md`
closest to the code it concerns, and add a one-line pointer here only if it
can bite anywhere in the repo.