Skip to content
Agentmart
Submit a tool

AGENTS.md · element-hq/element-web

AGENTS.md from element-hq/element-web: 195 lines covering Repository layout, Commands, Running unit tests, Running e2e tests and more.

Template by element-hq

IndexedAGPL-3.0★ 13,612
Write a review

Install

Works with Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code. Read the repository before you give any tool access to your data.

curl -fsSL https://raw.githubusercontent.com/element-hq/element-web/develop/AGENTS.md -o AGENTS.md

Read it and adapt it to your project before use.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

The AGENTS.md file that element-hq/element-web (13,611 stars) keeps for coding agents. It has 195 lines in 10 sections: Repository layout; Commands; Running unit tests; Running e2e tests; Writing UI code: MVVM; Code style; Comments; Tests are required; Commits; Pull requests. Use it as a model for your own project's instructions.

Use cases: prompt-engineering, code-generation

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

What's in it

An AGENTS.md file: instructions coding agents (Codex, Cursor, Copilot, Gemini CLI and others) read before working in a repo.

For
Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code
Length
195 lines
File
AGENTS.md

Sections

  1. Repository layout
  2. Commands
  3. Running unit tests
  4. Running e2e tests
  5. Writing UI code: MVVM
  6. Code style
  7. Comments
  8. Tests are required
  9. Commits
  10. Pull requests

First lines (shown under the repo's AGPL-3.0 license)

# AGENTS.md

Instructions for AI coding agents working in this repository. Humans should read
[CONTRIBUTING.md](./CONTRIBUTING.md) and [code_style.md](./code_style.md) instead — this file summarises those and
adds the things that are easy to get wrong here.

## Repository layout

A pnpm + nx monorepo (`element-web-monorepo`). Workspaces are `apps/*`, `packages/*`, `modules` and `modules/*`.

| Path                                                                                                  | Contents                                                                                                      |
| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `apps/web`                                                                                            | The Element Web app. Also hosts view models (`src/viewmodels/`) and the Playwright e2e suite (`playwright/`). |
| `apps/desktop`                                                                                        | Element Desktop.                                                                                              |
| `packages/shared-components`                                                                          | Published UI component library, `@element-hq/web-shared-components`. New views go here.                       |
| `packages/module-api`, `packages/shared-utils`, `packages/shared-types`, `packages/playwright-common` | Published support packages.                                                                                   |
| `modules/*`                                                                                           | Optional runtime modules (banner, widget toggles, …).                                                         |
| `docs/`                                                                                               | VitePress docs site.                                                                                          |

There is **no** top-level `playwright/` directory; `docs/playwright.md` uses paths relative to `apps/web`.

## Commands

Run from the repo root unless stated otherwise.

| Task                                                   | Command                                           |
| ------------------------------------------------------ | ------------------------------------------------- |
| Full lint (types, format, js, styles, workflows, knip) | `pnpm lint`                                       |
| Format                                                 | `pnpm lint:fmt:fix`                               |
| Lint JS/TS                                             | `pnpm lint:js:fix`                                |
| Typecheck                                              | `pnpm -r --workspace-concurrency=1 lint:types`    |
| All unit tests                                         | `pnpm test:unit`                                  |
| Regenerate i18n strings                                | `pnpm i18n`                                       |
| Start the app                                          | `cd apps/web && pnpm start`                       |
| Storybook                                              | `cd packages/shared-components && pnpm storybook` |

Formatting is **oxfmt** and linting is **oxlint**. This repo does not use prettier or eslint — running prettier here
reformats files against the project style and reports failures that do not exist.

Run `pnpm i18n` after adding or changing any translated string.

## Running unit tests

Both jest and vitest are in use; a migration to vitest is in progress. **The file path decides the runner.**

| Test file                                      | Runner           | Command                                                     |
| ---------------------------------------------- | ---------------- | ----------------------------------------------------------- |
| `apps/web/test/**/*-test.[tj]s?(x)`            | jest             | `cd apps/web && pnpm jest <path>`                           |
| `apps/web/src/**/*.test.{ts,tsx}`              | vitest           | `pnpm vitest run <path>`                                    |
| `apps/web/src/**/*.test.browser.{ts,tsx}`      | vitest (browser) | `pnpm vitest run --project web-browser <path>`              |
| `packages/shared-components/src/**/*.test.tsx` | vitest           | `cd packages/shared-components && pnpm test:unit -- <path>` |

Notes:

- Write new tests as co-located vitest `*.test.ts(x)` next to the source file. The `apps/web/test/` jest tree is
  legacy; extend it only when the file you are changing already lives there.
- Vitest runs with `globals: false`, so import `describe`, `it`, `expect` and friends explicitly.
- `packages/shared-components` is excluded from the root vitest config and must be run from its own directory.
- "No test files found" means you picked the wrong runner or path. It is not a pass.
- Prefer extending an existing test over adding a new file when covering a close variant of existing behaviour.

## Running e2e tests

Specs live in `apps/web/playwright/e2e`. See [docs/playwright.md](./docs/playwright.md) for the full guide.

```sh
cd apps/web && pnpm test:playwright -- playwright/e2e/<spec>.spec.ts --project=Chrome
```

- Requires a container runtime (Docker/Podman/Colima) for the homeserver testcontainers.
- On macOS, add `--ignore-snapshots`. Screenshot baselines are only committed for Linux, so comparisons fail locally.
- Never run `pnpm test:playwright:screenshots` locally to "fix" a screenshot diff — it writes host-rendered baselines.
  Screenshots are updated in the Docker environment, which CI matches.
- The config uses `reuseExistingServer: true`. Check `lsof -ti:8080` first; a stale dev server silently serves the
  tests. Port-bind errors are an infrastructure problem, not a test failure.
- Locate elements by role, label and accessible name, not CSS classes.
- Tag any test using `toMatchScreenshot` with `@screenshot`.

### Reviewing updated screenshots

Updated baselines are part of the diff and must be reviewed, not accepted blindly. Before committing any changed
`.png` under `apps/web/playwright/snapshots/` or `packages/shared-components/__vis__/`:

- Look at the image. Confirm every visible difference is one your change was meant to produce.
- Treat anything else as a regression until proven otherwise: shifted or clipped layout, changed spacing or font,
  missing or duplicated elements, a wrong theme, an untranslated or placeholder string, a loading or error state
  captured instead of the real content.
- Unrelated files changing baselines is a signal in itself — if a screenshot moved for a component you did not
  touch, find out why rather than committing it.
- Never regenerate baselines to make a failing test pass. A diff you cannot explain is a bug in the change.

## Writing UI code: MVVM

New UI follows MVVM. Full details in [docs/MVVM.md](./docs/MVVM.md); the shape for a feature `Foo`:

**View** — `packages/shared-components/src/<domain>/FooView/` containing `FooView.tsx`, `FooView.module.css`,
`FooView.test.tsx`, `FooView.stories.tsx` and `index.ts`. The view declares the contract:

```tsx
export interface FooViewSnapshot { title: string }
interface FooViewActions { setTitle: (title: string) => void }
export type FooViewModel = ViewModel<FooViewSnapshot, FooViewActions>;

export function FooView({ vm }: { vm: FooViewModel }): JSX.Element {
    const { title } = useViewModel(vm);
    ...
}
```

Views are dumb: they read the snapshot and call actions, nothing else. Develop them in Storybook.

**ViewModel** — `apps/web/src/viewmodels/<domain>/FooViewModel.ts`, a class extending `BaseViewModel` that implements
the interface from the view. `apps/web/src/components/viewmodels/` is the deprecated v1 location; do not add there.

Rules that are easy to miss:

- Define actions as arrow-function class properties (`public doThing = (): void => {}`) so `this` survives being
  passed as a callback.
- Use `this.snapshot.merge({ field })` for partial updates. `merge` already skips emitting when nothing changed, so
  do not add equality guards around it, and do not recompute the whole snapshot for one field.
- Track listeners and sub-view-models with `this.disposables.trackListener(...)` / `this.disposables.track(...)`.
- A non-MVVM function component owning a view model should create it with `useCreateAutoDisposedViewModel`; a class
  component creates it in `componentDidMount` and disposes it in `componentWillUnmount`.

Reference implementation: `packages/shared-components/src/room-list/RoomListSearchView/` with
`apps/web/src/viewmodels/room-list/RoomListSearchViewModel.ts`.

## Code style

Read [code_style.md](./code_style.md). The points most often missed:

- **Every new file needs a copyright header**, enforced by oxlint (`element-call/copyright-header`). Lint fails
  without it. Substitute the actual current year — check it rather than copying a year from another file — and leave
  headers on existing files alone:

    ```
    /*
    Copyright <current year> <copyright holder>

    SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
    Please see LICENSE in the repository root for full details.
    */
    ```

    The holder is `Element Creations Ltd.` **only for contributions made as part of Element**. An external
    contributor puts their own name or their company's there instead. Note that `oxlint --fix` inserts the Element
    line unconditionally, so external contributors should write the header by hand and check what the autofix added.

- TypeScript only, named exports only — avoid `export default`.
- 4-space indent, 120-column limit, double quotes, semicolons.
- Declare member visibility (`public`/`private`/`protected`) on class members.
- Avoid `any`; if unavoidable, comment why.
- Roughly one interface, class or enum per file, named after the file.
- Never mix cosmetic and functional changes.
- **Styles:** `apps/web` uses PostCSS (`res/css/**/_Component.pcss`, `mx_`-prefixed classes).
  `packages/shared-components` uses CSS modules (`Component.module.css`, semantic camelCase class names, no `mx_`
  prefix) imported as `styles`. Use Compound design tokens (`var(--cpd-color-…)`, `var(--cpd-space-…)`) for all values.
- Prefer Compound typography components over raw text elements, and `Flex`/`Box` from shared-components over raw
  flexbox markup.

Signals

Stars
13,612
Forks
2,802
Open issues
3,584
Last commit (this folder)
Oct 2, 2026 (26 days ago)
Last release
Oct 16, 2026 (12 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
AGPL-3.0
First seen
Oct 24, 2026
Last checked
Oct 24, 2026
Cite this page

AGENTS.md · element-hq/element-web. Agent Mart. https://agentmart.dev/templates/element-hq-element-web-agents-md (last updated 2026-10-24)