mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
* fix(admin): replace ~50 hardcoded German strings with i18n keys (#7735) PR #7716 ("chore: fixed admin design rework") rebuilt admin/src/pages with literal German copy inline — "Update verfügbar", "Aktualisieren", "Keine Pads gefunden", "Hook-Bindings", "de-DE" date formatters, etc. Non-DE users see a French/English/German salad: <Trans i18nKey="…"/> calls resolve correctly via translatewiki, but every literal stays German regardless of browser locale. This change: - Adds 90+ keys to src/locales/en.json under admin.*, admin_login.*, admin_pads.*, admin_plugins.*, admin_plugins_info.*, admin_settings.*, admin_shout.*, and the previously-orphaned update.page.{disabled, unauthorized,error}. - Replaces every hardcoded literal in admin/src/{App,LoginScreen, HomePage,HelpPage,PadPage,SettingsPage,ShoutPage,UpdatePage}.tsx with t() or <Trans>. - Threads i18n.language into PadPage so relativeTime() and toLocale*() honour the user's locale instead of forcing de-DE. Test coverage: - src/tests/backend-new/specs/admin-i18n-source-lint.test.ts (vitest): scans admin/src/pages/*.tsx + App.tsx for a denylist of German literals introduced by #7716, asserts PadPage no longer hardcodes 'de-DE', and pins the set of new en.json keys. - src/tests/frontend-new/admin-spec/admini18n.spec.ts (Playwright): extended to assert rendered English text on every page (Home, Pads, Help, Login) and verify no German leakage on the English path. Non-EN locales pick up translations from translatewiki on its normal cadence; until then i18next falls back to en.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(admin): reuse existing ep_admin_pads:* keys instead of duplicating Pre-rework admin already had: ep_admin_pads:ep_adminpads2_action ("Action") ep_admin_pads:ep_adminpads2_last-edited ("Last edited") ep_admin_pads:ep_adminpads2_no-results ("No results") Initial pass added admin_pads.{col.action, col.last_edited, sort.last_edited, empty_state} duplicating those — drop the duplicates from en.json and point PadPage.tsx at the existing translatewiki-fed keys. Stats/column heads that genuinely didn't exist before (admin_pads.col.{pad,users,revisions}, the filter chips, relative-time, pagination, etc.) stay as new keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(admin): address Qodo finding + restore #7716 regressions Qodo flagged a reliability bug in PadPage on PR #7736: i18n.language flows from user-controlled ?lng= straight into Intl.* formatters, which throw RangeError on malformed tags (e.g. 'en_US', '💥'). Crashing the pads page on a crafted URL. Wrap the locale in a sanitizeLocale() helper that normalises '_' → '-' and validates via Intl.DateTimeFormat.supportedLocalesOf(), falling back to 'en' so dates render in a sane locale rather than the user's browser default fighting page copy. Same audit surfaced four additional regressions from #7716 still on develop, fixed here on-theme: - HomePage dropped <a href="https://npmjs.com/..."> wrappers on both installed and available plugin rows. Restored with .pm-plugin-link. - "Downloads" column / "Most popular" default sort / "Popular" tag were dead UI — src/static/js/pluginfw/installer.ts::search() never populates `downloads`. Removed the column, default sort, and tag; dropped `downloads` from PluginDef + SearchParams.sortBy. - PadPage sort dropdown hardcoded `ascending: e.target.value === 'padName'`, leaving no way to invert direction. Replaced with a paired ↑/↓ button (.pm-sort-dir) for both HomePage and PadPage. - "1 Core" stat hint hardcoded count=1. Derived from installedPlugins.filter(p => p.name === 'ep_etherpad-lite').length. - Deleted orphan modules SearchField.tsx and sorting.ts (no longer imported anywhere after #7716). Tests: - admin-i18n-source-lint.test.ts: +3 assertions (sanitizeLocale pattern, dead-downloads check, orphan-module deletion, sort-dir toggle) → 14 passing. - admini18n.spec.ts: +2 assertions (npmjs link on ep_etherpad-lite row, sort-direction toggle visible). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(agents): add mandatory i18n + a11y guardrails PR #7716 ("admin design rework") shipped ~50 hardcoded German literals, dropped npmjs.com link affordances, removed the sort-direction control on PadPage, and forced `de-DE` into Intl formatters — none of which the AGENTS.MD guide explicitly forbade. Document the rules so the next UI refresh cannot regress these in the same way: - i18n section spells out which slots must be localised (JSX text, placeholders, titles, aria-labels, alts, toasts, options, alerts), which API to use per surface (<Trans>/t() in React, data-l10n-id in the legacy pad UI, never window._ rebound), where keys live (src/locales/en.json — never hand-edit non-EN locales), to reuse existing keys before duplicating, pluralisation via _one/_other, defaultValue is safety not a substitute, and points at the source-lint test that enforces the denylist. - a11y section spells out the lessons surfaced by the audit: icon-only buttons need aria-label AND title (both localised), sort controls must be focusable + reversible, semantic HTML over div soup, external navigation is <a>, "don't drop affordances when restyling" is a hard rule, Playwright specs must assert rendered strings + at least one structural affordance for UI changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
226 lines
12 KiB
Markdown
226 lines
12 KiB
Markdown
# Agent Guide - Etherpad
|
|
|
|
Welcome to the Etherpad project. This guide provides essential context and instructions for AI agents and developers to effectively contribute to the codebase.
|
|
|
|
## Project Overview
|
|
Etherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins.
|
|
|
|
## Technical Stack
|
|
- **Runtime:** Node.js >= 22.12.0
|
|
- **Package Manager:** pnpm (>= 11.0.0)
|
|
- **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML
|
|
- **Backend:** Express.js 5, Socket.io 4
|
|
- **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`)
|
|
- **Database:** ueberdb2 abstraction (supports dirtyDB, MySQL, PostgreSQL, Redis)
|
|
- **Build Tools:** Vite (for `ui` and `admin`), esbuild, tsx
|
|
- **Testing:** Mocha (backend), Playwright (frontend E2E), Vitest (unit)
|
|
- **Auth:** JWT (jose library), OIDC provider
|
|
|
|
## Directory Structure
|
|
- `src/node/` - Backend logic, API handlers, database models, hooks
|
|
- `src/static/` - Core frontend logic (legacy jQuery-based editor)
|
|
- `src/static/js/pluginfw/` - Plugin framework (installer, hook system)
|
|
- `src/tests/` - Test suites (backend, frontend, container)
|
|
- `ui/` - Modern React OIDC login UI (Vite + TypeScript)
|
|
- `admin/` - Modern React admin panel (Vite + TypeScript + Radix UI)
|
|
- `bin/` - CLI utilities, build scripts, plugin management tools
|
|
- `bin/plugins/` - Plugin maintenance scripts (checkPlugin.ts, updateCorePlugins.sh)
|
|
- `doc/` - Documentation (VitePress + Markdown/AsciiDoc)
|
|
- `local_plugins/` - Directory for developing and testing plugins locally
|
|
- `var/` - Runtime data (logs, dirtyDB, etc. - ignored by git)
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
pnpm install # Install all dependencies
|
|
pnpm run build:etherpad # Build admin UI and static assets
|
|
pnpm --filter ep_etherpad-lite run dev # Start dev server (port 9001)
|
|
pnpm --filter ep_etherpad-lite run prod # Start production server
|
|
```
|
|
|
|
## Core Mandates & Conventions
|
|
|
|
### Coding Style
|
|
- **Indentation:** 2 spaces for all files (JS/TS/CSS/HTML). No tabs.
|
|
- **TypeScript:** All new code should be TypeScript. Strict mode is enabled.
|
|
- **Comments:** Provide clear comments for complex logic only.
|
|
- **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files.
|
|
|
|
### Internationalisation (i18n) — Mandatory
|
|
- **Every user-facing string MUST go through i18n.** That means JSX text, `placeholder`, `title`, `aria-label`, `alt`, toast/alert titles, `<option>` labels, error messages, breadcrumbs, empty-state copy — anything a user can see or hear via a screen reader.
|
|
- **React (admin SPA):** use `<Trans i18nKey="…"/>` for JSX text and `t('…')` for attribute values. The `t` comes from `useTranslation()`.
|
|
- **Pad UI (legacy):** use `data-l10n-id="…"` (html10n) — never bind `window._` directly to `html10n.get` and call it (it's unbound; returns `undefined`).
|
|
- **String keys live in `src/locales/en.json`.** Other locales sync from translatewiki on its own cadence — never hand-edit non-EN locale files.
|
|
- **Reuse existing keys before inventing new ones.** Check `src/locales/en.json` and the relevant plugin's `static/locale/en.json` (e.g. `admin/public/ep_admin_pads/en.json`) for an existing match. Duplicating a key like `ep_admin_pads:ep_adminpads2_last-edited` as a fresh `admin_pads.col.last_edited` fragments the translation surface for translatewiki.
|
|
- **Naming:** dot-namespaced, kebab-or-underscore-cased: `admin_plugins.subtitle`, `admin_pads.filter.all`. Group by page/feature.
|
|
- **Pluralisation:** use i18next's `_one`/`_other` suffix forms with `t('key', {count: n})` — never `n > 1 ? 'X items' : '1 item'`.
|
|
- **Locale-aware formatters:** pass a sanitised locale to `Intl.*` / `toLocaleString`. `i18n.language` is influenced by `?lng=` (user-controlled) and a malformed tag throws `RangeError`. Use a `sanitizeLocale()` helper that normalises `_`→`-` and validates via `Intl.DateTimeFormat.supportedLocalesOf()`, falling back to `'en'`.
|
|
- **`defaultValue:` in `t()` is for safety, not a substitute** for adding the key to `en.json`. If you're tempted to inline English with `defaultValue:` and skip the key, you're shipping a future-broken translation.
|
|
- **Hard prohibition:** literal German / French / Spanish / any non-English in JSX. The denylist test at `src/tests/backend-new/specs/admin-i18n-source-lint.test.ts` enforces this for the admin SPA — extend it when adding new admin files or new known-bad words.
|
|
|
|
### Accessibility (a11y) — Mandatory
|
|
- **Icon-only buttons MUST have `aria-label` AND `title`** (both — screen readers prefer `aria-label`; hover users get `title`). Lucide icons inside a button are not text content. Both labels must be `t('…')`-localised.
|
|
- **Sort controls are focusable.** A `<select>` plus a paired direction toggle is fine; a clickable column header is fine; "click invisible part of the row to sort" is not. When restyling, never strip a direction toggle without adding back an equivalent.
|
|
- **Semantic HTML over `<div>` soup.** Use `<nav>`, `<main>`, `<button>`, `<a>` (for navigation), `<table>` for tabular data. If a thing navigates externally, it is `<a target="_blank" rel="noopener noreferrer">`, not a click-handler on a `<span>`.
|
|
- **Don't drop existing affordances when restyling.** A UI refresh that removes `<a href="https://npmjs.com/{plugin}">` links, removes a sort-direction control, or replaces semantic elements with non-focusable divs is a regression even if it looks nicer. Audit the before/after for: focus order, keyboard reachability, external links, aria-labels, alt text.
|
|
- **Tests assert rendered strings + structural affordances.** For UI changes, the Playwright spec must assert at least one rendered translated string (catches broken i18n loading) and one structural affordance you added/preserved (links, toggles, headings).
|
|
|
|
### Development Workflow
|
|
- **Branching:** Work in feature branches. Issue PRs against the `develop` branch. Never PR directly to `master`.
|
|
- **Commits:** Maintain a linear history (no merge commits). Use meaningful messages in the format: `submodule: description`.
|
|
- **Feature Flags:** New features should be placed behind feature flags and disabled by default.
|
|
- **Deprecation:** Never remove features abruptly; deprecate them first with a `WARN` log.
|
|
- **Forks:** For etherpad-lite changes, commit to `johnmclear/etherpad-lite` fork on a new branch, then PR to `ether/etherpad`. For plugins (`ep_*` repos), committing directly is acceptable.
|
|
|
|
### Testing & Validation
|
|
- **Requirement:** Every bug fix MUST include a regression test in the same commit.
|
|
- **Always run tests locally before pushing to CI.**
|
|
- **Linting:** `pnpm run lint`
|
|
- **Type Check:** `pnpm --filter ep_etherpad-lite run ts-check`
|
|
- **Build:** `pnpm run build:etherpad` before production deployment
|
|
|
|
#### Running Backend Tests Locally
|
|
|
|
Backend tests use Mocha with tsx and run against a real server instance (started automatically by the test harness). No separate server process is needed.
|
|
|
|
```bash
|
|
# Run ALL backend tests (includes plugin tests)
|
|
pnpm --filter ep_etherpad-lite run test
|
|
|
|
# Run only utility tests (faster, ~5s timeout)
|
|
pnpm --filter ep_etherpad-lite run test-utils
|
|
|
|
# Run a single test file directly
|
|
cd src && cross-env NODE_ENV=production npx mocha --import=tsx --timeout 120000 tests/backend/specs/YOUR_TEST.ts
|
|
|
|
# Run unit tests (Vitest)
|
|
cd src && npx vitest
|
|
```
|
|
|
|
- Tests run with `NODE_ENV=production`.
|
|
- Default timeout is 120 seconds per test.
|
|
- Test files live in `src/tests/backend/specs/`.
|
|
- Plugin backend tests live in `node_modules/ep_*/static/tests/backend/specs/` (at repo root) and are included automatically by the test script.
|
|
|
|
#### Running Frontend E2E Tests Locally
|
|
|
|
Frontend tests use Playwright. **You must have a running Etherpad server** before launching them — the Playwright config does not auto-start the server.
|
|
|
|
**Before running frontend or admin tests, ensure Playwright browsers are installed.** Check and install if needed:
|
|
|
|
```bash
|
|
# Check which browsers are installed
|
|
cd src && npx playwright install --dry-run
|
|
|
|
# Install all browsers and their system dependencies (must run from src/)
|
|
cd src && npx playwright install
|
|
cd src && sudo npx playwright install-deps
|
|
```
|
|
|
|
If `sudo` is unavailable, install system dependencies for webkit manually:
|
|
```bash
|
|
# Check which system libraries are missing for webkit
|
|
ldd ~/.cache/ms-playwright/webkit-*/minibrowser-wpe/MiniBrowser 2>&1 | grep "not found"
|
|
```
|
|
|
|
If browsers or system dependencies are missing, tests will fail silently or timeout — **always verify browser installation before debugging test failures.**
|
|
|
|
```bash
|
|
# 1. Start the dev server in a separate terminal
|
|
pnpm --filter ep_etherpad-lite run dev
|
|
|
|
# 2. Run frontend E2E tests
|
|
pnpm --filter ep_etherpad-lite run test-ui
|
|
|
|
# 3. Run with interactive Playwright UI (useful for debugging)
|
|
pnpm --filter ep_etherpad-lite run test-ui:ui
|
|
|
|
# Run a single test file
|
|
cd src && cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs/YOUR_TEST.spec.ts
|
|
```
|
|
|
|
- Tests expect the server at `localhost:9001`.
|
|
- Test files live in `src/tests/frontend-new/specs/`.
|
|
- Runs against chromium and firefox by default (webkit is disabled).
|
|
- Playwright config is at `src/playwright.config.ts`.
|
|
|
|
#### Running Admin Panel Tests Locally
|
|
|
|
```bash
|
|
# Requires a running server and Playwright browsers installed (same as frontend tests)
|
|
pnpm --filter ep_etherpad-lite run test-admin
|
|
|
|
# Interactive UI mode
|
|
pnpm --filter ep_etherpad-lite run test-admin:ui
|
|
```
|
|
|
|
- Admin tests run with `--workers 1` (sequential) on chromium and firefox only.
|
|
- Test files live in `src/tests/frontend-new/admin-spec/`.
|
|
|
|
### Backend Test Auth
|
|
Tests use JWT authentication, not API keys. Pattern:
|
|
```typescript
|
|
import * as common from 'ep_etherpad-lite/tests/backend/common';
|
|
|
|
const agent = await common.init(); // Starts server, returns supertest agent
|
|
const token = await common.generateJWTToken();
|
|
agent.get('/api/1/endpoint').set('authorization', token);
|
|
```
|
|
Do not use `APIKEY.txt` — it may not exist in the test environment.
|
|
|
|
## Key Concepts
|
|
|
|
### Easysync
|
|
The real-time synchronization engine. It is complex; refer to `doc/public/easysync/` before modifying core synchronization logic.
|
|
|
|
### Plugin Framework
|
|
Most functionality should be implemented as plugins (`ep_*`). Avoid modifying the core unless absolutely necessary.
|
|
|
|
**Plugin structure:**
|
|
```
|
|
ep_myplugin/
|
|
├── ep.json # Hook declarations (server_hooks, client_hooks)
|
|
├── index.js # Server-side hook implementations
|
|
├── package.json
|
|
├── static/
|
|
│ ├── js/ # Client-side code
|
|
│ ├── css/
|
|
│ └── tests/
|
|
│ ├── backend/specs/ # Backend tests (Mocha)
|
|
│ └── frontend-new/ # Frontend tests (Playwright)
|
|
├── templates/ # EJS templates
|
|
└── locales/ # i18n files
|
|
```
|
|
|
|
**Plugin management:**
|
|
```bash
|
|
pnpm run plugins i ep_plugin_name # Install from npm
|
|
pnpm run plugins i --path ../plugin # Install from local path
|
|
pnpm run plugins rm ep_plugin_name # Remove
|
|
pnpm run plugins ls # List installed
|
|
```
|
|
|
|
**Plugin installation internals:** Plugins are installed to `src/plugin_packages/` via `live-plugin-manager`, which stores them at `src/plugin_packages/.versions/ep_name@version/`. Symlinks are created: `src/node_modules/ep_name` → `src/plugin_packages/ep_name` → `.versions/ep_name@ver/`.
|
|
|
|
### Plugin Repositories
|
|
- **Monorepo:** `ether/ether-plugins` contains 80+ plugins with shared CI/publishing
|
|
- **Standalone repos:** Individual `ether/ep_*` repos still exist for many plugins
|
|
- **Plugin CI templates:** `bin/plugins/lib/` contains workflow templates pushed to standalone plugin repos via `checkPlugin.ts`
|
|
- **Shared pipelines:** `ether/ether-pipelines` contains reusable GitHub Actions workflows for plugin CI
|
|
|
|
### Settings
|
|
Configured via `settings.json`. A template is available at `settings.json.template`. Environment variables can override any setting using `"${ENV_VAR}"` or `"${ENV_VAR:default_value}"`.
|
|
|
|
## Monorepo Structure
|
|
|
|
This project uses pnpm workspaces. The workspaces are:
|
|
- `src/` - Core Etherpad (package: `ep_etherpad-lite`)
|
|
- `bin/` - CLI tools and plugin scripts
|
|
- `ui/` - Login UI
|
|
- `admin/` - Admin panel
|
|
- `doc/` - Documentation
|
|
|
|
Root-level commands operate across all workspaces. Use `pnpm --filter <package>` to target specific workspaces.
|
|
|
|
## AI-Specific Guidance
|
|
AI/Agent contributions are explicitly welcomed by the maintainers, provided they strictly adhere to the guidelines in `CONTRIBUTING.md` and this guide. Always prioritize stability, readability, and compatibility.
|