# Minddy agent rules and development conventions `AGENTS.md` and `CLAUDE.md` are identical copies of this guide, so every coding agent reads the same rules. Edit both files together; `scripts/agent-instructions.test.mjs` fails when they differ. ## Architecture and entry points Minddy is a Next.js App Router application with React, Tailwind CSS, and its own UI components. Supabase provides Postgres, Auth, Storage, and Realtime. The optional Electron shell has a separate package and build under `desktop/`. | Area | Source of truth | | --- | --- | | Authenticated product routes | `app/(app)/` | | Marketing and legal routes | `app/(marketing)/`, `app/(legal)/`, `lib/public-routes.ts` | | HTTP APIs and public capabilities | `app/api/`, `app/f/`, `app/p/` | | UI primitives and design tokens | `components/ui/`, `app/tokens.css` | | Reusable UI and client behavior | `components/`, `lib/` | | Application URLs (short codes, never titles) | `lib/app-urls.ts`, `lib/ref-registry.ts` | | Rich text editor engine (every surface) and its block catalog | `components/editor/`, `components/pages/blocks/` | | Server business logic | `lib/server/` | | Browser, session, and privileged database clients | `lib/supabase.ts`, `lib/supabase-server.ts`, `lib/supabase-service.ts` | | Numo conversations and tools | `lib/server/numo/`, `lib/server/assistant/` | | Pull requests and forge integration | `lib/server/agent/`, `lib/server/git/` | | External MCP tools and authentication | `lib/server/mcp/` | | Database schema and policies | `supabase/migrations/` | | Locale catalogs and configuration | `messages/`, `i18n/config.ts` | | Release and maintenance tooling | `scripts/`, `.github/workflows/` | Build every in-app link with `lib/app-urls.ts`: URLs address records by an eight-character short code (`/projects/min-3f2a9c81`, `?issue=MIN-42`), never by a title, because names are encrypted at rest and paths reach logs and analytics. Read URL references back through `lib/ref-registry.ts` on the client or `lib/server/route-refs.ts` on the server; both still accept full ids. Server-only modules use the `server-only` import guard. Keep authorization at server boundaries; a privileged database client does not replace membership or capability validation. Follow the existing domain repository and codec when reading or writing protected content rather than accessing its raw columns. `npm run dev` and `npm run build` first generate the page-Markdown bundle through their lifecycle hooks. Vitest also builds the page projection through `test/build-pages-md-setup.ts`. The root TypeScript configuration excludes `desktop/`; CI checks the Electron bundle separately with `node scripts/build-desktop.mjs`. See [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), and [the edition guide](docs/editions.md) for setup and supported deployment modes. ## Language policy: English-owned prose - All new comments, docstrings, test descriptions, internal documentation, configuration prose, and developer-facing CLI messages must be written in idiomatic English. - Do not add French prose to application code, tests, documentation, scripts, or configuration. French runtime copy belongs in `messages/fr.json`; official reader documentation and its assets use their declared locale under `content/documentation//` and `public/documentation//`. - Preserve intentional runtime translations in every supported catalog, language-specific test fixtures, locale branches, proper names, legal credits, identifiers, URLs, API values, and behavior. Do not translate those into English copies. - When editing existing French prose, translate it as part of the same change unless it is one of the intentional runtime or language-fixture exceptions. Do not change runtime translations or test semantics to satisfy the language policy. ### Translation-task boundary Translate comments and documentation without changing code, identifiers, URLs, configuration keys, migration behavior, or test semantics. If a French string is required to test localization or to serve the French locale, keep it and make the exception obvious from its surrounding code or fixture path. ## Verification - Run `npm run check:owned-english` after changing comments, documentation, tests, scripts, or configuration prose. - Run the smallest relevant test or lint command after source/configuration changes (see [Lint and tests](#lint-and-tests)). - Review `git diff --check` and confirm that excluded paths are untouched. ## Keep documentation current - Always review documentation impact when modifying code. Add documentation for new features, correct it when behavior changes, and remove obsolete instructions and illustrations when a feature is removed. Complete affected public guides, technical references and internal instructions in the same PR; do not defer documentation to a later release or separate task. - Documentation edits are required only when the change affects documented behavior, controls, configuration or contributor workflows. A small fix, refactor, formatting change or test-only PR that leaves documentation accurate needs no documentation edit or new illustration. An accurate manual may need no prose edit, but it always needs an impact review. - Follow the [documentation maintenance contract](content/documentation/README.md#release-maintenance) and [editorial guide](docs/documentation-editorial-guide.md), including their writing, technical inline-code, factual review, illustration and capture rules. - Keep the public manual equally complete in `en`, `fr`, `de`, `es`, `it`, and `pt-BR`. Update full translations, localized UI labels, examples, captions, alt text, screenshots and responsive diagrams together. Internal contributor documentation remains English. - Every release that changes the desktop version requires a documentation review against the release delta and intended version before freezing the candidate. Update affected articles, illustrations, compatibility and the frontmatter `review` field; a version bump or changelog entry alone is insufficient. - Run `npm run check:documentation`, `npm run check:knowledge`, `npm run check:owned-english` and `git diff --check` for documentation changes, and require `npm run check:documentation:release` before publication. List affected article/workflow/figure IDs and checks in the pull request description, not in a committed file. For a PR with no documentation impact, a brief note in the description is sufficient; do not require article IDs or release-wide checks. Avoid cosmetic edits or invented review dates. Unresolved documentation gaps caused by the change block completion. ## Repository hygiene: what a pull request may add The repository holds the product, the tooling it runs again, and durable project documentation. The working material of a change (its plan, investigation, evidence and screenshots) belongs in the pull request description or the Minddy issue, never in the tree. Before opening or updating a pull request, read `git diff --stat origin/main...` and remove every file of the kinds below. - **Documentation.** Add a file under `docs/` only for durable, general knowledge: architecture, a cross-cutting contract, operations, security or a contributor workflow. A fix, a feature or a refactor does not add a Markdown file of its own; a standalone document is justified only when the change reshapes Minddy deeply (a new subsystem or trust boundary), and it is then written as a reference, not as a ticket report. Feature behavior goes into the public manual or an existing reference. Never commit per-ticket plans, audits, investigation notes, validation, performance or review reports, evidence JSON, checklists or launch drafts. - **Scripts.** Commit only scripts the project runs again: package scripts, CI, release, tests and capture tooling. Delete one-off probes, benchmarks, backfills, measurement harnesses and debugging scripts before the pull request, or keep them outside the repository. - **Images.** Committed images are product assets (`public/`, `app/`, `desktop/build/`), manual figures under `public/documentation//`, and capture masters `captures/shots//out/-.png`, which `captures/lib/publish.mjs` and `captures/focus-marketing.mjs` read. Pull request screenshots, before/after comparisons, debug captures and test output are never part of the tree. To show screenshots in a pull request description, save them under the ignored `output/` directory, commit them with `git add -f`, remove them in the next commit and push both commits together; link each image through the first commit's SHA (`https://github.com/mangue-dev/minddy/blob//?raw=true`). Squash merging keeps them out of `main`, and the pull request keeps that commit reachable. Never rewrite that commit away while the description links it. - **Generated output.** Playwright results, capture run records, benchmarks and other run output go under the ignored `output/` directory. `npm run check:public-repo` (also run in CI) rejects the known working-material locations: `output/`, `docs/{audits,plans,validation,performance,reviews,screenshots}/`, images anywhere under `docs/`, `content/documentation/reviews/`, `scripts/performance/`, deepsec scan output under `.deepsec/data/` and capture outputs that are not masters. Passing it does not make another report, script or image acceptable. ## UI components Minddy owns its UI primitives: `components/ui/` holds one implementation per component (Radix-based controls, dialogs, sheets, menus, tooltips) and `app/tokens.css` the design tokens that `app/globals.css` imports. Change a primitive in its file rather than wrapping or re-exporting it under the same name elsewhere; a variant with different behavior gets its own name, as `ResponsivePopover` does next to `Popover`. Import primitives from their module (`@/components/ui/button`, `@/lib/utils` for `cn`, `sonner` for `toast`); there is no UI barrel. The former `mangue-ui` package is no longer a dependency, and `lib/ui-components-ownership.test.ts` fails if it returns. The persisted theme key `mangue-ui-theme` is kept for existing preferences. [DESIGN.md](DESIGN.md) holds the interface rules every UI change follows, starting with the rule that destructive actions always ask for confirmation through `ConfirmDeleteDialog`. ## Internationalization: catalog and call-site contract Visible interface copy uses `next-intl`. The supported locales are `en`, `fr`, `de`, `pt-BR`, `it`, and `es`, defined in `i18n/config.ts`. Keep all catalogs aligned on keys and placeholders when changing a message. A placeholder message must receive its values: ```tsx // messages/en.json: "deleteViewTitle": "Delete “{name}”?" t("deleteViewTitle", { name: view.name }); ``` The key types in `global.d.ts` do not prove placeholder correctness: imported JSON values widen to `string`. Run the actual formatter contract whenever you change `messages/*.json` or add a translation call: ```bash npx vitest run lib/i18n-contract.test.ts ``` The test checks catalog parity and missing values at statically identifiable call sites. Runtime-assembled keys still need explicit review. Use `MessageKey<"Namespace">` from `lib/i18n-keys.ts` for key tables, and preserve the namespace when typing a translator prop: `ReturnType>`. Angle-bracket syntax such as `` is a rich-text tag in ICU messages. Use plain notation such as `HMAC-SHA256(body)` for technical text unless a rich-text formatter is intended. ## Writing user-facing text Read [`.claude/skills/no-slop-writing/SKILL.md`](.claude/skills/no-slop-writing/SKILL.md) and its `references/` before writing or changing any text a person reads: UI strings and catalog values, the landing and marketing pages, emails, notifications, error messages, the manual, changelogs and CLI output. This applies when the text is only a small part of a code task. Claude Code loads it as the `no-slop-writing` skill; other agents open the file at that path. Where the skill and this repository disagree, the repository wins: - Update every catalog in the same change, reviewed key by key, rather than leaving the other locales for later translation (see the next section). - The register is set per locale by [docs/localization-and-copy.md](docs/localization-and-copy.md): formal in French, informal in the other languages. - Product names (minddy, Numo, Smart Fill, Smart Assign) keep their casing. ## Interface copy and translations [docs/localization-and-copy.md](docs/localization-and-copy.md) is the contract for every visible string: how to write the English source, the per-locale register and glossary (issue term, statuses, product names), translation rules, and how the manual and its screenshots follow a label change. Read it before changing `messages/*.json`, `lib/command-palette/i18n.ts` or quoted labels in `content/documentation/`. - Review every translated string key by key against `messages/en.json`; never ship unreviewed machine translation. Check meaning, grammar, register and the glossary, and keep the ICU structure exactly. - Use one term per concept per locale (issue → ticket, Ticket, incidencia, ticket, ticket) and never translate product names (minddy, Numo, Smart Assign, Smart Fill). Forge objects keep their own names. - Address the user in the register the guide sets for each locale (formal in French, informal in the other languages), in the UI and the manual alike. - When a label changes, update it in all six catalogs, then every article that quotes it, its figure alt and caption, the diagram SVG fallback, and recapture the screenshots that show it with the scripts listed in the guide. - Run `npx vitest run lib/i18n-contract.test.ts lib/i18n-glossary.test.ts lib/i18n-unused-keys.test.ts` after a catalog change, plus `lib/seo-surface.test.ts` when page metadata changes. - Reuse a `Common` label (cancel, save, delete, copy…) instead of adding a namespace key with the same text, and delete a key in all six catalogs when its last caller goes; `lib/i18n-unused-keys.test.ts` fails on keys no code reads. ## Public routes and sitemap dates `lib/public-routes.ts` defines public route metadata and explicit localized URLs. The proxy rewrites localized URLs to their canonical English implementation and sets the locale header; internal authenticated routes use the locale preference cookie. The table is shared by the sitemap, metadata, proxy, and navigation. When public content materially changes, update only the corresponding `lastModified` date (`YYYY-MM-DD`). Avoid changing dates on every build or for refactors that preserve content. Changelog freshness comes from `CHANGELOG_LAST_MODIFIED` in `lib/changelog.ts`. Adding a public page requires its route implementation, catalogs, and a matching `PUBLIC_ROUTES` entry with every supported locale path. Review neighboring route and metadata tests as well. ## Toolchain and lockfiles Use Node.js 24 (24.22.1 or later, the October 2026 security release) and pnpm **10.28.0**, as pinned by the `packageManager` field of `package.json` and by [the CI workflow](.github/workflows/ci.yml). Keep the two identical: `pnpm/action-setup` fails when they differ, and the field is what makes Dependabot regenerate `pnpm-lock.yaml` with the same pnpm instead of dropping its overrides and patches. ```bash corepack prepare pnpm@10.28.0 --activate pnpm install --frozen-lockfile ``` The repository maintains `pnpm-lock.yaml` for actual installation and `package-lock.json` for npm compatibility. After an intentional dependency change, synchronize both lockfiles using the pinned pnpm version and `npm install --package-lock-only --legacy-peer-deps`. Review the lockfile diff; preserve `pnpm.packageExtensions`, overrides, and patched dependencies. `@lobehub/icons` is imported per logo file, which needs React alone, so `pnpm.packageExtensions` marks its `@lobehub/ui` and `antd` peers optional. `npm run typecheck` uses the repository's TypeScript 7 compiler. The editor may use a different TypeScript version; use repository diagnostics for validation. Source-inspection tests import the `typescript-api` alias for the TypeScript 5 compiler API. Do not substitute an import from `typescript` in these tests. `tsconfig.json` enables incremental compilation; remove `tsconfig.tsbuildinfo` only when a clean diagnostic or timing comparison is required. ## Lint and tests Run the smallest relevant behavioral tests after a code change, along with the repository lint and type check where applicable: ```bash npm run lint npm run typecheck npx vitest run lib/server/assistant npm test npm run test:tooling ``` `npm run lint` runs `oxlint --deny-warnings`. The vendored anti-slop plugin lives in `tools/oxlint/anti-slop/`; `oxlint.config.ts` documents intentionally disabled rules and their historical audit counts. Re-enable a count-based rule only after its violations reach zero. Explain each local suppression with the concrete reason that makes it necessary. For example, copying `headers.keys()` before mutating the collection prevents iteration from skipping entries. A successful type check does not prove lifecycle or runtime behavior. Add a focused regression for a behavior change and read a neighboring test before inventing a new fixture or mocking approach. Avoid tests that merely duplicate the implementation. Vitest discovers tests in `lib`, `components`, and `tools`, using Node by default; DOM suites opt into jsdom per file. Prefer observable outcomes and distinct boundary cases over exact CSS, logo, or source formatting assertions. Structural checks remain useful for security and cross-file contracts that runtime tests do not exercise. Do not require a test file for every module. Use fake timers for polling and debounce tests instead of real elapsed waits; keep real process deadlines where process termination is the behavior under test. | Test concern | Existing example | | --- | --- | | Pure logic | `lib/plan.test.ts` | | Numo streaming loop | `lib/server/assistant/loop-completion.test.ts` | | Numo tool contracts | `lib/server/assistant/tools-contract.test.ts` | | Server boundary with external dependencies mocked | `lib/server/assistant/pull-request-writes.test.ts` | CI additionally runs release tooling, self-hosted contracts, edition-specific build/start checks, dependency auditing, and publication/secret guards. Consult the workflow for the current commands; historical test counts and local timings are not acceptance criteria. Opt-in database tests need an isolated fixture and must be reported separately from the default suite's skipped cases. `test:tooling` discovers deterministic Node tests under `scripts` and `captures/lib`; `*.integration.test.mjs` suites keep their explicit prerequisites and commands. CI starts its jobs in parallel: the publication and secret guards (the pull request's own commits on a pull request, the whole history on `main`), lint/typecheck/contract validation, one build-and-start job per deployable edition, and four Vitest shards. The required `Tests & typecheck` gate fails unless every one of them succeeds. ## Database environments and migrations Preview (`main`, `preview.minddy.app`) and production (`production`, `www.minddy.app`) use two distinct databases; see [database environments](docs/database-environments.md). - Develop and verify against the preview database. The local `.env`, the capture scripts and the test accounts point there, and pull request evidence comes from it. - Apply a new migration to preview only: check that `supabase/.temp/project-ref` is not the production ref, run `supabase db push --dry-run`, confirm that only the new file is listed, then `supabase db push`. State it in the pull request description. - Never apply a migration, a reset or another write to the production database by hand. `Promote production` applies pending migrations at release time. - Keep each migration compatible with the previous release: add before use, remove only once no deployed code reads the old shape. ## Encryption boundaries and operational gates Scoped managed keys, row/object codecs, and bounded backfill workers live in `lib/server/encryption/`. Domain `*-content.ts` modules connect those codecs to application readers and writers. Project, user, and system scopes are distinct; keep decrypted keys and content out of client persistence and diagnostic logs. Changes to encrypted surfaces require both access and schema validation: ```bash npm run check:encrypted-access npm run check:encryption-schema ``` Read [the encryption inventory](docs/security/encryption/README.md) and [the closure matrix](docs/security/encryption/closure-matrix.md) for source/copy coverage, writer fences, authenticated backfill proofs, historical-key requirements, sandbox cleanup, and local-client copies. Keep code CI readiness separate from production data closure. The readiness endpoint explicitly reports `globalReadiness: "not_assessed"`; critical-family and forge-object scans cannot certify the whole application or retained copies. Staging performance, actual Storage-service restore, historical-copy retirement, and provider cleanup remain operational gates. Do not infer completed production encryption from isolated fixtures, and do not enable flags or run a migration or deployment without the applicable authorization. ## Background work and response lifetime Use `afterOrNow` from `lib/server/after-safe.ts` for best-effort work outside a request's critical path, including usage timestamps, session refresh, and opportunistic maintenance: ```ts afterOrNow(async () => { await performMaintenance(); }); ``` Return or await the work inside the callback. Detaching a promise from a request can allow its invocation to finish before the work completes. `afterOrNow` schedules with Next.js `after()` and catches errors; outside a request context it starts the callback immediately. Required writes must still complete in the critical path so a successful response reflects their outcome. ## Git workflow (this repository only) Apply these rules only when the `origin` remote is `mangue-dev/minddy`. Never apply them to another project. ### Before changing code 1. Check the Git state and refresh the remote references (`git fetch origin --prune`). 2. Pick one dedicated branch for the goal. 3. Check whether that branch already exists locally or on `origin`: - If it exists locally, switch to it and continue the work there. - If it exists only on `origin`, create its local tracking branch. - If it does not exist, create it with `npm run work:start -- "short work name"`. 4. Never create a second branch for the same work. 5. Never commit or push directly to `main` or `production`. 6. Always preserve pre-existing uncommitted changes. ### When the work is ready - Run `npm run work:pr -- "Short title" -m "Complete description"` to create the pull request. The `-m` flag may be repeated for several paragraphs. - If the pull request already exists, the command pushes the new commits but keeps its title and description, including manual edits. Use `npm run work:pr` for follow-up commits; supplying another title or description also succeeds without replacing either field. Never retitle or rewrite the description of an existing pull request unless explicitly requested. A deliberate rewrite needs the explicit `--replace` flag of `work:pr` (or a manual `gh pr edit`). - After confirming the pull request is merged, run `npm run work:done`. - Run `npm run deploy` only when the user explicitly asks for a production deployment. ## Commits and pull requests - Always write commit messages and pull request titles and descriptions in idiomatic English. - Commit messages follow the Conventional Commits style used in this repository (`feat:`, `fix:`, `refactor:`, …). - Every pull request carries a complete, real description: what was done, why, how it was verified, and anything a reviewer needs to know. Never reduce a pull request description to a bare `Signed-off-by` trailer or a copy of the commit message. The DCO trailer belongs in the commits, not as the body of the pull request. - For UI changes, include screenshots of the work in the pull request description whenever feasible. Keep these images up to date as the pull request evolves. Prefer light mode by default; include both light and dark mode screenshots when relevant, but covering both modes is optional. The images stay out of the tree; see [Repository hygiene](#repository-hygiene-what-a-pull-request-may-add). - Every commit must carry the DCO sign-off (see below). ## DCO sign-offs - Every commit an agent creates or amends must include a `Signed-off-by` trailer that exactly matches the commit author's name and email. Use `git commit --signoff`, including when amending a commit. - Never use a generic agent identity for the sign-off when the commit has a different author. Before pushing or updating a pull request, verify every non-merge commit in its range has the matching trailer and repair the history when necessary. ## Minddy tools - Treat the Minddy MCP tools as available for issue work in this repository. Discover their callable names by searching for the `mcp__minddy__` prefix; do not conclude that they are unavailable from an exact short-name lookup. - When an issue identifier and project ID are provided, read the issue and its plan, keep plan task states synchronized while working, add a concise outcome comment, and update the issue status when the requested work is complete. # This is NOT the Next.js you know This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.