8.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository layout
This is a pnpm + Nx monorepo (not Yarn — the older single-package layout is gone). Workspaces are defined in pnpm-workspace.yaml:
apps/web(element-web) — the browser client. This is where almost all feature work happens. Source isapps/web/src/(not a top-level/src/).apps/desktop(element-desktop) — the Electron wrapper. Uses Vitest for unit tests andhakfor native modules.packages/module-api(@element-hq/element-web-module-api) — public API surface for runtime modules.packages/shared-components(@element-hq/web-shared-components) — shared React components; home of Storybook.packages/playwright-common,packages/vite-common— shared test/build helpers.
Build orchestration is Nx (nx.json); most apps/web scripts (build, start, test, lint:types) delegate to Nx targets defined in apps/web/project.json, which handle prebuild steps (prebuild:module_system, prebuild:rethemendex) automatically.
Prerequisites
- Node.js:
.node-versionpins 24 (package.jsonenginesallows>=22.18) - pnpm (the repo uses a
pnpm-lock.yamland a catalog for shared dependency versions)
Commands
Run package-scoped commands from inside the package dir (e.g. cd apps/web) or from the root with pnpm --filter element-web <script>. Root-level scripts (pnpm lint, pnpm i18n) fan out across all workspaces via pnpm -r.
Setup
pnpm install— install dependencies (runsscripts/pnpm-link.tspostinstall, see SDK linking below)- Copy
apps/web/config.sample.jsontoapps/web/config.jsonand configure (seedocs/config.md)
Development (from apps/web)
pnpm start— dev server on http://localhost:8080 (nx start)pnpm build— production build, outputs toapps/web/webapp/pnpm dist— distribution tarball (./scripts/package.sh)
Linux note: If builds fail silently or with EMFILE: too many open files, increase inotify limits:
sudo sysctl fs.inotify.max_user_watches=131072
sudo sysctl fs.inotify.max_user_instances=512
Testing (from apps/web)
pnpm test— Jest unit tests (nx test:unit)pnpm test -- path/to/file.test.ts— run a single Jest test file (the--forwards args to Jest; orcd apps/web && npx jest path/...)pnpm test:playwright— Playwright e2e testspnpm test:playwright:open— Playwright UIpnpm test:playwright:screenshots— update Playwright screenshot snapshotspnpm coverage— unit tests with coverage
Note: apps/desktop unit tests use Vitest (pnpm --filter element-desktop test:unit), not Jest.
Code Quality
pnpm lint(root) — full lint across all workspaces: types, prettier, eslint, stylelint, GH workflows, knippnpm --filter element-web lint— package-local shorthand forlint:types && lint:js && lint:stylepnpm --filter element-web lint:types— TypeScript check only (via Nx)pnpm --filter element-web lint:js— ESLint check (lint:js-fixauto-fixes)pnpm --filter element-web lint:style— Stylelint onres/css/**/*.pcsspnpm lint:knip(root) — unused deps/exports;pnpm lint:workflows— validate GH Actions YAML. Both run as part ofpnpm lintand gate CI.
Utilities
pnpm i18n(root or per-package) — regenerate/sort/lint translation strings; chainsi18n:sort,i18n:lint, andi18n:diff(run any one directly when only that step is needed)pnpm --filter @element-hq/web-shared-components storybook— Storybook on port 6007
Architecture
Element Web is a Matrix client built with React + TypeScript, customized here as "HealthChat". The architecture below describes apps/web/src/.
Component Structure
Hierarchical layout:
MatrixChat— root component managing whole-application stateLeftPanel— room list, spaces, navigationMainSplit— central room view with messages and composerRightPanel— member list, room info, contextual panels
Components live in apps/web/src/components/:
structures/— major layout components (these use React classes)views/— reusable UI grouped by function (auth, dialogs, messages, rooms, settings)
Other recurring apps/web/src/ layers worth knowing:
customisations/— Element customization hooks (e.g.Media.ts,WidgetPermissions.ts) — the sanctioned override pointsviewmodels/— view logic separated from React components, consumed bystructures/classesasync-components/— code-split / lazy-loaded mirrors of components (e.g.ErrorView.tsx)
State Management
Flux architecture:
- Centralized dispatcher in
apps/web/src/dispatcher/ - Stores in
apps/web/src/stores/as singletons (access viaFooStore.instance) - Dispatch:
dis.dispatch({ action: Action.FOO, ...payload })ordis.fire(Action.FOO)for action-only
Styling
- PostCSS
.pcssfiles, BEM-like namingmx_ComponentName_element - Styles co-located with components (e.g.
_MessageTile.pcss) res/css/_components.pcssis generated by therethemendexprebuild step — don't hand-edit it- Theming via CSS custom properties
Matrix Integration
matrix-js-sdkfor all Matrix protocol operations- Client accessed via
MatrixClientPeg - Room/event handling through SDK abstractions
Module System
Runtime plugin architecture (apps/web/module_system/, public API in packages/module-api):
prebuild:module_systemreadsapps/web/build_config.yamland generatesapps/web/src/modules.js(empty by default in this fork — no committedbuild_config.yaml)- In production, modules are loaded at runtime from
config.json'smodulesarray, not via the build-timemodules.jspath - HealthChat ships the
healthchat-workspacemodule (see release section); its version is pinned in.healthchat-workspace-version
HealthChat fork specifics
Conventions unique to this fork that span multiple files:
- Branding defaults: hardcoded in
apps/web/src/SdkConfig.ts(brand: "HealthChat", mobile app-store URLs); runtime config inapps/web/config.json(homeservermatrix.healthchat.ch, custom "HealthChat Light" theme).config.sample.jsonis the dev template — copy it toconfig.json. - Widget auto-approval: widgets served from
api.healthchat.chbypass permission dialogs and hide revoke/minimize controls. Logic lives inapps/web/src/stores/widgets/ElementWidgetDriver.ts,apps/web/src/components/views/elements/AppTile.tsx, and.../context_menus/WidgetContextMenu.tsx; base URL comes from thech.healthchat.api_base_urlconfig key. - Desktop deep-linking: scheme is
healthchat://(apps/desktop/src/protocol.ts), with the SSO session passed via thehealthchat-desktop-ssoidparam. Desktop variant config (and its build runbook) lives underapps/desktop/element.io/healthchat/(config.json,build.json,README).
Testing Patterns
- Web unit tests in
apps/web/test/unit-tests/(Jest) - E2E tests in
apps/web/playwright/e2e/(Playwright) - Mock the Matrix client/SDK extensively in tests
- Component stories/visual tests via Storybook in
packages/shared-components
Important Conventions
- TypeScript strict mode — always use explicit types
- Functional components with hooks preferred over class components (except
structures/which use classes) - Translations use
_t()with hierarchical keys - 4-space indentation, 120-character line limit
- Component files: UpperCamelCase; utilities: kebab-case
- Use
Optional<T>instead of truly optional params when callers should explicitly acknowledge missing values - CSS class names must be
mx_-prefixed and match the component name (e.g.mx_RoomTile) - Import matrix-js-sdk via
matrix-js-sdk/src/matrixonly (notmatrix-js-sdkormatrix-js-sdk/src) - Use
jest-matrix-reactinstead of@testing-library/reactfor component tests - No
React.forwardRef— use ref props directly - Use
UIStorefor window dimensions instead ofwindow.innerHeight/window.innerWidth - Use the
Mediahelper for MXC URL handling instead of directmxcUrlToHttpcalls
Linked Development with matrix-js-sdk
pnpm link is not used (it mutates package.json/lockfile). Instead, the postinstall hook runs scripts/pnpm-link.ts, which reads a .link-config file of DEPENDENCY=PATH lines and replaces those node_modules entries with symlinks. To develop against a local SDK, add a line like matrix-js-sdk=/abs/path/to/matrix-js-sdk to .link-config (not committed) and re-run pnpm install.
Releasing HealthChat
Releases are cut by pushing a healthchat-v<semver> tag against release/healthchat, which triggers .github/workflows/build-healthchat.yaml (web) and build-healthchat-desktop.yaml (desktop). Release notes come from HEALTHCHAT_CHANGES.md (kept separate from upstream CHANGELOG.md); the healthchat-workspace plugin ref is pinned in .healthchat-workspace-version. Full runbook: releasing-healthchat skill.