{"owner":"ether","repo":"etherpad","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":6981,"categories":["plugin-manifest","anthropic-skill","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["bin/plugins/README.md","bin/plugins/lib/README.md","bin/plugins/lib/CONTRIBUTING.md","AGENTS.MD"],"skills":{"bin/plugins/README.md":"The files in this folder are for Plugin developers.\n\n# Get suggestions to improve your Plugin\n\nThis code will check your plugin for known usual issues and some suggestions for\nimprovements. No changes will be made to your project.\n\n```\nnode src/bin/plugins/checkPlugin.js $PLUGIN_NAME$\n```\n\n# Basic Example:\n\n```\nnode src/bin/plugins/checkPlugin.js ep_webrtc\n```\n\n## Autofixing - will autofix any issues it can\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autofix\n```\n\n## Autocommitting - fix issues and commit\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autocommit\n```\n\n## Autopush - fix issues, commit, push, and publish (highly dangerous)\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autopush\n```\n\n# All the plugins\n\nReplace johnmclear with your github username\n\n```\n# Clones\ncd node_modules\nGHUSER=johnmclear; curl \"https://api.github.com/users/$GHUSER/repos?per_page=1000\" | grep -o 'git@[^\"]*' | grep /ep_ | xargs -L1 git clone\ncd ..\n\n# autofixes and autocommits /pushes & npm publishes\nfor dir in node_modules/ep_*; do\n  dir=${dir#node_modules/}\n  [ \"$dir\" != ep_etherpad-lite ] || continue\n  node src/bin/plugins/checkPlugin.js \"$dir\" autocommit\ndone\n```\n\n# Automating update of ether organization plugins\n\n```\ngetCorePlugins.sh\nupdateCorePlugins.sh\n```\n","bin/plugins/lib/README.md":"# [plugin_name]\n\nTODO: Describe the plugin.\n\n## Example animated gif of usage if appropriate\n\n![screenshot](https://user-images.githubusercontent.com/220864/99979953-97841d80-2d9f-11eb-9782-5f65817c58f4.PNG)\n\n## Installation\n\nFrom the Etherpad working directory, run:\n\n```shell\nnpm install --no-save --legacy-peer-deps [plugin_name]\n```\n\nOr, install from Etherpad's `/admin/plugins` page.\n\n## Configuration\n\nTODO\n\n## Testing\n\nTo run the backend tests, run the following from the Etherpad working directory:\n\n```shell\n(cd src && pnpm test)\n```\n\nTo run the frontend tests, visit: http://localhost:9001/tests/frontend/\n\n## Copyright and License\n\nCopyright © [yyyy] [name of copyright owner]\nand the [plugin_name] authors and contributors\n\nLicensed under the [Apache License, Version 2.0](LICENSE) (the \"License\"); you\nmay not use this file except in compliance with the License. You may obtain a\ncopy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n","bin/plugins/lib/CONTRIBUTING.md":"# Contributor Guidelines\n(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad#get-in-touch))\n\n## Pull requests\n\n* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary\n* PRs should be issued against the **develop** branch: we never pull directly into **master**\n* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing\n* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)\n* contain meaningful and detailed **commit messages** in the form:\n  ```\n  submodule: description\n\n  longer description of the change you have made, eventually mentioning the\n  number of the issue that is being fixed, in the form: Fixes #someIssueNumber\n  ```\n* if the PR is a **bug fix**:\n  * the first commit in the series must be a test that shows the failure\n  * subsequent commits will fix the bug and make the test pass\n  * the final commit message should include the text `Fixes: #xxx` to link it to its bug report\n* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**\n* if you want to remove a feature, **deprecate it instead**:\n  * write an issue with your deprecation plan\n  * output a `WARN` in the log informing that the feature is going to be removed\n  * remove the feature in the next version\n* if you want to add a new feature, put it under a **feature flag**:\n  * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early\n  * expose a mechanism for enabling/disabling the feature\n  * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration\n* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it\n\n## How to write a bug report\n\n* Please be polite, we all are humans and problems can occur.\n* Please add as much information as possible, for example\n  * client os(s) and version(s)\n    * browser(s) and version(s), is the problem reproducible on different clients\n    * special environments like firewalls or antivirus\n  * host os and version\n    * npm and nodejs version\n    * Logfiles if available\n  * steps to reproduce\n  * what you expected to happen\n  * what actually happened\n* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.\n\nIf you send logfiles, please set the loglevel switch DEBUG in your settings.json file:\n\n```\n/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */\n  \"loglevel\": \"DEBUG\",\n```\n\nThe logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.\n\n## General goals of Etherpad\nTo make sure everybody is going in the same direction:\n* easy to install for admins and easy to use for people\n* easy to integrate into other apps, but also usable as standalone\n* lightweight and scalable\n* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.\nAlso, keep it maintainable. We don't wanna end up as the monster Etherpad was!\n\n## How to work with git?\n* Don't work in your master branch.\n* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)\n* Don't use the online edit function of github (this only creates ugly and not working commits!)\n* Try to make clean commits that are easy readable (including descriptive commit messages!)\n* Test before you push. Sounds easy, it isn't!\n* Don't check in stuff that gets generated during build or runtime\n* Make small pull requests that are easy to review but make sure they do add value by themselves / individually\n\n## Coding style\n* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)\n* Never ever use tabs\n* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces\n* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!\n* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)\n* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!\n* If you do make changes, document them! (see below)\n* Use protocol independent urls \"//\"\n\n## Branching model / git workflow\nsee git flow http://nvie.com/posts/a-successful-git-branching-model/\n\n### `master` branch\n* the stable\n* This is the branch everyone should use for production stuff\n\n### `develop`branch\n* everything that is READY to go into master at some point in time\n* This stuff is tested and ready to go out\n\n### release branches\n* stuff that should go into master very soon\n* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)\n* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.\n\n### hotfix branches\n* fixes for bugs in master\n\n### feature branches (in your own repos)\n* these are the branches where you develop your features in\n* If it's ready to go out, it will be merged into develop\n\nOver the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop\n\n## Documentation\nThe docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.\n\nDocumentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.\n\nYou can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.\n\n## Testing\n\nFront-end tests are found in the `src/tests/frontend/` folder in the repository.\nRun them by pointing your browser to `<yourdomainhere>/tests/frontend`.\n\nBack-end tests can be run from the `src` directory, via `npm test`.\n\n## Things you can help with\nEtherpad is much more than software.  So if you aren't a developer then worry not, there is still a LOT you can do!  A big part of what we do is community engagement.  You can help in the following ways\n * Triage bugs (applying labels) and confirming their existence\n * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required\n * Notifying large site admins of new releases\n * Writing Changelogs for releases\n * Creating Windows packages\n * Creating releases\n * Bumping dependencies periodically and checking they don't break anything\n * Write proposals for grants\n * Co-Author and Publish CVEs\n * Work with SFC to maintain legal side of project\n * Maintain TODO page - https://github.com/ether/etherpad/wiki/TODO#IMPORTANT_TODOS\n\n","AGENTS.MD":"# Agent Guide - Etherpad\n\nWelcome to the Etherpad project. This guide provides essential context and instructions for AI agents and developers to effectively contribute to the codebase.\n\n## Project Overview\nEtherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins.\n\n## Technical Stack\n- **Runtime:** Node.js >= 22.12.0\n- **Package Manager:** pnpm (>= 11.0.0)\n- **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML\n- **Backend:** Express.js 5, Socket.io 4\n- **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`)\n- **Database:** ueberdb2 abstraction (supports dirtyDB, MySQL, PostgreSQL, Redis)\n- **Build Tools:** Vite (for `ui` and `admin`), esbuild, tsx\n- **Testing:** Mocha (backend), Playwright (frontend E2E), Vitest (unit)\n- **Auth:** JWT (jose library), OIDC provider\n\n## Directory Structure\n- `src/node/` - Backend logic, API handlers, database models, hooks\n- `src/static/` - Core frontend logic (legacy jQuery-based editor)\n- `src/static/js/pluginfw/` - Plugin framework (installer, hook system)\n- `src/tests/` - Test suites (backend, frontend, container)\n- `ui/` - Modern React OIDC login UI (Vite + TypeScript)\n- `admin/` - Modern React admin panel (Vite + TypeScript + Radix UI)\n- `bin/` - CLI utilities, build scripts, plugin management tools\n- `bin/plugins/` - Plugin maintenance scripts (checkPlugin.ts, updateCorePlugins.sh)\n- `doc/` - Documentation (VitePress + Markdown/AsciiDoc)\n- `local_plugins/` - Directory for developing and testing plugins locally\n- `var/` - Runtime data (logs, dirtyDB, etc. - ignored by git)\n\n## Quick Start\n\n```bash\npnpm install                                    # Install all dependencies\npnpm run build:etherpad                         # Build admin UI and static assets\npnpm --filter ep_etherpad-lite run dev          # Start dev server (port 9001)\npnpm --filter ep_etherpad-lite run prod         # Start production server\n```\n\n## Core Mandates & Conventions\n\n### Coding Style\n- **Indentation:** 2 spaces for all files (JS/TS/CSS/HTML). No tabs.\n- **TypeScript:** All new code should be TypeScript. Strict mode is enabled.\n- **Comments:** Provide clear comments for complex logic only.\n- **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files.\n\n### Internationalisation (i18n) — Mandatory\n- **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.\n- **React (admin SPA):** use `<Trans i18nKey=\"…\"/>` for JSX text and `t('…')` for attribute values. The `t` comes from `useTranslation()`.\n- **Pad UI (legacy):** use `data-l10n-id=\"…\"` (html10n) — never bind `window._` directly to `html10n.get` and call it (it's unbound; returns `undefined`).\n- **String keys live in `src/locales/en.json`.** Other locales sync from translatewiki on its own cadence — never hand-edit non-EN locale files.\n- **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.\n- **Naming:** dot-namespaced, kebab-or-underscore-cased: `admin_plugins.subtitle`, `admin_pads.filter.all`. Group by page/feature.\n- **Pluralisation:** use i18next's `_one`/`_other` suffix forms with `t('key', {count: n})` — never `n > 1 ? 'X items' : '1 item'`.\n- **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'`.\n- **`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.\n- **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.\n\n### Accessibility (a11y) — Mandatory\n- **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.\n- **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.\n- **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>`.\n- **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.\n- **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).\n\n### UI changes need a human click-through — Mandatory\nAutomated tests are not enough for UI. If an AI tool wrote or changed anything a user can see or interact with (a button, popup, form, setting, layout, wording, styling), a human must run it and use it before the PR is opened.\n\n- **Agents: stop and ask before opening the PR.** Start a local instance with the change, tell the human the URL and exactly what to click, and wait for them to say they tried it and are happy with how it looks and behaves. Do not open the PR until they do.\n- **Never tick the click-through checkbox in the PR template yourself.** Only the human who did the click-through ticks it. If they haven't, leave it unticked and say so in the PR description.\n- **Fix what they find first.** Layout, placement, wording and \"this should be one click\" feedback is the point of the click-through. Address it before the PR, not in review.\n- **A Playwright spec is still required** (see Accessibility above). It covers regressions, not whether the UI is any good.\n\n### Development Workflow\n- **Branching:** Work in feature branches. Issue PRs against the `develop` branch. Never PR directly to `master`.\n- **Commits:** Maintain a linear history (no merge commits). Use meaningful messages in the format: `submodule: description`.\n- **Feature Flags:** New features should be placed behind feature flags and disabled by default.\n- **Deprecation:** Never remove features abruptly; deprecate them first with a `WARN` log.\n- **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.\n\n### Testing & Validation\n- **Requirement:** Every bug fix MUST include a regression test in the same commit.\n- **Always run tests locally before pushing to CI.**\n- **Linting:** `pnpm run lint`\n- **Type Check:** `pnpm --filter ep_etherpad-lite run ts-check`\n- **Build:** `pnpm run build:etherpad` before production deployment\n\n#### Running Backend Tests Locally\n\nBackend tests use Mocha with tsx and run against a real server instance (started automatically by the test harness). No separate server process is needed.\n\n```bash\n# Run ALL backend tests (includes plugin tests)\npnpm --filter ep_etherpad-lite run test\n\n# Run only utility tests (faster, ~5s timeout)\npnpm --filter ep_etherpad-lite run test-utils\n\n# Run a single test file directly\ncd src && cross-env NODE_ENV=production npx mocha --import=tsx --timeout 120000 tests/backend/specs/YOUR_TEST.ts\n\n# Run unit tests (Vitest)\ncd src && npx vitest\n```\n\n- Tests run with `NODE_ENV=production`.\n- Default timeout is 120 seconds per test.\n- Test files live in `src/tests/backend/specs/`.\n- Plugin backend tests live in `node_modules/ep_*/static/tests/backend/specs/` (at repo root) and are included automatically by the test script.\n\n#### Running Frontend E2E Tests Locally\n\nFrontend tests use Playwright. **You must have a running Etherpad server** before launching them — the Playwright config does not auto-start the server.\n\n**Before running frontend or admin tests, ensure Playwright browsers are installed.** Check and install if needed:\n\n```bash\n# Check which browsers are installed\ncd src && npx playwright install --dry-run\n\n# Install all browsers and their system dependencies (must run from src/)\ncd src && npx playwright install\ncd src && sudo npx playwright install-deps\n```\n\nIf `sudo` is unavailable, install system dependencies for webkit manually:\n```bash\n# Check which system libraries are missing for webkit\nldd ~/.cache/ms-playwright/webkit-*/minibrowser-wpe/MiniBrowser 2>&1 | grep \"not found\"\n```\n\nIf browsers or system dependencies are missing, tests will fail silently or timeout — **always verify browser installation before debugging test failures.**\n\n```bash\n# 1. Start the dev server in a separate terminal\npnpm --filter ep_etherpad-lite run dev\n\n# 2. Run frontend E2E tests\npnpm --filter ep_etherpad-lite run test-ui\n\n# 3. Run with interactive Playwright UI (useful for debugging)\npnpm --filter ep_etherpad-lite run test-ui:ui\n\n# Run a single test file\ncd src && cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs/YOUR_TEST.spec.ts\n```\n\n- Tests expect the server at `localhost:9001`.\n- Test files live in `src/tests/frontend-new/specs/`.\n- Runs against chromium and firefox by default (webkit is disabled).\n- Playwright config is at `src/playwright.config.ts`.\n\n#### Running Admin Panel Tests Locally\n\n```bash\n# Requires a running server and Playwright browsers installed (same as frontend tests)\npnpm --filter ep_etherpad-lite run test-admin\n\n# Interactive UI mode\npnpm --filter ep_etherpad-lite run test-admin:ui\n```\n\n- Admin tests run with `--workers 1` (sequential) on chromium and firefox only.\n- Test files live in `src/tests/frontend-new/admin-spec/`.\n\n### Backend Test Auth\nTests use JWT authentication, not API keys. Pattern:\n```typescript\nimport * as common from 'ep_etherpad-lite/tests/backend/common';\n\nconst agent = await common.init();  // Starts server, returns supertest agent\nconst token = await common.generateJWTToken();\nagent.get('/api/1/endpoint').set('authorization', token);\n```\nDo not use `APIKEY.txt` — it may not exist in the test environment.\n\n## Key Concepts\n\n### Easysync\nThe real-time synchronization engine. It is complex; refer to `doc/public/easysync/` before modifying core synchronization logic.\n\n### Plugin Framework\nMost functionality should be implemented as plugins (`ep_*`). Avoid modifying the core unless absolutely necessary.\n\n**Plugin structure:**\n```\nep_myplugin/\n├── ep.json              # Hook declarations (server_hooks, client_hooks)\n├── index.js             # Server-side hook implementations\n├── package.json\n├── static/\n│   ├── js/              # Client-side code\n│   ├── css/\n│   └── tests/\n│       ├── backend/specs/    # Backend tests (Mocha)\n│       └── frontend-new/     # Frontend tests (Playwright)\n├── templates/           # EJS templates\n└── locales/             # i18n files\n```\n\n**Plugin management:**\n```bash\npnpm run plugins i ep_plugin_name       # Install from npm\npnpm run plugins i --path ../plugin     # Install from local path\npnpm run plugins rm ep_plugin_name      # Remove\npnpm run plugins ls                     # List installed\n```\n\n**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/`.\n\n### Plugin Repositories\n- **Monorepo:** `ether/ether-plugins` contains 80+ plugins with shared CI/publishing\n- **Standalone repos:** Individual `ether/ep_*` repos still exist for many plugins\n- **Plugin CI templates:** `bin/plugins/lib/` contains workflow templates pushed to standalone plugin repos via `checkPlugin.ts`\n- **Shared pipelines:** `ether/ether-pipelines` contains reusable GitHub Actions workflows for plugin CI\n\n### Settings\nConfigured 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}\"`.\n\n## Releasing\n\nReleases are driven almost entirely by GitHub Actions. A maintainer dispatches **one** workflow; the version bump, tagging, GitHub Release, Docker images, and snap all cascade off the pushed tag. The npm publish is a separate manual dispatch.\n\n### Prerequisites (check these before dispatching)\n- **A `# X.Y.Z` changelog section for the _target_ version must already be at the top of `CHANGELOG.md`.** `bin/release.ts` aborts with `No changelog record for X.Y.Z, please create changelog record` if it's missing. Write the section before dispatching.\n- **All four `package.json` files must agree on the current version:** root `package.json`, `src/package.json`, `admin/package.json`, `bin/package.json`. `release.ts` reads the *current* version from **`src/package.json`** and computes the next with `semver.inc(current, type)`. If the files are out of sync (e.g. one was hand-edited), the computed target is wrong and the changelog guard fails. *(This exact desync — `src`/`bin` left at 3.3.0 while root/admin were 3.2.0 — blocked the 3.3.0 release in June 2026.)*\n\n### Cutting a release\n1. **Actions → \"Release etherpad\"** → Run workflow (`workflow_dispatch`), choose `patch` / `minor` / `major`. Cadence is monthly **minors** (3.0.0 → 3.1.0 → 3.2.0 → …); use `major` only for breaking changes.\n2. The **Prepare release** step runs `bin/release.ts`, which:\n   - sanity-checks the tree (clean working dir, on `develop`, `develop`/`master` upstreams in sync, `../ether.github.com` cloned & clean on `master`);\n   - bumps the version in all four `package.json` files;\n   - commits `bump version`, merges `develop` → `master`, creates both `X.Y.Z` **and** `vX.Y.Z` tags, merges `master` back to `develop`;\n   - builds and stages the versioned docs into the website repo (see **Documentation** below).\n3. The **Push after release** step (`bin/push-after-release.sh`) pushes `master`, `develop`, the tag, `--tags`, and the `ether.github.com` docs commit.\n4. The pushed **`vX.Y.Z` tag auto-triggers** three workflows:\n   - `handleRelease.yml` → builds Etherpad, extracts the matching changelog section via `generateChangelog` (`bin/generateReleaseNotes.ts`), and publishes the **GitHub Release** (`make_latest: true`);\n   - `docker.yml` → builds & pushes the Docker images;\n   - `snap-publish.yml` → publishes the snap.\n5. **npm publish — PARKED, not part of the release.** `releaseEtherpad.yaml` publishes the core as `ep_etherpad` to npm, but that package is **not load-bearing**: it has 0 dependents, nothing depends on it (plugins import `ep_etherpad-lite` from the *local* core install; plugin CI clones the repo), and Etherpad is run via clone/Docker/zip/snap — never `npm install`. The publish currently fails with `E404` because `ep_etherpad` has no OIDC trusted publisher configured on npmjs.com, which is why npm sits at 2.5.0 while 3.x shipped fine without it. The workflow is gated behind a `confirm: true` input so it can't run by accident. **Skip it for a normal release.** To revive it, the npm owner of `ep_etherpad` (`samtv12345`) configures a trusted publisher (npmjs.com → ep_etherpad → Settings → Trusted Publisher → repo `ether/etherpad`, workflow `releaseEtherpad.yml`); otherwise the workflow can be removed. Decision pending.\n\n### Documentation\n\nTwo distinct things, both important:\n\n**1. Per-PR doc updates — your responsibility in every behaviour-change PR.**\nThe `doc/` workspace holds the user/admin/API docs (Markdown + AsciiDoc). Whenever a PR changes API responses, CLI flags, settings keys, hooks, or error formats, update the relevant files in `doc/` **in the same PR** — don't defer it to release time. The HTTP API reference is `doc/api/http_api.{md,adoc}` (keep both in sync). Preview locally with `pnpm run makeDocs`.\n\n**2. Release-time versioned docs publishing — automated, no manual step.**\nDuring a release, `release.ts` runs `pnpm run makeDocs` (→ `bin/make_docs.ts`) to render `doc/` into `out/doc/`, then copies it into the sibling website repo at `../ether.github.com/public/doc/vX.Y.Z`, bumps that repo's version, and commits `X.Y.Z docs`; `push-after-release.sh` pushes it, publishing the versioned docs on etherpad.org. This requires `ether.github.com` checked out as a **sibling directory** — the **Release etherpad** workflow checks it out automatically. If you ever run `release.ts` by hand, clone it first: `cd .. && git clone git@github.com:ether/ether.github.com.git`.\n\n## Monorepo Structure\n\nThis project uses pnpm workspaces. The workspaces are:\n- `src/` - Core Etherpad (package: `ep_etherpad-lite`)\n- `bin/` - CLI tools and plugin scripts\n- `ui/` - Login UI\n- `admin/` - Admin panel\n- `doc/` - Documentation\n\nRoot-level commands operate across all workspaces. Use `pnpm --filter <package>` to target specific workspaces.\n\n## AI-Specific Guidance\nAI/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.\n"},"files":{"bin/plugins/README.md":"The files in this folder are for Plugin developers.\n\n# Get suggestions to improve your Plugin\n\nThis code will check your plugin for known usual issues and some suggestions for\nimprovements. No changes will be made to your project.\n\n```\nnode src/bin/plugins/checkPlugin.js $PLUGIN_NAME$\n```\n\n# Basic Example:\n\n```\nnode src/bin/plugins/checkPlugin.js ep_webrtc\n```\n\n## Autofixing - will autofix any issues it can\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autofix\n```\n\n## Autocommitting - fix issues and commit\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autocommit\n```\n\n## Autopush - fix issues, commit, push, and publish (highly dangerous)\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autopush\n```\n\n# All the plugins\n\nReplace johnmclear with your github username\n\n```\n# Clones\ncd node_modules\nGHUSER=johnmclear; curl \"https://api.github.com/users/$GHUSER/repos?per_page=1000\" | grep -o 'git@[^\"]*' | grep /ep_ | xargs -L1 git clone\ncd ..\n\n# autofixes and autocommits /pushes & npm publishes\nfor dir in node_modules/ep_*; do\n  dir=${dir#node_modules/}\n  [ \"$dir\" != ep_etherpad-lite ] || continue\n  node src/bin/plugins/checkPlugin.js \"$dir\" autocommit\ndone\n```\n\n# Automating update of ether organization plugins\n\n```\ngetCorePlugins.sh\nupdateCorePlugins.sh\n```\n","bin/plugins/lib/README.md":"# [plugin_name]\n\nTODO: Describe the plugin.\n\n## Example animated gif of usage if appropriate\n\n![screenshot](https://user-images.githubusercontent.com/220864/99979953-97841d80-2d9f-11eb-9782-5f65817c58f4.PNG)\n\n## Installation\n\nFrom the Etherpad working directory, run:\n\n```shell\nnpm install --no-save --legacy-peer-deps [plugin_name]\n```\n\nOr, install from Etherpad's `/admin/plugins` page.\n\n## Configuration\n\nTODO\n\n## Testing\n\nTo run the backend tests, run the following from the Etherpad working directory:\n\n```shell\n(cd src && pnpm test)\n```\n\nTo run the frontend tests, visit: http://localhost:9001/tests/frontend/\n\n## Copyright and License\n\nCopyright © [yyyy] [name of copyright owner]\nand the [plugin_name] authors and contributors\n\nLicensed under the [Apache License, Version 2.0](LICENSE) (the \"License\"); you\nmay not use this file except in compliance with the License. You may obtain a\ncopy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n","bin/plugins/lib/CONTRIBUTING.md":"# Contributor Guidelines\n(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad#get-in-touch))\n\n## Pull requests\n\n* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary\n* PRs should be issued against the **develop** branch: we never pull directly into **master**\n* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing\n* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)\n* contain meaningful and detailed **commit messages** in the form:\n  ```\n  submodule: description\n\n  longer description of the change you have made, eventually mentioning the\n  number of the issue that is being fixed, in the form: Fixes #someIssueNumber\n  ```\n* if the PR is a **bug fix**:\n  * the first commit in the series must be a test that shows the failure\n  * subsequent commits will fix the bug and make the test pass\n  * the final commit message should include the text `Fixes: #xxx` to link it to its bug report\n* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**\n* if you want to remove a feature, **deprecate it instead**:\n  * write an issue with your deprecation plan\n  * output a `WARN` in the log informing that the feature is going to be removed\n  * remove the feature in the next version\n* if you want to add a new feature, put it under a **feature flag**:\n  * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early\n  * expose a mechanism for enabling/disabling the feature\n  * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration\n* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it\n\n## How to write a bug report\n\n* Please be polite, we all are humans and problems can occur.\n* Please add as much information as possible, for example\n  * client os(s) and version(s)\n    * browser(s) and version(s), is the problem reproducible on different clients\n    * special environments like firewalls or antivirus\n  * host os and version\n    * npm and nodejs version\n    * Logfiles if available\n  * steps to reproduce\n  * what you expected to happen\n  * what actually happened\n* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.\n\nIf you send logfiles, please set the loglevel switch DEBUG in your settings.json file:\n\n```\n/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */\n  \"loglevel\": \"DEBUG\",\n```\n\nThe logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.\n\n## General goals of Etherpad\nTo make sure everybody is going in the same direction:\n* easy to install for admins and easy to use for people\n* easy to integrate into other apps, but also usable as standalone\n* lightweight and scalable\n* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.\nAlso, keep it maintainable. We don't wanna end up as the monster Etherpad was!\n\n## How to work with git?\n* Don't work in your master branch.\n* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)\n* Don't use the online edit function of github (this only creates ugly and not working commits!)\n* Try to make clean commits that are easy readable (including descriptive commit messages!)\n* Test before you push. Sounds easy, it isn't!\n* Don't check in stuff that gets generated during build or runtime\n* Make small pull requests that are easy to review but make sure they do add value by themselves / individually\n\n## Coding style\n* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)\n* Never ever use tabs\n* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces\n* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!\n* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)\n* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!\n* If you do make changes, document them! (see below)\n* Use protocol independent urls \"//\"\n\n## Branching model / git workflow\nsee git flow http://nvie.com/posts/a-successful-git-branching-model/\n\n### `master` branch\n* the stable\n* This is the branch everyone should use for production stuff\n\n### `develop`branch\n* everything that is READY to go into master at some point in time\n* This stuff is tested and ready to go out\n\n### release branches\n* stuff that should go into master very soon\n* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)\n* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.\n\n### hotfix branches\n* fixes for bugs in master\n\n### feature branches (in your own repos)\n* these are the branches where you develop your features in\n* If it's ready to go out, it will be merged into develop\n\nOver the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop\n\n## Documentation\nThe docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.\n\nDocumentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.\n\nYou can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.\n\n## Testing\n\nFront-end tests are found in the `src/tests/frontend/` folder in the repository.\nRun them by pointing your browser to `<yourdomainhere>/tests/frontend`.\n\nBack-end tests can be run from the `src` directory, via `npm test`.\n\n## Things you can help with\nEtherpad is much more than software.  So if you aren't a developer then worry not, there is still a LOT you can do!  A big part of what we do is community engagement.  You can help in the following ways\n * Triage bugs (applying labels) and confirming their existence\n * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required\n * Notifying large site admins of new releases\n * Writing Changelogs for releases\n * Creating Windows packages\n * Creating releases\n * Bumping dependencies periodically and checking they don't break anything\n * Write proposals for grants\n * Co-Author and Publish CVEs\n * Work with SFC to maintain legal side of project\n * Maintain TODO page - https://github.com/ether/etherpad/wiki/TODO#IMPORTANT_TODOS\n\n","AGENTS.MD":"# Agent Guide - Etherpad\n\nWelcome to the Etherpad project. This guide provides essential context and instructions for AI agents and developers to effectively contribute to the codebase.\n\n## Project Overview\nEtherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins.\n\n## Technical Stack\n- **Runtime:** Node.js >= 22.12.0\n- **Package Manager:** pnpm (>= 11.0.0)\n- **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML\n- **Backend:** Express.js 5, Socket.io 4\n- **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`)\n- **Database:** ueberdb2 abstraction (supports dirtyDB, MySQL, PostgreSQL, Redis)\n- **Build Tools:** Vite (for `ui` and `admin`), esbuild, tsx\n- **Testing:** Mocha (backend), Playwright (frontend E2E), Vitest (unit)\n- **Auth:** JWT (jose library), OIDC provider\n\n## Directory Structure\n- `src/node/` - Backend logic, API handlers, database models, hooks\n- `src/static/` - Core frontend logic (legacy jQuery-based editor)\n- `src/static/js/pluginfw/` - Plugin framework (installer, hook system)\n- `src/tests/` - Test suites (backend, frontend, container)\n- `ui/` - Modern React OIDC login UI (Vite + TypeScript)\n- `admin/` - Modern React admin panel (Vite + TypeScript + Radix UI)\n- `bin/` - CLI utilities, build scripts, plugin management tools\n- `bin/plugins/` - Plugin maintenance scripts (checkPlugin.ts, updateCorePlugins.sh)\n- `doc/` - Documentation (VitePress + Markdown/AsciiDoc)\n- `local_plugins/` - Directory for developing and testing plugins locally\n- `var/` - Runtime data (logs, dirtyDB, etc. - ignored by git)\n\n## Quick Start\n\n```bash\npnpm install                                    # Install all dependencies\npnpm run build:etherpad                         # Build admin UI and static assets\npnpm --filter ep_etherpad-lite run dev          # Start dev server (port 9001)\npnpm --filter ep_etherpad-lite run prod         # Start production server\n```\n\n## Core Mandates & Conventions\n\n### Coding Style\n- **Indentation:** 2 spaces for all files (JS/TS/CSS/HTML). No tabs.\n- **TypeScript:** All new code should be TypeScript. Strict mode is enabled.\n- **Comments:** Provide clear comments for complex logic only.\n- **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files.\n\n### Internationalisation (i18n) — Mandatory\n- **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.\n- **React (admin SPA):** use `<Trans i18nKey=\"…\"/>` for JSX text and `t('…')` for attribute values. The `t` comes from `useTranslation()`.\n- **Pad UI (legacy):** use `data-l10n-id=\"…\"` (html10n) — never bind `window._` directly to `html10n.get` and call it (it's unbound; returns `undefined`).\n- **String keys live in `src/locales/en.json`.** Other locales sync from translatewiki on its own cadence — never hand-edit non-EN locale files.\n- **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.\n- **Naming:** dot-namespaced, kebab-or-underscore-cased: `admin_plugins.subtitle`, `admin_pads.filter.all`. Group by page/feature.\n- **Pluralisation:** use i18next's `_one`/`_other` suffix forms with `t('key', {count: n})` — never `n > 1 ? 'X items' : '1 item'`.\n- **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'`.\n- **`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.\n- **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.\n\n### Accessibility (a11y) — Mandatory\n- **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.\n- **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.\n- **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>`.\n- **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.\n- **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).\n\n### UI changes need a human click-through — Mandatory\nAutomated tests are not enough for UI. If an AI tool wrote or changed anything a user can see or interact with (a button, popup, form, setting, layout, wording, styling), a human must run it and use it before the PR is opened.\n\n- **Agents: stop and ask before opening the PR.** Start a local instance with the change, tell the human the URL and exactly what to click, and wait for them to say they tried it and are happy with how it looks and behaves. Do not open the PR until they do.\n- **Never tick the click-through checkbox in the PR template yourself.** Only the human who did the click-through ticks it. If they haven't, leave it unticked and say so in the PR description.\n- **Fix what they find first.** Layout, placement, wording and \"this should be one click\" feedback is the point of the click-through. Address it before the PR, not in review.\n- **A Playwright spec is still required** (see Accessibility above). It covers regressions, not whether the UI is any good.\n\n### Development Workflow\n- **Branching:** Work in feature branches. Issue PRs against the `develop` branch. Never PR directly to `master`.\n- **Commits:** Maintain a linear history (no merge commits). Use meaningful messages in the format: `submodule: description`.\n- **Feature Flags:** New features should be placed behind feature flags and disabled by default.\n- **Deprecation:** Never remove features abruptly; deprecate them first with a `WARN` log.\n- **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.\n\n### Testing & Validation\n- **Requirement:** Every bug fix MUST include a regression test in the same commit.\n- **Always run tests locally before pushing to CI.**\n- **Linting:** `pnpm run lint`\n- **Type Check:** `pnpm --filter ep_etherpad-lite run ts-check`\n- **Build:** `pnpm run build:etherpad` before production deployment\n\n#### Running Backend Tests Locally\n\nBackend tests use Mocha with tsx and run against a real server instance (started automatically by the test harness). No separate server process is needed.\n\n```bash\n# Run ALL backend tests (includes plugin tests)\npnpm --filter ep_etherpad-lite run test\n\n# Run only utility tests (faster, ~5s timeout)\npnpm --filter ep_etherpad-lite run test-utils\n\n# Run a single test file directly\ncd src && cross-env NODE_ENV=production npx mocha --import=tsx --timeout 120000 tests/backend/specs/YOUR_TEST.ts\n\n# Run unit tests (Vitest)\ncd src && npx vitest\n```\n\n- Tests run with `NODE_ENV=production`.\n- Default timeout is 120 seconds per test.\n- Test files live in `src/tests/backend/specs/`.\n- Plugin backend tests live in `node_modules/ep_*/static/tests/backend/specs/` (at repo root) and are included automatically by the test script.\n\n#### Running Frontend E2E Tests Locally\n\nFrontend tests use Playwright. **You must have a running Etherpad server** before launching them — the Playwright config does not auto-start the server.\n\n**Before running frontend or admin tests, ensure Playwright browsers are installed.** Check and install if needed:\n\n```bash\n# Check which browsers are installed\ncd src && npx playwright install --dry-run\n\n# Install all browsers and their system dependencies (must run from src/)\ncd src && npx playwright install\ncd src && sudo npx playwright install-deps\n```\n\nIf `sudo` is unavailable, install system dependencies for webkit manually:\n```bash\n# Check which system libraries are missing for webkit\nldd ~/.cache/ms-playwright/webkit-*/minibrowser-wpe/MiniBrowser 2>&1 | grep \"not found\"\n```\n\nIf browsers or system dependencies are missing, tests will fail silently or timeout — **always verify browser installation before debugging test failures.**\n\n```bash\n# 1. Start the dev server in a separate terminal\npnpm --filter ep_etherpad-lite run dev\n\n# 2. Run frontend E2E tests\npnpm --filter ep_etherpad-lite run test-ui\n\n# 3. Run with interactive Playwright UI (useful for debugging)\npnpm --filter ep_etherpad-lite run test-ui:ui\n\n# Run a single test file\ncd src && cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs/YOUR_TEST.spec.ts\n```\n\n- Tests expect the server at `localhost:9001`.\n- Test files live in `src/tests/frontend-new/specs/`.\n- Runs against chromium and firefox by default (webkit is disabled).\n- Playwright config is at `src/playwright.config.ts`.\n\n#### Running Admin Panel Tests Locally\n\n```bash\n# Requires a running server and Playwright browsers installed (same as frontend tests)\npnpm --filter ep_etherpad-lite run test-admin\n\n# Interactive UI mode\npnpm --filter ep_etherpad-lite run test-admin:ui\n```\n\n- Admin tests run with `--workers 1` (sequential) on chromium and firefox only.\n- Test files live in `src/tests/frontend-new/admin-spec/`.\n\n### Backend Test Auth\nTests use JWT authentication, not API keys. Pattern:\n```typescript\nimport * as common from 'ep_etherpad-lite/tests/backend/common';\n\nconst agent = await common.init();  // Starts server, returns supertest agent\nconst token = await common.generateJWTToken();\nagent.get('/api/1/endpoint').set('authorization', token);\n```\nDo not use `APIKEY.txt` — it may not exist in the test environment.\n\n## Key Concepts\n\n### Easysync\nThe real-time synchronization engine. It is complex; refer to `doc/public/easysync/` before modifying core synchronization logic.\n\n### Plugin Framework\nMost functionality should be implemented as plugins (`ep_*`). Avoid modifying the core unless absolutely necessary.\n\n**Plugin structure:**\n```\nep_myplugin/\n├── ep.json              # Hook declarations (server_hooks, client_hooks)\n├── index.js             # Server-side hook implementations\n├── package.json\n├── static/\n│   ├── js/              # Client-side code\n│   ├── css/\n│   └── tests/\n│       ├── backend/specs/    # Backend tests (Mocha)\n│       └── frontend-new/     # Frontend tests (Playwright)\n├── templates/           # EJS templates\n└── locales/             # i18n files\n```\n\n**Plugin management:**\n```bash\npnpm run plugins i ep_plugin_name       # Install from npm\npnpm run plugins i --path ../plugin     # Install from local path\npnpm run plugins rm ep_plugin_name      # Remove\npnpm run plugins ls                     # List installed\n```\n\n**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/`.\n\n### Plugin Repositories\n- **Monorepo:** `ether/ether-plugins` contains 80+ plugins with shared CI/publishing\n- **Standalone repos:** Individual `ether/ep_*` repos still exist for many plugins\n- **Plugin CI templates:** `bin/plugins/lib/` contains workflow templates pushed to standalone plugin repos via `checkPlugin.ts`\n- **Shared pipelines:** `ether/ether-pipelines` contains reusable GitHub Actions workflows for plugin CI\n\n### Settings\nConfigured 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}\"`.\n\n## Releasing\n\nReleases are driven almost entirely by GitHub Actions. A maintainer dispatches **one** workflow; the version bump, tagging, GitHub Release, Docker images, and snap all cascade off the pushed tag. The npm publish is a separate manual dispatch.\n\n### Prerequisites (check these before dispatching)\n- **A `# X.Y.Z` changelog section for the _target_ version must already be at the top of `CHANGELOG.md`.** `bin/release.ts` aborts with `No changelog record for X.Y.Z, please create changelog record` if it's missing. Write the section before dispatching.\n- **All four `package.json` files must agree on the current version:** root `package.json`, `src/package.json`, `admin/package.json`, `bin/package.json`. `release.ts` reads the *current* version from **`src/package.json`** and computes the next with `semver.inc(current, type)`. If the files are out of sync (e.g. one was hand-edited), the computed target is wrong and the changelog guard fails. *(This exact desync — `src`/`bin` left at 3.3.0 while root/admin were 3.2.0 — blocked the 3.3.0 release in June 2026.)*\n\n### Cutting a release\n1. **Actions → \"Release etherpad\"** → Run workflow (`workflow_dispatch`), choose `patch` / `minor` / `major`. Cadence is monthly **minors** (3.0.0 → 3.1.0 → 3.2.0 → …); use `major` only for breaking changes.\n2. The **Prepare release** step runs `bin/release.ts`, which:\n   - sanity-checks the tree (clean working dir, on `develop`, `develop`/`master` upstreams in sync, `../ether.github.com` cloned & clean on `master`);\n   - bumps the version in all four `package.json` files;\n   - commits `bump version`, merges `develop` → `master`, creates both `X.Y.Z` **and** `vX.Y.Z` tags, merges `master` back to `develop`;\n   - builds and stages the versioned docs into the website repo (see **Documentation** below).\n3. The **Push after release** step (`bin/push-after-release.sh`) pushes `master`, `develop`, the tag, `--tags`, and the `ether.github.com` docs commit.\n4. The pushed **`vX.Y.Z` tag auto-triggers** three workflows:\n   - `handleRelease.yml` → builds Etherpad, extracts the matching changelog section via `generateChangelog` (`bin/generateReleaseNotes.ts`), and publishes the **GitHub Release** (`make_latest: true`);\n   - `docker.yml` → builds & pushes the Docker images;\n   - `snap-publish.yml` → publishes the snap.\n5. **npm publish — PARKED, not part of the release.** `releaseEtherpad.yaml` publishes the core as `ep_etherpad` to npm, but that package is **not load-bearing**: it has 0 dependents, nothing depends on it (plugins import `ep_etherpad-lite` from the *local* core install; plugin CI clones the repo), and Etherpad is run via clone/Docker/zip/snap — never `npm install`. The publish currently fails with `E404` because `ep_etherpad` has no OIDC trusted publisher configured on npmjs.com, which is why npm sits at 2.5.0 while 3.x shipped fine without it. The workflow is gated behind a `confirm: true` input so it can't run by accident. **Skip it for a normal release.** To revive it, the npm owner of `ep_etherpad` (`samtv12345`) configures a trusted publisher (npmjs.com → ep_etherpad → Settings → Trusted Publisher → repo `ether/etherpad`, workflow `releaseEtherpad.yml`); otherwise the workflow can be removed. Decision pending.\n\n### Documentation\n\nTwo distinct things, both important:\n\n**1. Per-PR doc updates — your responsibility in every behaviour-change PR.**\nThe `doc/` workspace holds the user/admin/API docs (Markdown + AsciiDoc). Whenever a PR changes API responses, CLI flags, settings keys, hooks, or error formats, update the relevant files in `doc/` **in the same PR** — don't defer it to release time. The HTTP API reference is `doc/api/http_api.{md,adoc}` (keep both in sync). Preview locally with `pnpm run makeDocs`.\n\n**2. Release-time versioned docs publishing — automated, no manual step.**\nDuring a release, `release.ts` runs `pnpm run makeDocs` (→ `bin/make_docs.ts`) to render `doc/` into `out/doc/`, then copies it into the sibling website repo at `../ether.github.com/public/doc/vX.Y.Z`, bumps that repo's version, and commits `X.Y.Z docs`; `push-after-release.sh` pushes it, publishing the versioned docs on etherpad.org. This requires `ether.github.com` checked out as a **sibling directory** — the **Release etherpad** workflow checks it out automatically. If you ever run `release.ts` by hand, clone it first: `cd .. && git clone git@github.com:ether/ether.github.com.git`.\n\n## Monorepo Structure\n\nThis project uses pnpm workspaces. The workspaces are:\n- `src/` - Core Etherpad (package: `ep_etherpad-lite`)\n- `bin/` - CLI tools and plugin scripts\n- `ui/` - Login UI\n- `admin/` - Admin panel\n- `doc/` - Documentation\n\nRoot-level commands operate across all workspaces. Use `pnpm --filter <package>` to target specific workspaces.\n\n## AI-Specific Guidance\nAI/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.\n"},"items":[{"name":"AGENTS.MD","path":"AGENTS.MD","rawUrl":"https://raw.githubusercontent.com/ether/etherpad/HEAD/AGENTS.MD","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Agent Guide - Etherpad\n\nWelcome to the Etherpad project. This guide provides essential context and instructions for AI agents and developers to effectively contribute to the codebase.\n\n## Project Overview\nEtherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins.\n\n## Technical Stack\n- **Runtime:** Node.js >= 22.12.0\n- **Package Manager:** pnpm (>= 11.0.0)\n- **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML\n- **Backend:** Express.js 5, Socket.io 4\n- **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`)\n- **Database:** ueberdb2 abstraction (supports dirtyDB, MySQL, PostgreSQL, Redis)\n- **Build Tools:** Vite (for `ui` and `admin`), esbuild, tsx\n- **Testing:** Mocha (backend), Playwright (frontend E2E), Vitest (unit)\n- **Auth:** JWT (jose library), OIDC provider\n\n## Directory Structure\n- `src/node/` - Backend logic, API handlers, database models, hooks\n- `src/static/` - Core frontend logic (legacy jQuery-based editor)\n- `src/static/js/pluginfw/` - Plugin framework (installer, hook system)\n- `src/tests/` - Test suites (backend, frontend, container)\n- `ui/` - Modern React OIDC login UI (Vite + TypeScript)\n- `admin/` - Modern React admin panel (Vite + TypeScript + Radix UI)\n- `bin/` - CLI utilities, build scripts, plugin management tools\n- `bin/plugins/` - Plugin maintenance scripts (checkPlugin.ts, updateCorePlugins.sh)\n- `doc/` - Documentation (VitePress + Markdown/AsciiDoc)\n- `local_plugins/` - Directory for developing and testing plugins locally\n- `var/` - Runtime data (logs, dirtyDB, etc. - ignored by git)\n\n## Quick Start\n\n```bash\npnpm install                                    # Install all dependencies\npnpm run build:etherpad                         # Build admin UI and static assets\npnpm --filter ep_etherpad-lite run dev          # Start dev server (port 9001)\npnpm --filter ep_etherpad-lite run prod         # Start production server\n```\n\n## Core Mandates & Conventions\n\n### Coding Style\n- **Indentation:** 2 spaces for all files (JS/TS/CSS/HTML). No tabs.\n- **TypeScript:** All new code should be TypeScript. Strict mode is enabled.\n- **Comments:** Provide clear comments for complex logic only.\n- **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files.\n\n### Internationalisation (i18n) — Mandatory\n- **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.\n- **React (admin SPA):** use `<Trans i18nKey=\"…\"/>` for JSX text and `t('…')` for attribute values. The `t` comes from `useTranslation()`.\n- **Pad UI (legacy):** use `data-l10n-id=\"…\"` (html10n) — never bind `window._` directly to `html10n.get` and call it (it's unbound; returns `undefined`).\n- **String keys live in `src/locales/en.json`.** Other locales sync from translatewiki on its own cadence — never hand-edit non-EN locale files.\n- **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.\n- **Naming:** dot-namespaced, kebab-or-underscore-cased: `admin_plugins.subtitle`, `admin_pads.filter.all`. Group by page/feature.\n- **Pluralisation:** use i18next's `_one`/`_other` suffix forms with `t('key', {count: n})` — never `n > 1 ? 'X items' : '1 item'`.\n- **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'`.\n- **`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.\n- **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.\n\n### Accessibility (a11y) — Mandatory\n- **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.\n- **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.\n- **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>`.\n- **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.\n- **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).\n\n### UI changes need a human click-through — Mandatory\nAutomated tests are not enough for UI. If an AI tool wrote or changed anything a user can see or interact with (a button, popup, form, setting, layout, wording, styling), a human must run it and use it before the PR is opened.\n\n- **Agents: stop and ask before opening the PR.** Start a local instance with the change, tell the human the URL and exactly what to click, and wait for them to say they tried it and are happy with how it looks and behaves. Do not open the PR until they do.\n- **Never tick the click-through checkbox in the PR template yourself.** Only the human who did the click-through ticks it. If they haven't, leave it unticked and say so in the PR description.\n- **Fix what they find first.** Layout, placement, wording and \"this should be one click\" feedback is the point of the click-through. Address it before the PR, not in review.\n- **A Playwright spec is still required** (see Accessibility above). It covers regressions, not whether the UI is any good.\n\n### Development Workflow\n- **Branching:** Work in feature branches. Issue PRs against the `develop` branch. Never PR directly to `master`.\n- **Commits:** Maintain a linear history (no merge commits). Use meaningful messages in the format: `submodule: description`.\n- **Feature Flags:** New features should be placed behind feature flags and disabled by default.\n- **Deprecation:** Never remove features abruptly; deprecate them first with a `WARN` log.\n- **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.\n\n### Testing & Validation\n- **Requirement:** Every bug fix MUST include a regression test in the same commit.\n- **Always run tests locally before pushing to CI.**\n- **Linting:** `pnpm run lint`\n- **Type Check:** `pnpm --filter ep_etherpad-lite run ts-check`\n- **Build:** `pnpm run build:etherpad` before production deployment\n\n#### Running Backend Tests Locally\n\nBackend tests use Mocha with tsx and run against a real server instance (started automatically by the test harness). No separate server process is needed.\n\n```bash\n# Run ALL backend tests (includes plugin tests)\npnpm --filter ep_etherpad-lite run test\n\n# Run only utility tests (faster, ~5s timeout)\npnpm --filter ep_etherpad-lite run test-utils\n\n# Run a single test file directly\ncd src && cross-env NODE_ENV=production npx mocha --import=tsx --timeout 120000 tests/backend/specs/YOUR_TEST.ts\n\n# Run unit tests (Vitest)\ncd src && npx vitest\n```\n\n- Tests run with `NODE_ENV=production`.\n- Default timeout is 120 seconds per test.\n- Test files live in `src/tests/backend/specs/`.\n- Plugin backend tests live in `node_modules/ep_*/static/tests/backend/specs/` (at repo root) and are included automatically by the test script.\n\n#### Running Frontend E2E Tests Locally\n\nFrontend tests use Playwright. **You must have a running Etherpad server** before launching them — the Playwright config does not auto-start the server.\n\n**Before running frontend or admin tests, ensure Playwright browsers are installed.** Check and install if needed:\n\n```bash\n# Check which browsers are installed\ncd src && npx playwright install --dry-run\n\n# Install all browsers and their system dependencies (must run from src/)\ncd src && npx playwright install\ncd src && sudo npx playwright install-deps\n```\n\nIf `sudo` is unavailable, install system dependencies for webkit manually:\n```bash\n# Check which system libraries are missing for webkit\nldd ~/.cache/ms-playwright/webkit-*/minibrowser-wpe/MiniBrowser 2>&1 | grep \"not found\"\n```\n\nIf browsers or system dependencies are missing, tests will fail silently or timeout — **always verify browser installation before debugging test failures.**\n\n```bash\n# 1. Start the dev server in a separate terminal\npnpm --filter ep_etherpad-lite run dev\n\n# 2. Run frontend E2E tests\npnpm --filter ep_etherpad-lite run test-ui\n\n# 3. Run with interactive Playwright UI (useful for debugging)\npnpm --filter ep_etherpad-lite run test-ui:ui\n\n# Run a single test file\ncd src && cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs/YOUR_TEST.spec.ts\n```\n\n- Tests expect the server at `localhost:9001`.\n- Test files live in `src/tests/frontend-new/specs/`.\n- Runs against chromium and firefox by default (webkit is disabled).\n- Playwright config is at `src/playwright.config.ts`.\n\n#### Running Admin Panel Tests Locally\n\n```bash\n# Requires a running server and Playwright browsers installed (same as frontend tests)\npnpm --filter ep_etherpad-lite run test-admin\n\n# Interactive UI mode\npnpm --filter ep_etherpad-lite run test-admin:ui\n```\n\n- Admin tests run with `--workers 1` (sequential) on chromium and firefox only.\n- Test files live in `src/tests/frontend-new/admin-spec/`.\n\n### Backend Test Auth\nTests use JWT authentication, not API keys. Pattern:\n```typescript\nimport * as common from 'ep_etherpad-lite/tests/backend/common';\n\nconst agent = await common.init();  // Starts server, returns supertest agent\nconst token = await common.generateJWTToken();\nagent.get('/api/1/endpoint').set('authorization', token);\n```\nDo not use `APIKEY.txt` — it may not exist in the test environment.\n\n## Key Concepts\n\n### Easysync\nThe real-time synchronization engine. It is complex; refer to `doc/public/easysync/` before modifying core synchronization logic.\n\n### Plugin Framework\nMost functionality should be implemented as plugins (`ep_*`). Avoid modifying the core unless absolutely necessary.\n\n**Plugin structure:**\n```\nep_myplugin/\n├── ep.json              # Hook declarations (server_hooks, client_hooks)\n├── index.js             # Server-side hook implementations\n├── package.json\n├── static/\n│   ├── js/              # Client-side code\n│   ├── css/\n│   └── tests/\n│       ├── backend/specs/    # Backend tests (Mocha)\n│       └── frontend-new/     # Frontend tests (Playwright)\n├── templates/           # EJS templates\n└── locales/             # i18n files\n```\n\n**Plugin management:**\n```bash\npnpm run plugins i ep_plugin_name       # Install from npm\npnpm run plugins i --path ../plugin     # Install from local path\npnpm run plugins rm ep_plugin_name      # Remove\npnpm run plugins ls                     # List installed\n```\n\n**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/`.\n\n### Plugin Repositories\n- **Monorepo:** `ether/ether-plugins` contains 80+ plugins with shared CI/publishing\n- **Standalone repos:** Individual `ether/ep_*` repos still exist for many plugins\n- **Plugin CI templates:** `bin/plugins/lib/` contains workflow templates pushed to standalone plugin repos via `checkPlugin.ts`\n- **Shared pipelines:** `ether/ether-pipelines` contains reusable GitHub Actions workflows for plugin CI\n\n### Settings\nConfigured 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}\"`.\n\n## Releasing\n\nReleases are driven almost entirely by GitHub Actions. A maintainer dispatches **one** workflow; the version bump, tagging, GitHub Release, Docker images, and snap all cascade off the pushed tag. The npm publish is a separate manual dispatch.\n\n### Prerequisites (check these before dispatching)\n- **A `# X.Y.Z` changelog section for the _target_ version must already be at the top of `CHANGELOG.md`.** `bin/release.ts` aborts with `No changelog record for X.Y.Z, please create changelog record` if it's missing. Write the section before dispatching.\n- **All four `package.json` files must agree on the current version:** root `package.json`, `src/package.json`, `admin/package.json`, `bin/package.json`. `release.ts` reads the *current* version from **`src/package.json`** and computes the next with `semver.inc(current, type)`. If the files are out of sync (e.g. one was hand-edited), the computed target is wrong and the changelog guard fails. *(This exact desync — `src`/`bin` left at 3.3.0 while root/admin were 3.2.0 — blocked the 3.3.0 release in June 2026.)*\n\n### Cutting a release\n1. **Actions → \"Release etherpad\"** → Run workflow (`workflow_dispatch`), choose `patch` / `minor` / `major`. Cadence is monthly **minors** (3.0.0 → 3.1.0 → 3.2.0 → …); use `major` only for breaking changes.\n2. The **Prepare release** step runs `bin/release.ts`, which:\n   - sanity-checks the tree (clean working dir, on `develop`, `develop`/`master` upstreams in sync, `../ether.github.com` cloned & clean on `master`);\n   - bumps the version in all four `package.json` files;\n   - commits `bump version`, merges `develop` → `master`, creates both `X.Y.Z` **and** `vX.Y.Z` tags, merges `master` back to `develop`;\n   - builds and stages the versioned docs into the website repo (see **Documentation** below).\n3. The **Push after release** step (`bin/push-after-release.sh`) pushes `master`, `develop`, the tag, `--tags`, and the `ether.github.com` docs commit.\n4. The pushed **`vX.Y.Z` tag auto-triggers** three workflows:\n   - `handleRelease.yml` → builds Etherpad, extracts the matching changelog section via `generateChangelog` (`bin/generateReleaseNotes.ts`), and publishes the **GitHub Release** (`make_latest: true`);\n   - `docker.yml` → builds & pushes the Docker images;\n   - `snap-publish.yml` → publishes the snap.\n5. **npm publish — PARKED, not part of the release.** `releaseEtherpad.yaml` publishes the core as `ep_etherpad` to npm, but that package is **not load-bearing**: it has 0 dependents, nothing depends on it (plugins import `ep_etherpad-lite` from the *local* core install; plugin CI clones the repo), and Etherpad is run via clone/Docker/zip/snap — never `npm install`. The publish currently fails with `E404` because `ep_etherpad` has no OIDC trusted publisher configured on npmjs.com, which is why npm sits at 2.5.0 while 3.x shipped fine without it. The workflow is gated behind a `confirm: true` input so it can't run by accident. **Skip it for a normal release.** To revive it, the npm owner of `ep_etherpad` (`samtv12345`) configures a trusted publisher (npmjs.com → ep_etherpad → Settings → Trusted Publisher → repo `ether/etherpad`, workflow `releaseEtherpad.yml`); otherwise the workflow can be removed. Decision pending.\n\n### Documentation\n\nTwo distinct things, both important:\n\n**1. Per-PR doc updates — your responsibility in every behaviour-change PR.**\nThe `doc/` workspace holds the user/admin/API docs (Markdown + AsciiDoc). Whenever a PR changes API responses, CLI flags, settings keys, hooks, or error formats, update the relevant files in `doc/` **in the same PR** — don't defer it to release time. The HTTP API reference is `doc/api/http_api.{md,adoc}` (keep both in sync). Preview locally with `pnpm run makeDocs`.\n\n**2. Release-time versioned docs publishing — automated, no manual step.**\nDuring a release, `release.ts` runs `pnpm run makeDocs` (→ `bin/make_docs.ts`) to render `doc/` into `out/doc/`, then copies it into the sibling website repo at `../ether.github.com/public/doc/vX.Y.Z`, bumps that repo's version, and commits `X.Y.Z docs`; `push-after-release.sh` pushes it, publishing the versioned docs on etherpad.org. This requires `ether.github.com` checked out as a **sibling directory** — the **Release etherpad** workflow checks it out automatically. If you ever run `release.ts` by hand, clone it first: `cd .. && git clone git@github.com:ether/ether.github.com.git`.\n\n## Monorepo Structure\n\nThis project uses pnpm workspaces. The workspaces are:\n- `src/` - Core Etherpad (package: `ep_etherpad-lite`)\n- `bin/` - CLI tools and plugin scripts\n- `ui/` - Login UI\n- `admin/` - Admin panel\n- `doc/` - Documentation\n\nRoot-level commands operate across all workspaces. Use `pnpm --filter <package>` to target specific workspaces.\n\n## AI-Specific Guidance\nAI/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.\n","isInternal":false,"tokens":4503,"sizeBytes":18317},{"name":"CONTRIBUTING.md","path":"bin/plugins/lib/CONTRIBUTING.md","rawUrl":"https://raw.githubusercontent.com/ether/etherpad/HEAD/bin/plugins/lib/CONTRIBUTING.md","title":"Lib Skill","category":"anthropic-skill","format":"markdown","content":"# Contributor Guidelines\n(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad#get-in-touch))\n\n## Pull requests\n\n* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary\n* PRs should be issued against the **develop** branch: we never pull directly into **master**\n* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing\n* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)\n* contain meaningful and detailed **commit messages** in the form:\n  ```\n  submodule: description\n\n  longer description of the change you have made, eventually mentioning the\n  number of the issue that is being fixed, in the form: Fixes #someIssueNumber\n  ```\n* if the PR is a **bug fix**:\n  * the first commit in the series must be a test that shows the failure\n  * subsequent commits will fix the bug and make the test pass\n  * the final commit message should include the text `Fixes: #xxx` to link it to its bug report\n* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**\n* if you want to remove a feature, **deprecate it instead**:\n  * write an issue with your deprecation plan\n  * output a `WARN` in the log informing that the feature is going to be removed\n  * remove the feature in the next version\n* if you want to add a new feature, put it under a **feature flag**:\n  * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early\n  * expose a mechanism for enabling/disabling the feature\n  * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration\n* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it\n\n## How to write a bug report\n\n* Please be polite, we all are humans and problems can occur.\n* Please add as much information as possible, for example\n  * client os(s) and version(s)\n    * browser(s) and version(s), is the problem reproducible on different clients\n    * special environments like firewalls or antivirus\n  * host os and version\n    * npm and nodejs version\n    * Logfiles if available\n  * steps to reproduce\n  * what you expected to happen\n  * what actually happened\n* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.\n\nIf you send logfiles, please set the loglevel switch DEBUG in your settings.json file:\n\n```\n/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */\n  \"loglevel\": \"DEBUG\",\n```\n\nThe logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.\n\n## General goals of Etherpad\nTo make sure everybody is going in the same direction:\n* easy to install for admins and easy to use for people\n* easy to integrate into other apps, but also usable as standalone\n* lightweight and scalable\n* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.\nAlso, keep it maintainable. We don't wanna end up as the monster Etherpad was!\n\n## How to work with git?\n* Don't work in your master branch.\n* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)\n* Don't use the online edit function of github (this only creates ugly and not working commits!)\n* Try to make clean commits that are easy readable (including descriptive commit messages!)\n* Test before you push. Sounds easy, it isn't!\n* Don't check in stuff that gets generated during build or runtime\n* Make small pull requests that are easy to review but make sure they do add value by themselves / individually\n\n## Coding style\n* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)\n* Never ever use tabs\n* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces\n* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!\n* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)\n* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!\n* If you do make changes, document them! (see below)\n* Use protocol independent urls \"//\"\n\n## Branching model / git workflow\nsee git flow http://nvie.com/posts/a-successful-git-branching-model/\n\n### `master` branch\n* the stable\n* This is the branch everyone should use for production stuff\n\n### `develop`branch\n* everything that is READY to go into master at some point in time\n* This stuff is tested and ready to go out\n\n### release branches\n* stuff that should go into master very soon\n* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)\n* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.\n\n### hotfix branches\n* fixes for bugs in master\n\n### feature branches (in your own repos)\n* these are the branches where you develop your features in\n* If it's ready to go out, it will be merged into develop\n\nOver the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop\n\n## Documentation\nThe docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.\n\nDocumentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.\n\nYou can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.\n\n## Testing\n\nFront-end tests are found in the `src/tests/frontend/` folder in the repository.\nRun them by pointing your browser to `<yourdomainhere>/tests/frontend`.\n\nBack-end tests can be run from the `src` directory, via `npm test`.\n\n## Things you can help with\nEtherpad is much more than software.  So if you aren't a developer then worry not, there is still a LOT you can do!  A big part of what we do is community engagement.  You can help in the following ways\n * Triage bugs (applying labels) and confirming their existence\n * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required\n * Notifying large site admins of new releases\n * Writing Changelogs for releases\n * Creating Windows packages\n * Creating releases\n * Bumping dependencies periodically and checking they don't break anything\n * Write proposals for grants\n * Co-Author and Publish CVEs\n * Work with SFC to maintain legal side of project\n * Maintain TODO page - https://github.com/ether/etherpad/wiki/TODO#IMPORTANT_TODOS\n\n","isInternal":false,"tokens":1833,"sizeBytes":8212},{"name":"README.md","path":"bin/plugins/lib/README.md","rawUrl":"https://raw.githubusercontent.com/ether/etherpad/HEAD/bin/plugins/lib/README.md","title":"lib Documentation","category":"plugin-manifest","format":"markdown","content":"# [plugin_name]\n\nTODO: Describe the plugin.\n\n## Example animated gif of usage if appropriate\n\n![screenshot](https://user-images.githubusercontent.com/220864/99979953-97841d80-2d9f-11eb-9782-5f65817c58f4.PNG)\n\n## Installation\n\nFrom the Etherpad working directory, run:\n\n```shell\nnpm install --no-save --legacy-peer-deps [plugin_name]\n```\n\nOr, install from Etherpad's `/admin/plugins` page.\n\n## Configuration\n\nTODO\n\n## Testing\n\nTo run the backend tests, run the following from the Etherpad working directory:\n\n```shell\n(cd src && pnpm test)\n```\n\nTo run the frontend tests, visit: http://localhost:9001/tests/frontend/\n\n## Copyright and License\n\nCopyright © [yyyy] [name of copyright owner]\nand the [plugin_name] authors and contributors\n\nLicensed under the [Apache License, Version 2.0](LICENSE) (the \"License\"); you\nmay not use this file except in compliance with the License. You may obtain a\ncopy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n","isInternal":false,"tokens":302,"sizeBytes":1268},{"name":"README.md","path":"bin/plugins/README.md","rawUrl":"https://raw.githubusercontent.com/ether/etherpad/HEAD/bin/plugins/README.md","title":"plugins Documentation","category":"plugin-manifest","format":"markdown","content":"The files in this folder are for Plugin developers.\n\n# Get suggestions to improve your Plugin\n\nThis code will check your plugin for known usual issues and some suggestions for\nimprovements. No changes will be made to your project.\n\n```\nnode src/bin/plugins/checkPlugin.js $PLUGIN_NAME$\n```\n\n# Basic Example:\n\n```\nnode src/bin/plugins/checkPlugin.js ep_webrtc\n```\n\n## Autofixing - will autofix any issues it can\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autofix\n```\n\n## Autocommitting - fix issues and commit\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autocommit\n```\n\n## Autopush - fix issues, commit, push, and publish (highly dangerous)\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autopush\n```\n\n# All the plugins\n\nReplace johnmclear with your github username\n\n```\n# Clones\ncd node_modules\nGHUSER=johnmclear; curl \"https://api.github.com/users/$GHUSER/repos?per_page=1000\" | grep -o 'git@[^\"]*' | grep /ep_ | xargs -L1 git clone\ncd ..\n\n# autofixes and autocommits /pushes & npm publishes\nfor dir in node_modules/ep_*; do\n  dir=${dir#node_modules/}\n  [ \"$dir\" != ep_etherpad-lite ] || continue\n  node src/bin/plugins/checkPlugin.js \"$dir\" autocommit\ndone\n```\n\n# Automating update of ether organization plugins\n\n```\ngetCorePlugins.sh\nupdateCorePlugins.sh\n```\n","isInternal":false,"tokens":343,"sizeBytes":1287}],"systemPromptSnippet":"<yakaai_skills repo=\"ether/etherpad\">\n<!-- File: AGENTS.MD (Tokens: ~4503 | Category: root-instruction) -->\n# Agent Guide - Etherpad\n\nWelcome to the Etherpad project. This guide provides essential context and instructions for AI agents and developers to effectively contribute to the codebase.\n\n## Project Overview\nEtherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins.\n\n## Technical Stack\n- **Runtime:** Node.js >= 22.12.0\n- **Package Manager:** pnpm (>= 11.0.0)\n- **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML\n- **Backend:** Express.js 5, Socket.io 4\n- **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`)\n- **Database:** ueberdb2 abstraction (supports dirtyDB, MySQL, PostgreSQL, Redis)\n- **Build Tools:** Vite (for `ui` and `admin`), esbuild, tsx\n- **Testing:** Mocha (backend), Playwright (frontend E2E), Vitest (unit)\n- **Auth:** JWT (jose library), OIDC provider\n\n## Directory Structure\n- `src/node/` - Backend logic, API handlers, database models, hooks\n- `src/static/` - Core frontend logic (legacy jQuery-based editor)\n- `src/static/js/pluginfw/` - Plugin framework (installer, hook system)\n- `src/tests/` - Test suites (backend, frontend, container)\n- `ui/` - Modern React OIDC login UI (Vite + TypeScript)\n- `admin/` - Modern React admin panel (Vite + TypeScript + Radix UI)\n- `bin/` - CLI utilities, build scripts, plugin management tools\n- `bin/plugins/` - Plugin maintenance scripts (checkPlugin.ts, updateCorePlugins.sh)\n- `doc/` - Documentation (VitePress + Markdown/AsciiDoc)\n- `local_plugins/` - Directory for developing and testing plugins locally\n- `var/` - Runtime data (logs, dirtyDB, etc. - ignored by git)\n\n## Quick Start\n\n```bash\npnpm install                                    # Install all dependencies\npnpm run build:etherpad                         # Build admin UI and static assets\npnpm --filter ep_etherpad-lite run dev          # Start dev server (port 9001)\npnpm --filter ep_etherpad-lite run prod         # Start production server\n```\n\n## Core Mandates & Conventions\n\n### Coding Style\n- **Indentation:** 2 spaces for all files (JS/TS/CSS/HTML). No tabs.\n- **TypeScript:** All new code should be TypeScript. Strict mode is enabled.\n- **Comments:** Provide clear comments for complex logic only.\n- **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files.\n\n### Internationalisation (i18n) — Mandatory\n- **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.\n- **React (admin SPA):** use `<Trans i18nKey=\"…\"/>` for JSX text and `t('…')` for attribute values. The `t` comes from `useTranslation()`.\n- **Pad UI (legacy):** use `data-l10n-id=\"…\"` (html10n) — never bind `window._` directly to `html10n.get` and call it (it's unbound; returns `undefined`).\n- **String keys live in `src/locales/en.json`.** Other locales sync from translatewiki on its own cadence — never hand-edit non-EN locale files.\n- **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.\n- **Naming:** dot-namespaced, kebab-or-underscore-cased: `admin_plugins.subtitle`, `admin_pads.filter.all`. Group by page/feature.\n- **Pluralisation:** use i18next's `_one`/`_other` suffix forms with `t('key', {count: n})` — never `n > 1 ? 'X items' : '1 item'`.\n- **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'`.\n- **`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.\n- **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.\n\n### Accessibility (a11y) — Mandatory\n- **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.\n- **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.\n- **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>`.\n- **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.\n- **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).\n\n### UI changes need a human click-through — Mandatory\nAutomated tests are not enough for UI. If an AI tool wrote or changed anything a user can see or interact with (a button, popup, form, setting, layout, wording, styling), a human must run it and use it before the PR is opened.\n\n- **Agents: stop and ask before opening the PR.** Start a local instance with the change, tell the human the URL and exactly what to click, and wait for them to say they tried it and are happy with how it looks and behaves. Do not open the PR until they do.\n- **Never tick the click-through checkbox in the PR template yourself.** Only the human who did the click-through ticks it. If they haven't, leave it unticked and say so in the PR description.\n- **Fix what they find first.** Layout, placement, wording and \"this should be one click\" feedback is the point of the click-through. Address it before the PR, not in review.\n- **A Playwright spec is still required** (see Accessibility above). It covers regressions, not whether the UI is any good.\n\n### Development Workflow\n- **Branching:** Work in feature branches. Issue PRs against the `develop` branch. Never PR directly to `master`.\n- **Commits:** Maintain a linear history (no merge commits). Use meaningful messages in the format: `submodule: description`.\n- **Feature Flags:** New features should be placed behind feature flags and disabled by default.\n- **Deprecation:** Never remove features abruptly; deprecate them first with a `WARN` log.\n- **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.\n\n### Testing & Validation\n- **Requirement:** Every bug fix MUST include a regression test in the same commit.\n- **Always run tests locally before pushing to CI.**\n- **Linting:** `pnpm run lint`\n- **Type Check:** `pnpm --filter ep_etherpad-lite run ts-check`\n- **Build:** `pnpm run build:etherpad` before production deployment\n\n#### Running Backend Tests Locally\n\nBackend tests use Mocha with tsx and run against a real server instance (started automatically by the test harness). No separate server process is needed.\n\n```bash\n# Run ALL backend tests (includes plugin tests)\npnpm --filter ep_etherpad-lite run test\n\n# Run only utility tests (faster, ~5s timeout)\npnpm --filter ep_etherpad-lite run test-utils\n\n# Run a single test file directly\ncd src && cross-env NODE_ENV=production npx mocha --import=tsx --timeout 120000 tests/backend/specs/YOUR_TEST.ts\n\n# Run unit tests (Vitest)\ncd src && npx vitest\n```\n\n- Tests run with `NODE_ENV=production`.\n- Default timeout is 120 seconds per test.\n- Test files live in `src/tests/backend/specs/`.\n- Plugin backend tests live in `node_modules/ep_*/static/tests/backend/specs/` (at repo root) and are included automatically by the test script.\n\n#### Running Frontend E2E Tests Locally\n\nFrontend tests use Playwright. **You must have a running Etherpad server** before launching them — the Playwright config does not auto-start the server.\n\n**Before running frontend or admin tests, ensure Playwright browsers are installed.** Check and install if needed:\n\n```bash\n# Check which browsers are installed\ncd src && npx playwright install --dry-run\n\n# Install all browsers and their system dependencies (must run from src/)\ncd src && npx playwright install\ncd src && sudo npx playwright install-deps\n```\n\nIf `sudo` is unavailable, install system dependencies for webkit manually:\n```bash\n# Check which system libraries are missing for webkit\nldd ~/.cache/ms-playwright/webkit-*/minibrowser-wpe/MiniBrowser 2>&1 | grep \"not found\"\n```\n\nIf browsers or system dependencies are missing, tests will fail silently or timeout — **always verify browser installation before debugging test failures.**\n\n```bash\n# 1. Start the dev server in a separate terminal\npnpm --filter ep_etherpad-lite run dev\n\n# 2. Run frontend E2E tests\npnpm --filter ep_etherpad-lite run test-ui\n\n# 3. Run with interactive Playwright UI (useful for debugging)\npnpm --filter ep_etherpad-lite run test-ui:ui\n\n# Run a single test file\ncd src && cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs/YOUR_TEST.spec.ts\n```\n\n- Tests expect the server at `localhost:9001`.\n- Test files live in `src/tests/frontend-new/specs/`.\n- Runs against chromium and firefox by default (webkit is disabled).\n- Playwright config is at `src/playwright.config.ts`.\n\n#### Running Admin Panel Tests Locally\n\n```bash\n# Requires a running server and Playwright browsers installed (same as frontend tests)\npnpm --filter ep_etherpad-lite run test-admin\n\n# Interactive UI mode\npnpm --filter ep_etherpad-lite run test-admin:ui\n```\n\n- Admin tests run with `--workers 1` (sequential) on chromium and firefox only.\n- Test files live in `src/tests/frontend-new/admin-spec/`.\n\n### Backend Test Auth\nTests use JWT authentication, not API keys. Pattern:\n```typescript\nimport * as common from 'ep_etherpad-lite/tests/backend/common';\n\nconst agent = await common.init();  // Starts server, returns supertest agent\nconst token = await common.generateJWTToken();\nagent.get('/api/1/endpoint').set('authorization', token);\n```\nDo not use `APIKEY.txt` — it may not exist in the test environment.\n\n## Key Concepts\n\n### Easysync\nThe real-time synchronization engine. It is complex; refer to `doc/public/easysync/` before modifying core synchronization logic.\n\n### Plugin Framework\nMost functionality should be implemented as plugins (`ep_*`). Avoid modifying the core unless absolutely necessary.\n\n**Plugin structure:**\n```\nep_myplugin/\n├── ep.json              # Hook declarations (server_hooks, client_hooks)\n├── index.js             # Server-side hook implementations\n├── package.json\n├── static/\n│   ├── js/              # Client-side code\n│   ├── css/\n│   └── tests/\n│       ├── backend/specs/    # Backend tests (Mocha)\n│       └── frontend-new/     # Frontend tests (Playwright)\n├── templates/           # EJS templates\n└── locales/             # i18n files\n```\n\n**Plugin management:**\n```bash\npnpm run plugins i ep_plugin_name       # Install from npm\npnpm run plugins i --path ../plugin     # Install from local path\npnpm run plugins rm ep_plugin_name      # Remove\npnpm run plugins ls                     # List installed\n```\n\n**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/`.\n\n### Plugin Repositories\n- **Monorepo:** `ether/ether-plugins` contains 80+ plugins with shared CI/publishing\n- **Standalone repos:** Individual `ether/ep_*` repos still exist for many plugins\n- **Plugin CI templates:** `bin/plugins/lib/` contains workflow templates pushed to standalone plugin repos via `checkPlugin.ts`\n- **Shared pipelines:** `ether/ether-pipelines` contains reusable GitHub Actions workflows for plugin CI\n\n### Settings\nConfigured 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}\"`.\n\n## Releasing\n\nReleases are driven almost entirely by GitHub Actions. A maintainer dispatches **one** workflow; the version bump, tagging, GitHub Release, Docker images, and snap all cascade off the pushed tag. The npm publish is a separate manual dispatch.\n\n### Prerequisites (check these before dispatching)\n- **A `# X.Y.Z` changelog section for the _target_ version must already be at the top of `CHANGELOG.md`.** `bin/release.ts` aborts with `No changelog record for X.Y.Z, please create changelog record` if it's missing. Write the section before dispatching.\n- **All four `package.json` files must agree on the current version:** root `package.json`, `src/package.json`, `admin/package.json`, `bin/package.json`. `release.ts` reads the *current* version from **`src/package.json`** and computes the next with `semver.inc(current, type)`. If the files are out of sync (e.g. one was hand-edited), the computed target is wrong and the changelog guard fails. *(This exact desync — `src`/`bin` left at 3.3.0 while root/admin were 3.2.0 — blocked the 3.3.0 release in June 2026.)*\n\n### Cutting a release\n1. **Actions → \"Release etherpad\"** → Run workflow (`workflow_dispatch`), choose `patch` / `minor` / `major`. Cadence is monthly **minors** (3.0.0 → 3.1.0 → 3.2.0 → …); use `major` only for breaking changes.\n2. The **Prepare release** step runs `bin/release.ts`, which:\n   - sanity-checks the tree (clean working dir, on `develop`, `develop`/`master` upstreams in sync, `../ether.github.com` cloned & clean on `master`);\n   - bumps the version in all four `package.json` files;\n   - commits `bump version`, merges `develop` → `master`, creates both `X.Y.Z` **and** `vX.Y.Z` tags, merges `master` back to `develop`;\n   - builds and stages the versioned docs into the website repo (see **Documentation** below).\n3. The **Push after release** step (`bin/push-after-release.sh`) pushes `master`, `develop`, the tag, `--tags`, and the `ether.github.com` docs commit.\n4. The pushed **`vX.Y.Z` tag auto-triggers** three workflows:\n   - `handleRelease.yml` → builds Etherpad, extracts the matching changelog section via `generateChangelog` (`bin/generateReleaseNotes.ts`), and publishes the **GitHub Release** (`make_latest: true`);\n   - `docker.yml` → builds & pushes the Docker images;\n   - `snap-publish.yml` → publishes the snap.\n5. **npm publish — PARKED, not part of the release.** `releaseEtherpad.yaml` publishes the core as `ep_etherpad` to npm, but that package is **not load-bearing**: it has 0 dependents, nothing depends on it (plugins import `ep_etherpad-lite` from the *local* core install; plugin CI clones the repo), and Etherpad is run via clone/Docker/zip/snap — never `npm install`. The publish currently fails with `E404` because `ep_etherpad` has no OIDC trusted publisher configured on npmjs.com, which is why npm sits at 2.5.0 while 3.x shipped fine without it. The workflow is gated behind a `confirm: true` input so it can't run by accident. **Skip it for a normal release.** To revive it, the npm owner of `ep_etherpad` (`samtv12345`) configures a trusted publisher (npmjs.com → ep_etherpad → Settings → Trusted Publisher → repo `ether/etherpad`, workflow `releaseEtherpad.yml`); otherwise the workflow can be removed. Decision pending.\n\n### Documentation\n\nTwo distinct things, both important:\n\n**1. Per-PR doc updates — your responsibility in every behaviour-change PR.**\nThe `doc/` workspace holds the user/admin/API docs (Markdown + AsciiDoc). Whenever a PR changes API responses, CLI flags, settings keys, hooks, or error formats, update the relevant files in `doc/` **in the same PR** — don't defer it to release time. The HTTP API reference is `doc/api/http_api.{md,adoc}` (keep both in sync). Preview locally with `pnpm run makeDocs`.\n\n**2. Release-time versioned docs publishing — automated, no manual step.**\nDuring a release, `release.ts` runs `pnpm run makeDocs` (→ `bin/make_docs.ts`) to render `doc/` into `out/doc/`, then copies it into the sibling website repo at `../ether.github.com/public/doc/vX.Y.Z`, bumps that repo's version, and commits `X.Y.Z docs`; `push-after-release.sh` pushes it, publishing the versioned docs on etherpad.org. This requires `ether.github.com` checked out as a **sibling directory** — the **Release etherpad** workflow checks it out automatically. If you ever run `release.ts` by hand, clone it first: `cd .. && git clone git@github.com:ether/ether.github.com.git`.\n\n## Monorepo Structure\n\nThis project uses pnpm workspaces. The workspaces are:\n- `src/` - Core Etherpad (package: `ep_etherpad-lite`)\n- `bin/` - CLI tools and plugin scripts\n- `ui/` - Login UI\n- `admin/` - Admin panel\n- `doc/` - Documentation\n\nRoot-level commands operate across all workspaces. Use `pnpm --filter <package>` to target specific workspaces.\n\n## AI-Specific Guidance\nAI/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.\n\n<!-- File: bin/plugins/lib/CONTRIBUTING.md (Tokens: ~1833 | Category: anthropic-skill) -->\n# Contributor Guidelines\n(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad#get-in-touch))\n\n## Pull requests\n\n* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary\n* PRs should be issued against the **develop** branch: we never pull directly into **master**\n* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing\n* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)\n* contain meaningful and detailed **commit messages** in the form:\n  ```\n  submodule: description\n\n  longer description of the change you have made, eventually mentioning the\n  number of the issue that is being fixed, in the form: Fixes #someIssueNumber\n  ```\n* if the PR is a **bug fix**:\n  * the first commit in the series must be a test that shows the failure\n  * subsequent commits will fix the bug and make the test pass\n  * the final commit message should include the text `Fixes: #xxx` to link it to its bug report\n* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**\n* if you want to remove a feature, **deprecate it instead**:\n  * write an issue with your deprecation plan\n  * output a `WARN` in the log informing that the feature is going to be removed\n  * remove the feature in the next version\n* if you want to add a new feature, put it under a **feature flag**:\n  * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early\n  * expose a mechanism for enabling/disabling the feature\n  * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration\n* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it\n\n## How to write a bug report\n\n* Please be polite, we all are humans and problems can occur.\n* Please add as much information as possible, for example\n  * client os(s) and version(s)\n    * browser(s) and version(s), is the problem reproducible on different clients\n    * special environments like firewalls or antivirus\n  * host os and version\n    * npm and nodejs version\n    * Logfiles if available\n  * steps to reproduce\n  * what you expected to happen\n  * what actually happened\n* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.\n\nIf you send logfiles, please set the loglevel switch DEBUG in your settings.json file:\n\n```\n/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */\n  \"loglevel\": \"DEBUG\",\n```\n\nThe logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.\n\n## General goals of Etherpad\nTo make sure everybody is going in the same direction:\n* easy to install for admins and easy to use for people\n* easy to integrate into other apps, but also usable as standalone\n* lightweight and scalable\n* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.\nAlso, keep it maintainable. We don't wanna end up as the monster Etherpad was!\n\n## How to work with git?\n* Don't work in your master branch.\n* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)\n* Don't use the online edit function of github (this only creates ugly and not working commits!)\n* Try to make clean commits that are easy readable (including descriptive commit messages!)\n* Test before you push. Sounds easy, it isn't!\n* Don't check in stuff that gets generated during build or runtime\n* Make small pull requests that are easy to review but make sure they do add value by themselves / individually\n\n## Coding style\n* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)\n* Never ever use tabs\n* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces\n* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!\n* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)\n* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!\n* If you do make changes, document them! (see below)\n* Use protocol independent urls \"//\"\n\n## Branching model / git workflow\nsee git flow http://nvie.com/posts/a-successful-git-branching-model/\n\n### `master` branch\n* the stable\n* This is the branch everyone should use for production stuff\n\n### `develop`branch\n* everything that is READY to go into master at some point in time\n* This stuff is tested and ready to go out\n\n### release branches\n* stuff that should go into master very soon\n* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)\n* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.\n\n### hotfix branches\n* fixes for bugs in master\n\n### feature branches (in your own repos)\n* these are the branches where you develop your features in\n* If it's ready to go out, it will be merged into develop\n\nOver the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop\n\n## Documentation\nThe docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.\n\nDocumentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.\n\nYou can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.\n\n## Testing\n\nFront-end tests are found in the `src/tests/frontend/` folder in the repository.\nRun them by pointing your browser to `<yourdomainhere>/tests/frontend`.\n\nBack-end tests can be run from the `src` directory, via `npm test`.\n\n## Things you can help with\nEtherpad is much more than software.  So if you aren't a developer then worry not, there is still a LOT you can do!  A big part of what we do is community engagement.  You can help in the following ways\n * Triage bugs (applying labels) and confirming their existence\n * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required\n * Notifying large site admins of new releases\n * Writing Changelogs for releases\n * Creating Windows packages\n * Creating releases\n * Bumping dependencies periodically and checking they don't break anything\n * Write proposals for grants\n * Co-Author and Publish CVEs\n * Work with SFC to maintain legal side of project\n * Maintain TODO page - https://github.com/ether/etherpad/wiki/TODO#IMPORTANT_TODOS\n\n<!-- File: bin/plugins/lib/README.md (Tokens: ~302 | Category: plugin-manifest) -->\n# [plugin_name]\n\nTODO: Describe the plugin.\n\n## Example animated gif of usage if appropriate\n\n![screenshot](https://user-images.githubusercontent.com/220864/99979953-97841d80-2d9f-11eb-9782-5f65817c58f4.PNG)\n\n## Installation\n\nFrom the Etherpad working directory, run:\n\n```shell\nnpm install --no-save --legacy-peer-deps [plugin_name]\n```\n\nOr, install from Etherpad's `/admin/plugins` page.\n\n## Configuration\n\nTODO\n\n## Testing\n\nTo run the backend tests, run the following from the Etherpad working directory:\n\n```shell\n(cd src && pnpm test)\n```\n\nTo run the frontend tests, visit: http://localhost:9001/tests/frontend/\n\n## Copyright and License\n\nCopyright © [yyyy] [name of copyright owner]\nand the [plugin_name] authors and contributors\n\nLicensed under the [Apache License, Version 2.0](LICENSE) (the \"License\"); you\nmay not use this file except in compliance with the License. You may obtain a\ncopy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n\n<!-- File: bin/plugins/README.md (Tokens: ~343 | Category: plugin-manifest) -->\nThe files in this folder are for Plugin developers.\n\n# Get suggestions to improve your Plugin\n\nThis code will check your plugin for known usual issues and some suggestions for\nimprovements. No changes will be made to your project.\n\n```\nnode src/bin/plugins/checkPlugin.js $PLUGIN_NAME$\n```\n\n# Basic Example:\n\n```\nnode src/bin/plugins/checkPlugin.js ep_webrtc\n```\n\n## Autofixing - will autofix any issues it can\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autofix\n```\n\n## Autocommitting - fix issues and commit\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autocommit\n```\n\n## Autopush - fix issues, commit, push, and publish (highly dangerous)\n\n```\nnode src/bin/plugins/checkPlugin.js ep_whatever autopush\n```\n\n# All the plugins\n\nReplace johnmclear with your github username\n\n```\n# Clones\ncd node_modules\nGHUSER=johnmclear; curl \"https://api.github.com/users/$GHUSER/repos?per_page=1000\" | grep -o 'git@[^\"]*' | grep /ep_ | xargs -L1 git clone\ncd ..\n\n# autofixes and autocommits /pushes & npm publishes\nfor dir in node_modules/ep_*; do\n  dir=${dir#node_modules/}\n  [ \"$dir\" != ep_etherpad-lite ] || continue\n  node src/bin/plugins/checkPlugin.js \"$dir\" autocommit\ndone\n```\n\n# Automating update of ether organization plugins\n\n```\ngetCorePlugins.sh\nupdateCorePlugins.sh\n```\n</yakaai_skills>"}