9580 Commits

Author SHA1 Message Date
John McLear
e58dfa4752
feat: add timeslider line numbers (#7542)
* feat: add timeslider line numbers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* perf: coalesce timeslider line-number updates

Addresses Qodo review: updateLineNumbers() was called synchronously
from applyChangeset() on every changeset, forcing full-document layout
reads/writes during timeslider scrubbing/playback. scheduleLineNumberUpdate()
also queued a fresh double-rAF pair for every resize tick. Add a pending
flag so only one rAF pair is in flight, and route applyChangeset() through
the scheduler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:18:54 +01:00
John McLear
e0ccdb4d9f
Add creator-owned pad settings defaults (#7545)
* Add creator-owned pad settings defaults

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine pad settings layout

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix settings popup heading and width

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Explain enforced user settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Cover creator override flow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Let creators bypass enforced settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address pad settings follow-ups

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-19 11:13:44 +01:00
John McLear
2f0b5b0fca
fix: warn when a pending edit is not accepted (#7540)
* fix: warn when a pending edit is not accepted

Show a gritter warning only when the pad disconnects while a local commit is still awaiting acceptance, leaving normal editing UI unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: cover unaccepted-commit warning path

Addresses Qodo review: adds regression coverage for the two contract
changes this PR introduces — acceptCommit() must clear the pending
marker so hasUnacceptedCommit() returns false after a server ACK, and
the disconnect handler must surface the unsaved-edit gritter when a
commit is still pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:11:07 +01:00
John McLear
4137109efe
fix: allow undo of clear authorship colors without disconnect (#7430)
* fix: allow undo of clear authorship colors without disconnect (#2802)

When a user clears authorship colors and then undoes, the undo changeset
re-applies author attributes for all authors who contributed text. The
server was rejecting this because it treated any changeset containing
another author's ID as impersonation, disconnecting the user.

The fix distinguishes between:
- '+' ops (new text): still reject if attributed to another author
- '=' ops (attribute changes on existing text): allow restoring other
  authors' attributes, which is needed for undo of clear authorship

Also removes the client-side workaround in undomodule.ts that prevented
clear authorship from being undone at all, and adds backend + frontend
tests covering the multi-author undo scenario.

Fixes: https://github.com/ether/etherpad-lite/issues/2802

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use robust Playwright assertions in authorship undo tests

- Use toHaveAttribute with regex instead of raw getAttribute + toContain
- Check div/span attributes within pad body instead of broad selectors
- Use Playwright auto-retry (expect with timeout) instead of toHaveCount(0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: handle confirm dialog and sync timing in Playwright tests

- Add page.on('dialog') handler to accept the confirm dialog triggered
  by clearAuthorship when no text is selected (clears whole pad)
- Use auto-retrying toHaveAttribute assertions instead of raw getAttribute
- Increase cross-user sync timeouts to 15s for CI reliability
- Add retries: 2 to multi-user test for CI flakiness
- Scope assertions to pad body spans instead of broad selectors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use persistent socket listeners to avoid missing messages in CI

Replace sequential waitForSocketEvent loops with single persistent
listeners that filter messages inline. This prevents race conditions
where messages arrive between off/on listener cycles, causing timeouts
on slower CI runners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reject - ops with foreign author to prevent pool injection

The '-' op attribs are discarded from the document but still get added
to the pad's attribute pool by moveOpsToNewPool. Without this check, an
attacker could inject a fabricated author ID into the pool via a '-' op,
then use a '=' op to attribute text to that fabricated author (bypassing
the pool existence check).

Now all non-'=' ops (+, -) with foreign author IDs are rejected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: use not.toHaveClass for cleared authorship spans

Addresses Qodo review: linestylefilter skips attribs with empty values,
so a span with author='' has no class attribute at all. The previous
negative-lookahead regex on the class attribute failed against a null
attribute and was flaky in CI. Switch to not.toHaveClass(/author-/),
which also passes when the attribute is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 09:09:03 +01:00
John McLear
84060fb538
feat: add timeslider playback speed setting (#7541)
Add a core timeslider playback speed setting with an original-speed default, a realtime mode that uses revision timestamps, and frontend coverage for the new behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-19 09:08:37 +01:00
John McLear
7ec581afca
feat!: replace Abiword with LibreOffice and add DOCX export (#7539)
* feat!: replace Abiword with LibreOffice and add DOCX export (#4805)

The Abiword converter is dropped. Abiword's DOCX export is weak and the
project is niche on modern platforms; LibreOffice (soffice) is the
common deployment path and now serves as the sole converter backend.

DOCX is added as an export format and becomes the new target for the
"Microsoft Word" UI button. The /export/doc URL still works for legacy
API consumers.

BREAKING CHANGE: The 'abiword' setting, the INSTALL_ABIWORD Dockerfile
build arg, the abiwordAvailable clientVar, and the
#importmessageabiword UI element (with locale key
pad.importExport.abiword.innerHTML) are removed. Deployments relying on
Abiword must configure 'soffice' instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add docxExport feature flag and abiword deprecation WARN

- Add `docxExport: true` setting to opt out of DOCX (use legacy DOC)
- Pass `docxExport` to client via clientVars
- Use `docxExport` flag in pad_impexp.ts for Word button format
- Emit a specific WARN when deprecated `abiword` config is detected
- Update settings.json.template and settings.json.docker with docxExport
- Add docxExport to ClientVarPayload type in SocketIOMessage.ts

Agent-Logs-Url: https://github.com/ether/etherpad/sessions/9afc5291-73b2-4b66-b028-feed39e7056f

Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>

* refactor: extract wordFormat variable and improve docxExport comment

Agent-Logs-Url: https://github.com/ether/etherpad/sessions/9afc5291-73b2-4b66-b028-feed39e7056f

Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>

* fix: restore import-limitation message when no converter is configured

The abiword removal dropped both the #importmessageabiword DOM element
and its locale key, but Copilot's refactor still expected the show()
call to surface a message when exportAvailable === 'no'. Result: users
with no soffice binary got silent failure instead of an explanation.

Add #importmessagenoconverter back with updated, LibreOffice-focused
copy (new locale key pad.importExport.noConverter.innerHTML) and flip
the hidden prop when the client knows no converter is available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* i18n: inline English fallback for noConverter import message

The original abiword message existed in ~70 locale files and was
removed from all of them by this PR. The replacement key was only
added to en.json, so non-English users had an empty div until
translators localize. Follow the project's usual pad.html pattern
(e.g. line 146's "Font type:") and include the English text inside
the div as the fallback content; html10n replaces it when a
translation is available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert "i18n: inline English fallback for noConverter import message"

This reverts commit f336f24d. Follow the project convention: add the
new locale key to en.json only and let translations catch up via the
translation system, rather than putting inline fallback in the template.

* i18n: leave non-English locale files untouched

The PR had removed pad.importExport.abiword.innerHTML from ~82 locale
files alongside its removal from en.json. The replacement message uses
a new key (pad.importExport.noConverter.innerHTML) in en.json only, so
churning every localisation file for a key that is no longer referenced
produces useless translation diffs. Restore every non-en locale file to
its pre-PR state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>
2026-04-19 09:08:22 +01:00
John McLear
66f49bb808
docs(openapi): document apikey auth in openapi.json (#7534)
* docs(openapi): document apikey auth in openapi.json (#7532)

The API accepts the key via ?apikey=, ?api_key=, or the apikey header, but
only ?apikey= was advertised in /api-docs.json. /api/{version}/openapi.json
was worse: it hardcoded an OAuth2 scheme even when Etherpad was started in
apikey auth mode.

Switch both generators on settings.authenticationMethod and publish apiKey
schemes for the query (apikey, api_key) and header (apikey) variants. The
openapi.ts definition is now regenerated per request so runtime settings
are reflected.

The raw authorization: <key> header still works in code but is deliberately
not documented — pinning it in the spec would ossify a quirk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(openapi): add apiKeyAlias/apiKeyHeader conditionally in RestAPI.ts

In SSO mode, apiKeyAlias and apiKeyHeader were always present in
securitySchemes even though they're only relevant when
authenticationMethod is 'apikey'. Mirror the pattern used for the sso
scheme: add these two schemes dynamically inside the apikey branch, and
mark them optional in the TypeScript type annotation.

Agent-Logs-Url: https://github.com/ether/etherpad/sessions/1d440432-7389-462e-9aac-9a3c027640e8

Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>
2026-04-18 17:09:18 +01:00
John McLear
0206e0447c
checkPlugin: flag absolute /static/plugins/ asset paths in templates (#5203) (#7535)
* checkPlugin: flag absolute /static/plugins/ paths in templates (#5203)

Plugin templates that reference assets as \`/static/plugins/...\`
(absolute) silently break any Etherpad instance hosted behind a reverse
proxy at a sub-path — the browser resolves the path against the domain
root instead of the proxy prefix and the asset 404s. The right form is
\`../static/plugins/...\` (relative), which ep_embedmedia PR #4 fixed
manually and which #5203 asked for as a mechanical check.

Walk \`templates/\` and \`static/\` of the plugin, scan every \`*.ejs\` /
\`*.html\` for \`/static/plugins/\` not preceded by a URL scheme, dot, or
word char (so \`https://host/static/plugins/...\` and already-correct
\`../static/plugins/...\` stay untouched). Warn normally; in \`autofix\`
mode rewrite to the relative form in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* checkPlugin: skip autofix for static/*.html

Addresses Qodo review: HTML served from a plugin's static/ directory
resolves against /static/plugins/<plugin>/static/..., so rewriting
/static/plugins/... to ../static/plugins/... yields a broken URL. Keep
scanning static/ for warnings but no longer rewrite, and clarify the
remediation guidance to point at the file's own location.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:09:02 +01:00
dependabot[bot]
d62ab657ef
build(deps): bump oidc-provider from 9.8.1 to 9.8.2 (#7537)
Bumps [oidc-provider](https://github.com/panva/node-oidc-provider) from 9.8.1 to 9.8.2.
- [Release notes](https://github.com/panva/node-oidc-provider/releases)
- [Changelog](https://github.com/panva/node-oidc-provider/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/node-oidc-provider/compare/v9.8.1...v9.8.2)

---
updated-dependencies:
- dependency-name: oidc-provider
  dependency-version: 9.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-18 10:39:56 +01:00
dependabot[bot]
e8adc4d102
build(deps-dev): bump the dev-dependencies group with 5 updates (#7536)
Bumps the dev-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@types/express-session](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express-session) | `1.18.2` | `1.19.0` |
| [eslint-config-etherpad](https://github.com/ether/eslint-config-etherpad) | `4.0.4` | `4.0.5` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.2` | `6.0.3` |
| [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) | `7.0.1` | `7.1.0` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.3` | `17.0.4` |


Updates `@types/express-session` from 1.18.2 to 1.19.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express-session)

Updates `eslint-config-etherpad` from 4.0.4 to 4.0.5
- [Commits](https://github.com/ether/eslint-config-etherpad/compare/v4.0.4...v4.0.5)

Updates `typescript` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.2...v6.0.3)

Updates `eslint-plugin-react-hooks` from 7.0.1 to 7.1.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/eslint-plugin-react-hooks)

Updates `react-i18next` from 17.0.3 to 17.0.4
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.3...v17.0.4)

---
updated-dependencies:
- dependency-name: "@types/express-session"
  dependency-version: 1.19.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: eslint-config-etherpad
  dependency-version: 4.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: typescript
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: eslint-plugin-react-hooks
  dependency-version: 7.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: react-i18next
  dependency-version: 17.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 17:21:33 +01:00
John McLear
aed5424e82
fix: use hardlink package-import-method so the Docker build works on ZFS (#7342) (#7533)
pnpm's default \`auto\` package-import-method eventually falls through
to \`copyfile\`, which uses \`copy_file_range\`. That syscall fails on
ZFS with \`EAGAIN: resource temporarily unavailable\` (see
https://github.com/pnpm/pnpm/issues/7024), so \`docker compose build\`
aborts inside the \`RUN pnpm install\` step on any host with a ZFS
root. Operators had to hand-patch every pnpm invocation in the
Dockerfile and install scripts.

Force \`package-import-method=hardlink\` in \`.npmrc\` so all pnpm
invocations (Docker build, \`bin/installDeps.sh\`,
\`bin/installLocalPlugins.sh\`, \`bin/updatePlugins.sh\`) pick up the
setting automatically. Hardlinks are fast, save disk, and work on
every filesystem Etherpad supports.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:03:21 +01:00
John McLear
706dd4bc4c
docs(readme): lead with positioning, not feature description (#7526)
- Rewrite title + About to lead with what Etherpad is for
  (authorship, sovereignty, malleability) before features
- Rewrite Project Status to make the maintainer ask specific and
  to situate the project's 16-year track record
- Add new "Who uses Etherpad" section with categorical adopter
  profiles so institutional evaluators have proof points on-page

No code or behaviour changes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:55:26 +01:00
John McLear
32d5d90c9d
chore: remove dead root files (.travis.yml, .lgtm.yml, *.bat) (#7531)
These files are stale: there's no CI/tooling left that reads them.

- .travis.yml — Etherpad moved to GitHub Actions years ago. The
  workflows in .github/workflows/ are the source of truth.
- .lgtm.yml — LGTM was sunset by GitHub in late 2022.
- start.bat — README only documents the PowerShell installer for
  Windows now (irm .../installer.ps1 | iex), no docs or scripts
  reference start.bat.
- bin/installOnWindows.bat — same; not referenced by README, docs
  or workflows.

Also drop the .travis.yml line from the plugin layout in
doc/plugins.md and replace it with a pointer at .github/workflows/.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 17:29:24 +01:00
dependabot[bot]
252e198622
build(deps): bump oidc-provider from 9.8.0 to 9.8.1 (#7530)
Bumps [oidc-provider](https://github.com/panva/node-oidc-provider) from 9.8.0 to 9.8.1.
- [Release notes](https://github.com/panva/node-oidc-provider/releases)
- [Changelog](https://github.com/panva/node-oidc-provider/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/node-oidc-provider/compare/v9.8.0...v9.8.1)

---
updated-dependencies:
- dependency-name: oidc-provider
  dependency-version: 9.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 17:08:50 +01:00
John McLear
3ef99bb03a
fix: flush pending changesets immediately after reconnect (#7458)
* fix: flush pending changesets immediately after reconnect

After reconnecting, setUpSocket() did not call handleUserChanges(),
so any edits made while disconnected were not sent to the server until
the user made another change. This caused divergent pad state between
users.

Now calls handleUserChanges() after reconnect to immediately transmit
any pending local changesets.

Fixes #5108

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: defer handleUserChanges on connect to avoid editor init race

Calling handleUserChanges() synchronously in setUpSocket() caused
"Cannot read properties of null (reading 'changeset')" because the
editor isn't fully initialized on first connect. Deferred with
setTimeout(500ms) to allow initialization to complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add test for pending changeset flush after reconnect

Verifies that edits made while disconnected are transmitted to the
server immediately upon reconnection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flush pending changesets on actual reconnect, not just initial connect

setUpSocket() only runs during initialization. Move handleUserChanges()
to the reconnect code path so pending edits are flushed when the
connection is re-established.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flush pending changes when isPendingRevision clears after reconnect

The existing fix in setChannelState('CONNECTED') calls handleUserChanges(),
but at that point isPendingRevision is still true so changes are blocked.
The real trigger must be in setIsPendingRevision(): when it transitions
from true to false (after all CLIENT_RECONNECT messages are processed),
call handleUserChanges() to flush any locally-queued edits.

Also adds a targeted regression test that simulates the exact reconnect
state transitions and verifies pending edits reach the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: rewrite reconnect flush test for reliability

Replaced the fragile offline/online simulation with a direct test that
uses separate browser contexts. Simplified to a single test that
exercises the exact setIsPendingRevision(false) -> handleUserChanges()
codepath and verifies the flushed text is visible from another client.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: remove Playwright reconnect test (not feasible)

The reconnect test requires access to pad.collabClient internals which
are not exposed on window in the browser context. Playwright cannot
call setStateIdle/setIsPendingRevision/setChannelState. The backend
tests adequately cover the code fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: don't flush in setChannelState to avoid editor init race

Calling handleUserChanges() in setChannelState('CONNECTED') fires
synchronously on first connect before the editor is fully initialized,
breaking chat/user_name tests. The setIsPendingRevision(false) trigger
is sufficient for the reconnect path, and the existing
setTimeout(handleUserChanges, 500) in setUpSocket() already handles
the initial connect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove setTimeout flush in setUpSocket — rely on setIsPendingRevision trigger

The setTimeout(handleUserChanges, 500) in setUpSocket was a timing hack
that:
- Only fires on initial connect (setUpSocket is called once at the end
  of getCollabClient; reconnects go through pad.ts:248 which calls
  setChannelState('CONNECTED') directly, bypassing setUpSocket).
- Doesn't actually fix issue #5108 (the reconnect-flush bug). That's
  fixed deterministically by the wasPending && !value trigger in
  setIsPendingRevision, which fires whenever the server's CLIENT_RECONNECT
  message lands (both for noChanges and after replaying revisions).
- Introduced a 500ms race window on initial pad load.

The reconnect path now relies entirely on the deterministic event-based
trigger (setIsPendingRevision), with no timing assumptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:23:42 +01:00
John McLear
372ea3be94
fix: downgrade ERR_PNPM_IGNORED_BUILDS to a warning (#7527)
Plugin CI is still failing on ERR_PNPM_IGNORED_BUILDS even with the
build-script policy declared in both pnpm-workspace.yaml (#7523) and
package.json (#7525). pnpm's strict-dep-builds defaults to true in 10+,
so any transitive dep with an unrecognized postinstall fails the build.

For etherpad-lite — and especially for downstream plugin repos that
pull this codebase as their core install — that's a footgun: the moment
some new transitive ships a postinstall, every plugin's CI explodes.

Set strictDepBuilds: false in pnpm-workspace.yaml AND
strict-dep-builds=false in .npmrc as a defensive layer, so unknown
postinstalls become a warning instead of a hard failure. The
allow/ignore lists still control what actually runs.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:20:00 +01:00
John McLear
58c8e26eb1
fix: also declare pnpm build-script policy in package.json (#7525)
Some pnpm versions don't read onlyBuiltDependencies / ignoredBuiltDependencies
from pnpm-workspace.yaml — leaving CI on plugin repos to fail with
ERR_PNPM_IGNORED_BUILDS even after #7523 added the workspace.yaml entries.

Mirror the same configuration into package.json's "pnpm" field, which is
the older (and more widely supported) location. The two files are kept in
sync; whichever pnpm version reads the values picks them up from one or
the other.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:42:05 +01:00
John McLear
2a264eacc2
fix(tests): stabilize flaky list_wrap_indent frontend test (#7522)
The test was failing intermittently in CI with "Element is not attached
to the DOM" at selectText(). The long text typed by writeToPad triggers
DOM re-renders in Etherpad, and on slower CI runners the div locator
could detach between resolve and action.

Switch to keyboard-based selection (Ctrl+A) via the existing
selectAllText helper, which doesn't depend on a specific DOM element
staying attached. Verified stable across 10 repeated runs on chromium.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:24:58 +01:00
John McLear
9542052343
fix: explicitly ignore scarf build scripts to fix bin install CI (#7523)
* fix: explicitly ignore scarf build scripts to fix bin install CI

My earlier commit removed @scarf/scarf from onlyBuiltDependencies, but
pnpm 10.7+ requires every encountered build script to be either allowed
(onlyBuiltDependencies) or explicitly ignored (ignoredBuiltDependencies),
otherwise it errors with ERR_PNPM_IGNORED_BUILDS.

The Update Plugins workflow runs pnpm install inside ./bin, which pulls
in ep_etherpad-lite -> swagger-ui-dist -> @scarf/scarf as a transitive
dep. Add scarf to ignoredBuiltDependencies so pnpm silently skips its
install-time telemetry script instead of failing the install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(tests): serialize admin-spec tests to avoid shared-state races

Admin tests mutate global server state (install/uninstall plugins,
save settings, restart etherpad). Running them in parallel — both
across browsers (chromium + firefox) and with playwright's
fullyParallel mode — caused intermittent failures where one test's
install would leak into another test's assertions (e.g. seeing 3
installed plugins when expecting 2, or "ep_font_colormain" in the
hooks list when expecting only core hooks).

Two changes:
1. Drop firefox from test-admin — admin UI is browser-agnostic and
   running two browsers concurrently was the main race source
2. Add test.describe.configure({ mode: 'serial' }) to each admin
   spec file as a safety net in case the project list or worker
   count changes in the future

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(tests): use ep_set_title_on_pad for admin plugin install test

ep_font_color depends on ep_plugin_helpers, so installing it via the
admin UI actually installs two plugins (ep_font_color + ep_plugin_helpers),
making the installed plugins table show 3 rows (including ep_etherpad-lite)
instead of the 2 the test asserts.

Switch to ep_set_title_on_pad which has no transitive deps, so install
produces exactly one new row as the test expects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:27:40 +01:00
John McLear
2f632e5a87 fix: remove scarf telemetry and unused swc from approved build scripts
pnpm 10.7+ blocks dependency postinstall scripts unless explicitly
approved via onlyBuiltDependencies. Only esbuild genuinely needs its
postinstall (to download platform-specific native binaries).

Remove @scarf/scarf (install-time telemetry from swagger-ui-dist) and
@swc/core (not in the dependency tree) from the approved list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:06:00 +01:00
dependabot[bot]
36af9efaed
build(deps): bump ueberdb2 from 5.0.34 to 5.0.45 (#7520)
Bumps [ueberdb2](https://github.com/ether/ueberDB) from 5.0.34 to 5.0.45.
- [Changelog](https://github.com/ether/ueberDB/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ether/ueberDB/compare/v5.0.34...v5.0.45)

---
updated-dependencies:
- dependency-name: ueberdb2
  dependency-version: 5.0.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 21:19:08 +02:00
dependabot[bot]
269ea7189a
build(deps-dev): bump the dev-dependencies group across 1 directory with 5 updates (#7521)
Bumps the dev-dependencies group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.58.1` | `8.58.2` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.58.1` | `8.58.2` |
| [i18next](https://github.com/i18next/i18next) | `26.0.4` | `26.0.5` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.2` | `17.0.3` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.14.0` | `7.14.1` |



Updates `@typescript-eslint/eslint-plugin` from 8.58.1 to 8.58.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.2/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.58.1 to 8.58.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.2/packages/parser)

Updates `i18next` from 26.0.4 to 26.0.5
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.0.4...v26.0.5)

Updates `react-i18next` from 17.0.2 to 17.0.3
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.2...v17.0.3)

Updates `react-router-dom` from 7.14.0 to 7.14.1
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.14.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.58.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.58.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: i18next
  dependency-version: 26.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: react-i18next
  dependency-version: 17.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: react-router-dom
  dependency-version: 7.14.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 21:18:38 +02:00
dependabot[bot]
7a535d7c7e
build(deps): bump resolve from 1.22.11 to 1.22.12 (#7504)
Bumps [resolve](https://github.com/browserify/resolve) from 1.22.11 to 1.22.12.
- [Commits](https://github.com/browserify/resolve/compare/v1.22.11...v1.22.12)

---
updated-dependencies:
- dependency-name: resolve
  dependency-version: 1.22.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 22:10:15 +02:00
dependabot[bot]
6ba51d66db
build(deps): bump actions/upload-pages-artifact from 4 to 5 (#7517)
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 22:10:07 +02:00
John McLear
f4dde52d54
Bump ueberdb2 to ^5.0.42 — production dependencies for db drivers (#7516)
* Bump ueberdb2 to ^5.0.42 — adds production dependencies for db drivers

5.0.42 moves core database drivers (dirty-ts, rusty-store-kv) to
production dependencies so they're installed in production/Docker.
Optional drivers (cassandra, mongodb, etc.) are now optional
peerDependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update to ueberdb2 ^5.0.43 — fixes Node engine requirement

5.0.43 relaxes the Node engine from >=22.22.0 to >=18.0.0,
matching etherpad-lite's supported Node versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:59:51 +01:00
John McLear
649f960341
Add docs for aceRegisterLineAttributes hook (#7512)
* Add docs for aceRegisterLineAttributes hook

Documents the new hook in both .md and .adoc client-side hook references.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix: correct source file path from .js to .ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:48:33 +01:00
John McLear
5e0602c5ee
Fix Docker build: use production-only workspace config (#7514)
The pnpm-workspace.yaml references admin, doc, and ui packages that
aren't copied into the production Docker image. This caused pnpm
install warnings and incomplete dependency resolution, which broke
ueberdb2's modular build (missing bin symlinks, broken module paths).

Fix: overwrite pnpm-workspace.yaml in the production stage with a
minimal version that only lists the packages present in the image
(src and bin).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:42:03 +01:00
John McLear
df1377982f
Bump ueberdb2 to ^5.0.41 and update lockfile (#7513)
5.0.41 includes the rolldown runtime fix (ether/ueberDB#925) needed
for the lazy-loaded database drivers to work at runtime.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:36:37 +01:00
John McLear
2622fedf9b
Bump ueberdb2 to ^5.0.40 — fixes Docker CI crash (#7511)
ueberdb2 5.0.40 lazy-loads database drivers so only the configured
backend's dependencies need to be installed. Fixes the Docker production
build crash: "Cannot find module 'cassandra-driver'"

See ether/ueberDB#924

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:07:05 +01:00
John McLear
55f1124ba7
Add aceRegisterLineAttributes hook for line attribute preservation on Enter (#7509)
When pressing Enter in a line with attributes like heading or align,
the attributes were lost on the new line. Lists had special-case code
in doReturnKey() but all other line attributes were ignored.

This adds a new client hook aceRegisterLineAttributes that plugins can
use to declare which attributes should be preserved when a line is
split by Enter:

- Enter at middle/end of line: attribute is copied to the new line
- Enter at start of line (col 0): attribute moves down with the text,
  the now-empty line above gets the attribute removed

Plugins register by returning attribute names from the hook:
  exports.aceRegisterLineAttributes = () => ['heading'];

Fixes ether/ep_headings2#7

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:25:45 +01:00
dependabot[bot]
555ccbe7b3
build(deps): bump pnpm/action-setup from 5 to 6 (#7502)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v5...v6)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:26:39 +02:00
dependabot[bot]
0eb444a063
build(deps): bump lru-cache from 11.3.2 to 11.3.5 (#7505)
Bumps [lru-cache](https://github.com/isaacs/node-lru-cache) from 11.3.2 to 11.3.5.
- [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-lru-cache/compare/v11.3.2...v11.3.5)

---
updated-dependencies:
- dependency-name: lru-cache
  dependency-version: 11.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:26:31 +02:00
dependabot[bot]
bc43105a32
build(deps): bump ejs from 5.0.1 to 5.0.2 (#7506)
Bumps [ejs](https://github.com/mde/ejs) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/RELEASE_NOTES_v5.md)
- [Commits](https://github.com/mde/ejs/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: ejs
  dependency-version: 5.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:26:17 +02:00
dependabot[bot]
28db7a3121
build(deps): bump axios from 1.14.0 to 1.15.0 (#7498)
Bumps [axios](https://github.com/axios/axios) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:26:07 +02:00
dependabot[bot]
18c735c9a5
build(deps-dev): bump sinon in the dev-dependencies group (#7503)
Bumps the dev-dependencies group with 1 update: [sinon](https://github.com/sinonjs/sinon).


Updates `sinon` from 21.1.0 to 21.1.2
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v21.1.0...v21.1.2)

---
updated-dependencies:
- dependency-name: sinon
  dependency-version: 21.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:25:52 +02:00
dependabot[bot]
a908c1a5b7
build(deps): bump softprops/action-gh-release from 2 to 3 (#7501)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:25:37 +02:00
dependabot[bot]
5df63551a7
build(deps): bump ueberdb2 from 5.0.33 to 5.0.34 (#7507)
Bumps [ueberdb2](https://github.com/ether/ueberDB) from 5.0.33 to 5.0.34.
- [Changelog](https://github.com/ether/ueberDB/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ether/ueberDB/compare/v5.0.33...v5.0.34)

---
updated-dependencies:
- dependency-name: ueberdb2
  dependency-version: 5.0.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:25:23 +02:00
Stefan Müller
8f16851373
Update versions in markdown files (#7465)
* Update versions in markdown files

* Update also adoc file
2026-04-12 12:27:03 +02:00
dependabot[bot]
b6df192fec
build(deps-dev): bump the dev-dependencies group across 1 directory with 10 updates (#7500)
Bumps the dev-dependencies group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@types/formidable](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/formidable) | `3.5.0` | `3.5.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.2` | `25.6.0` |
| [sinon](https://github.com/sinonjs/sinon) | `21.0.3` | `21.1.0` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.3` | `4.1.4` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.58.0` | `8.58.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.58.0` | `8.58.1` |
| [i18next](https://github.com/i18next/i18next) | `26.0.3` | `26.0.4` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.7.0` | `1.8.0` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.4` | `19.2.5` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.4` | `19.2.5` |



Updates `@types/formidable` from 3.5.0 to 3.5.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/formidable)

Updates `@types/node` from 25.5.2 to 25.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `sinon` from 21.0.3 to 21.1.0
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v21.0.3...v21.1.0)

Updates `vitest` from 4.1.3 to 4.1.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.4/packages/vitest)

Updates `@typescript-eslint/eslint-plugin` from 8.58.0 to 8.58.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.58.0 to 8.58.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/parser)

Updates `i18next` from 26.0.3 to 26.0.4
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.0.3...v26.0.4)

Updates `lucide-react` from 1.7.0 to 1.8.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.8.0/packages/lucide-react)

Updates `react` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react)

Updates `react-dom` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom)

---
updated-dependencies:
- dependency-name: "@types/formidable"
  dependency-version: 3.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: sinon
  dependency-version: 21.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: vitest
  dependency-version: 4.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: i18next
  dependency-version: 26.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: lucide-react
  dependency-version: 1.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: react
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: react-dom
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 11:56:46 +02:00
dependabot[bot]
2761c4ae79
build(deps): bump ueberdb2 from 5.0.23 to 5.0.33 (#7497)
Bumps [ueberdb2](https://github.com/ether/ueberDB) from 5.0.23 to 5.0.33.
- [Changelog](https://github.com/ether/ueberDB/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ether/ueberDB/compare/v5.0.23...v5.0.33)

---
updated-dependencies:
- dependency-name: ueberdb2
  dependency-version: 5.0.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 19:43:26 +01:00
John McLear
aee356ab76
fix: use atomic git push in plugin npmpublish workflow (#7494)
The plugin publish workflow ran `git push --follow-tags` after `pnpm
version patch`. `--follow-tags` is non-atomic per ref: if a concurrent
publish run won the race, the branch fast-forward would be rejected
but the tag push would still land — leaving a dangling `vN+1` tag with
no matching version-bump commit on the branch. Every subsequent push
would then fail forever with `npm error fatal: tag 'vN+1' already
exists`, because `pnpm version patch` would re-derive the same tag
name from the unchanged `package.json`.

On 2026-04-08, a single churn day (badge fixes + Dependabot merges
firing back-to-back) put ~46 plugins into this state simultaneously.
Recovery required hand-bumping `package.json` past the dangling tag
on every affected repo, twice (a second wave appeared after the first
sweep finished, racing the next wave of publishes).

Fix: use `git push --atomic origin <branch> <tag>` so the branch
update and the tag update succeed or fail as a single server-side
transaction. A rejected branch push now also rejects the tag push,
the run aborts cleanly, and the next workflow tick can retry against
the up-to-date refs without leaving any orphaned tags.

Also derive the new tag name from `package.json` after the bump
(rather than parsing pnpm version's stdout, which has historically
varied) and pass it explicitly into the push.

Adds a backend regression test that asserts the workflow file uses
`--atomic`, does not contain a literal `git push --follow-tags`
command (ignoring the historical comment), and includes both the
branch ref and the freshly-bumped tag in the atomic push. The test
gates against accidental reverts.

This file is the source of truth that `bin/plugins/checkPlugin.ts`
propagates into every `ether/ep_*` plugin's `.github/workflows/`, so
the next `update-plugins` cron tick will roll the fix out across all
plugins automatically.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:38:41 +01:00
John McLear
b57b25a4d7
fix: setup-trusted-publishers.sh works with real npm trust CLI (#7491)
* fix: setup-trusted-publishers.sh works with real npm trust CLI

Two issues found when running the script for the first time after #7490:

1. `npm trust github --file` wants ONLY the workflow filename basename
   (e.g. `test-and-release.yml`), not the full
   `.github/workflows/test-and-release.yml` path. npm errors out with
   "GitHub Actions workflow must be just a file not a path" otherwise.
   Constants updated.

2. `npm trust github` requires 2FA on accounts that have it enabled,
   and there is no way to disable that requirement. Add a `--otp <code>`
   pass-through flag and forward it to every call so a maintainer can
   batch-process multiple packages within a single TOTP window.
   Documented the limitation in the script header.

Also reword the call site so the npm command line is built without
shell-string round-tripping (passing $CMD through `$( $CMD )` was
unrelated to this bug but was bad practice).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: setup-trusted-publishers.sh recognizes 409 as already-configured

When --skip-existing is set, treat HTTP 409 Conflict from
POST /-/package/<name>/trust as 'already configured' so re-runs of
the bulk script don't fail on packages that were configured in a
previous run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: cover setup-trusted-publishers.sh, harden against set -e, document --otp

Addresses qodo review on #7491:

- Add backend regression test that shims `npm` on PATH and asserts
  `--file` is given the workflow basename (never a path), `--otp` is
  forwarded to every `npm trust github` call when supplied, and the
  loop survives a non-zero exit so `--skip-existing` can absorb 409
  Conflict responses from the registry.
- Wrap the `npm trust github` invocation in `set +e` / `set -e`. The
  `if configure_one` already shields the function from errexit in
  practice, but a future refactor moving the call site out of an `if`
  would silently reintroduce the bug — the explicit shim makes intent
  obvious and survives such refactors.
- Document `--otp` and the 2FA / TOTP-expiry workflow in
  doc/npm-trusted-publishing.md so maintainers don't follow the docs
  and hit EOTP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:56:13 +01:00
John McLear
7b6109e28d
ci: auto-merge clean Dependabot PRs on plugin repos in update-plugins cron (#7493)
The daily update-plugins workflow already syncs boilerplate (workflows,
dependabot.yml, etc.) into every ether/ep_* repo via checkPlugin, but it
never closes the loop on the Dependabot PRs that config produces. With
plugin repos having no per-repo auto-merge wiring, those PRs sit green
indefinitely (e.g. ether/ep_loading_message#77).

Add a final step that, after the per-plugin updates run, walks every
ep_* repo and squash-merges any open Dependabot PR whose mergeStateStatus
is CLEAN — i.e. no conflicts, branch up to date, all required checks
green. Anything else (DIRTY, BLOCKED, BEHIND, UNSTABLE, …) is left alone
for a human.

No semver gating: trust each plugin's own CI to fail on a breaking
major bump rather than pre-filtering by version delta.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:53:42 +01:00
John McLear
31e0a61126
fix: capture head revision atomically with atext to prevent mismatched apply (#7480)
* fix: capture head revision atomically with atext to prevent mismatched apply

When constructing CLIENT_VARS, pad.atext was captured at one point but
pad.getHeadRevisionNumber() was called later. If concurrent edits
advanced the revision between these two reads, the client received
initialAttributedText from rev N but rev=N+3, causing "mismatched apply"
errors when the next changeset arrived (expecting rev N+3 text).

Now captures headRev at the same time as atext and uses the captured
value consistently in CLIENT_VARS and sessionInfo.

Fixes #4040

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flush missed revisions after socket joins pad room

During handleClientReady(), the server awaits the clientVars hook before
socket.join(). Any revisions appended during that await window are
broadcast to existing room members but the connecting socket misses them.
Call updatePadClients(pad) after joining to flush any such revisions.

Also adds a regression test that injects a slow clientVars hook and
verifies the connecting client receives catch-up changesets for edits
that occurred during the hook await window.

Fixes #4040

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: fix race condition in clientVars hook test

Listen for messages during handshake to avoid missing NEW_CHANGES that
arrive before the explicit waitForSocketEvent listener is attached.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: initialize sessionInfo.time before catch-up updatePadClients

The catch-up updatePadClients() call introduced in this PR could send
NEW_CHANGES with timeDelta=NaN because sessionInfo.time was never set
for new sessions. NaN poisons the client-side broadcast/timeslider
currentTime tracking.

Initialize sessionInfo.time to the timestamp of the snapshot revision
before the catch-up flush, with a fallback to Date.now() if the
revision date is unavailable.

Also strengthens the regression tests:
- Validate that initialAttributedText matches the pad AText at the
  EXACT advertised rev (not just the latest pad text), using
  pad.getInternalRevisionAText(rev).
- Add a load test that hammers the pad with concurrent edits while
  multiple clients connect, asserting CLIENT_VARS consistency under
  the exact race condition the fix is targeting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: replace open-ended load loop with bounded mid-handshake edit

The previous load test ran 'while (!stopLoad) await pad.setText(...)'
in the background while the test connected clients. This saturated
ueberDB's write queue and on shutdown the queued writes never drained,
hanging the mocha process for the full 6h GitHub Actions job timeout.

Replace it with a bounded approach: a clientVars hook lands 3 edits
mid-handshake (deterministic, no background loop, no shutdown hang).
Still exercises the exact race the fix targets — an edit advancing
the rev after the atext snapshot but before CLIENT_VARS is sent —
and asserts AText / rev consistency via getInternalRevisionAText.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: address remaining Qodo concerns on PR #7480

Addresses Qodo review items 1, 2, 5 from
https://github.com/ether/etherpad-lite/issues/comments/4194702740 :

- Concern 1 (no loadTesting reproduction test): the suite now toggles
  settings.loadTest = true in before(), restores in after(). The
  middle test also pre-populates the pad with 20 revisions before
  connecting so we genuinely exercise a busy/loaded pad rather than a
  fresh one.

- Concern 2 (no CLIENT_VARS / NEW_CHANGES delay test): the slow
  clientVars hook in the middle test now has explicit setTimeout
  delays before AND after the mid-handshake edits, so the race window
  between atext snapshot and CLIENT_VARS send is observably wide
  rather than relying on async scheduling alone. The test also
  collects post-handshake messages and asserts a NEW_CHANGES catch-up
  arrives when the pad advanced past the advertised rev.

- Concern 5 (test doesn't validate rev): both rev-consistency tests
  use pad.getInternalRevisionAText(advertisedRev) and assert text and
  attribs match, not just `pad.text() === clientVars.text`.

Concerns 3 (connect can miss revisions) and 4 (NaN timeDelta) were
already addressed in earlier commits on this branch via the catch-up
updatePadClients() call and the sessionInfo.time initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:30:08 +01:00
John McLear
7c3837891b
feat: migrate npm publish to OIDC trusted publishing (#7401) (#7490)
* feat: migrate npm publish to OIDC trusted publishing (#7401)

Replaces NPM_TOKEN-based publishing with npm Trusted Publishing over
OIDC for both etherpad-lite core and the shared plugin publish
template. Tokens no longer expire every 90 days; each publish
authenticates via a short-lived OIDC token issued to the GitHub
Actions runner.

Changes:

- bin/plugins/lib/npmpublish.yml: the reusable workflow propagated to
  every ether/ep_* plugin via the update-plugins cron. Now bumps Node
  to 22, upgrades npm to >=11.5.1, declares id-token: write, drops
  NODE_AUTH_TOKEN, and calls `npm publish --provenance --access public`
  directly (not via pnpm/gnpm wrappers, which obscure the npm CLI
  version requirement).

- bin/plugins/lib/test-and-release.yml: the parent workflow that calls
  npmpublish.yml as a reusable workflow. Top-level and release-job
  permissions now grant id-token: write so the OIDC token can flow
  into the called workflow.

- .github/workflows/releaseEtherpad.yml: core's own publish workflow
  for the ep_etherpad package. Same OIDC migration; keeps the gnpm
  install + rename steps but switches the final publish to npm.

- doc/npm-trusted-publishing.md: explains how trusted publishing
  works, the one-time per-package setup that has to happen on
  npmjs.com, requirements (Node 22.14+, npm 11.5.1+, cloud runners),
  and common errors.

The next update-plugins cron run will propagate the new template to
every plugin. Once that lands and the trusted publisher is configured
on npmjs.com per package, the NPM_TOKEN secret can be removed.

Closes #7401

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add bin/setup-trusted-publishers.sh for bulk OIDC config (#7401)

Adds a script that automates the per-package trusted-publisher setup
that previously had to be done by clicking through npmjs.com once for
each of the 80+ ep_* plugins. Uses the new `npm trust github` CLI
(npm >= 11.5.1) so the whole org can be configured in one shot:

  npm login
  bin/setup-trusted-publishers.sh

The script:
- Discovers every non-archived ether/ep_* repo via `gh repo list`
- Maps ep_etherpad to the etherpad-lite repo / releaseEtherpad.yml,
  and every plugin to its same-named repo / test-and-release.yml
- Runs `npm trust github <pkg> --repository <org>/<repo> --file
  <workflow> --yes` for each package
- Supports --dry-run, --packages <comma list>, and --skip-existing
- Verifies npm >= 11.5.1 and that the user is logged in before doing
  anything destructive

Doc updated to feature the script as the recommended setup path,
with manual web-UI steps kept as a fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: don't bump CI Node version to 22 for OIDC

npm 11.5.1 (the version that ships trusted publishing) actually
requires '^20.17.0 || >=22.9.0', not Node 22.14+. The npm docs
recommend Node 22 but only because that's what bundles a recent
enough npm — installing 'npm@latest' on top of Node 20.17+ works
just as well.

The repo already requires Node >= 20.0.0 in engines.node and the
setup-node@v6 'version: 20' input resolves to the latest 20.x
(currently 20.20+), which satisfies npm 11's range. Revert the CI
publish workflows from node-version: 22 back to 20 so this PR does
not raise the Node bar at all.

Doc updated to explain the actual constraint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:38:23 +01:00
dependabot[bot]
ef1a8d93ed
build(deps): bump jsdom from 29.0.1 to 29.0.2 (#7489)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.1 to 29.0.2.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.0.1...v29.0.2)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 17:26:52 +01:00
dependabot[bot]
65c3fd3a00
build(deps): bump lru-cache from 11.3.0 to 11.3.2 (#7488)
Bumps [lru-cache](https://github.com/isaacs/node-lru-cache) from 11.3.0 to 11.3.2.
- [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-lru-cache/compare/v11.3.0...v11.3.2)

---
updated-dependencies:
- dependency-name: lru-cache
  dependency-version: 11.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 17:13:14 +01:00
dependabot[bot]
b00d9d680a
build(deps): bump oidc-provider from 9.7.1 to 9.8.0 (#7487)
Bumps [oidc-provider](https://github.com/panva/node-oidc-provider) from 9.7.1 to 9.8.0.
- [Release notes](https://github.com/panva/node-oidc-provider/releases)
- [Changelog](https://github.com/panva/node-oidc-provider/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/node-oidc-provider/compare/v9.7.1...v9.8.0)

---
updated-dependencies:
- dependency-name: oidc-provider
  dependency-version: 9.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 17:13:12 +01:00
dependabot[bot]
d6ee322fa5
build(deps-dev): bump vitest in the dev-dependencies group (#7486)
Bumps the dev-dependencies group with 1 update: [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `vitest` from 4.1.2 to 4.1.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.3/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 17:13:08 +01:00
John McLear
301ae4df2c
feat: add one-line installer script (#7466) (#7485)
* feat: add one-line installer script (#7466)

Adds bin/installer.sh, a small POSIX shell script that:
- Verifies prerequisites (git, Node.js >= 18)
- Installs pnpm globally if missing (with sudo fallback)
- Clones etherpad-lite (configurable branch / dir)
- Runs `pnpm i` and `pnpm run build:etherpad`
- Optionally starts Etherpad if ETHERPAD_RUN=1

Users can now install Etherpad with a single command:

  curl -fsSL https://raw.githubusercontent.com/ether/etherpad-lite/master/bin/installer.sh | sh

README updated to feature the one-liner above the existing
Docker-Compose / manual install instructions.

Closes #7466

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add installer-test workflow + Windows PowerShell installer

- bin/installer.ps1: PowerShell port of installer.sh so the one-liner
  also works on Windows via 'irm ... | iex'.
- .github/workflows/installer-test.yml: end-to-end CI that runs each
  installer against the PR's own commit (via ETHERPAD_REPO/BRANCH env
  vars), verifies clone + node_modules + admin SPA artifacts, and
  smoke-tests by starting Etherpad and curling /api. Runs on
  ubuntu-latest, macos-latest, and windows-latest. Includes a
  shellcheck job for installer.sh.
- README: feature the Windows one-liner alongside the POSIX one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: fix windows smoke test - wrap pnpm in cmd /c

Start-Process can't run pnpm.cmd directly ("not a valid Win32 application").
Wrap it via cmd.exe /c instead, and bump the wait window to 90s for slower
Windows runners. Also dump stderr alongside stdout when the smoke test
fails for easier debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Qodo review on installer (#7485)

Two correctness issues caught by Qodo:

1. Node version mismatch: installer required Node >= 18, but the repo's
   engines.node is >= 20. Bump REQUIRED_NODE_MAJOR to 20 in both shell
   and PowerShell installers, and update the README's quick-install
   prerequisite and Requirements section to match.

2. Branch ignored for existing checkouts: when ETHERPAD_DIR already
   existed, the script ran 'git pull --ff-only' on whatever branch
   happened to be checked out, ignoring ETHERPAD_BRANCH and never
   verifying ETHERPAD_REPO. The existing-dir path now:
   - validates the remote URL matches ETHERPAD_REPO
   - refuses to clobber uncommitted changes (excluding pnpm-lock.yaml,
     which pnpm i rewrites during install)
   - fetches with --tags --prune
   - checks out ETHERPAD_BRANCH as a branch or detaches at it as a tag
   - prints the resulting commit short SHA for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:10:22 +01:00