Files
healthchat-web/CLAUDE.md
T

137 lines
9.6 KiB
Markdown

# 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 is `apps/web/src/` (not a top-level `/src/`).
- `apps/desktop` (`element-desktop`) — the Electron wrapper. Uses Vitest for unit tests and `hak` for 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-version` pins **24** (package.json `engines` allows `>=22.18`)
- pnpm (the repo uses a `pnpm-lock.yaml` and 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 (runs `scripts/pnpm-link.ts` postinstall, see SDK linking below)
- Copy `apps/web/config.sample.json` to `apps/web/config.json` and configure (see `docs/config.md`)
### Development (from `apps/web`)
- `pnpm start` — dev server on http://localhost:8080 (`nx start`)
- `pnpm build` — production build, outputs to `apps/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:
```bash
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; or `cd apps/web && npx jest path/...`)
- `pnpm test:playwright` — Playwright e2e tests
- `pnpm test:playwright:open` — Playwright UI
- `pnpm test:playwright:screenshots` — update Playwright screenshot snapshots
- `pnpm 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, knip
- `pnpm --filter element-web lint` — package-local shorthand for `lint:types && lint:js && lint:style`
- `pnpm --filter element-web lint:types` — TypeScript check only (via Nx)
- `pnpm --filter element-web lint:js` — ESLint check (`lint:js-fix` auto-fixes)
- `pnpm --filter element-web lint:style` — Stylelint on `res/css/**/*.pcss`
- `pnpm lint:knip` (root) — unused deps/exports; `pnpm lint:workflows` — validate GH Actions YAML. Both run as part of `pnpm lint` and gate CI.
### Utilities
- `pnpm i18n` (root or per-package) — regenerate/sort/lint translation strings; chains `i18n:sort`, `i18n:lint`, and `i18n: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 state
- `LeftPanel` — room list, spaces, navigation
- `MainSplit` — central room view with messages and composer
- `RightPanel` — 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 points
- `viewmodels/` — view logic separated from React components, consumed by `structures/` classes
- `async-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 via `FooStore.instance`)
- Dispatch: `dis.dispatch({ action: Action.FOO, ...payload })` or `dis.fire(Action.FOO)` for action-only
### Styling
- PostCSS `.pcss` files, BEM-like naming `mx_ComponentName_element`
- Styles co-located with components (e.g. `_MessageTile.pcss`)
- `res/css/_components.pcss` is **generated** by the `rethemendex` prebuild step — don't hand-edit it
- Theming via CSS custom properties
### Matrix Integration
- `matrix-js-sdk` for 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_system` reads `apps/web/build_config.yaml` and generates `apps/web/src/modules.js` (empty by default in this fork — no committed `build_config.yaml`)
- In production, modules are loaded at runtime from `config.json`'s `modules` array, not via the build-time `modules.js` path
- HealthChat ships the `healthchat-workspace` module (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 in `apps/web/config.json` (homeserver `matrix.healthchat.ch`, custom "HealthChat Light" theme). `config.sample.json` is the dev template — copy it to `config.json`.
- **Widget auto-approval**: widgets served from `api.healthchat.ch` bypass permission dialogs and hide revoke/minimize controls. Logic lives in `apps/web/src/stores/widgets/ElementWidgetDriver.ts`, `apps/web/src/components/views/elements/AppTile.tsx`, and `.../context_menus/WidgetContextMenu.tsx`; base URL comes from the `ch.healthchat.api_base_url` config key.
- **Desktop deep-linking**: scheme is `healthchat://` (`apps/desktop/src/protocol.ts`), with the SSO session passed via the `healthchat-desktop-ssoid` param. Desktop variant config (and its build runbook) lives under `apps/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/matrix` only (not `matrix-js-sdk` or `matrix-js-sdk/src`)
- Use `jest-matrix-react` instead of `@testing-library/react` for component tests
- No `React.forwardRef` — use ref props directly
- Use `UIStore` for window dimensions instead of `window.innerHeight`/`window.innerWidth`
- Use the `Media` helper for MXC URL handling instead of direct `mxcUrlToHttp` calls
### 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`. The image is published to `ghcr.io/novaloop-ag/element-web/healthchat-web` as `:<version>`, `:<sha>`, and `:latest` — where `:latest` moves **only** for a clean `X.Y.Z` tag push, never for `-rc`/`-hotfix` tags or manual dispatch runs. **Deploying is a separate manual step for both halves** and needs no version bump anywhere: the web side is a `gh workflow run deploy.yml` against the ansible repo (whose inventory tracks `:latest`), and the desktop side is `deploy-healthchat-desktop.yaml`, which publishes the update feed to healthchat.ch. Cutting a tag never pushes an update to installed desktop clients — `build-healthchat-desktop.yaml` only attaches a staged `healthchat-desktop-feed-<version>.tar.gz` to the Release. Full runbook: `releasing-healthchat` skill.