Commit graph

9927 commits

Author SHA1 Message Date
John McLear
a59b310dbf
deps: pin ueberdb2 to 6.1.9 (fixes standalone-server startup exit) + run deb smoke on PRs (#7969)
ueberdb2 6.1.10 rewrote the cache/buffer layer (CacheAndBufferLayer.ts),
replacing the constructor's always-on, *referenced* `setInterval` flush timer
with a lazily-armed `setTimeout` that is `.unref()`'d and only created when
there are dirty keys. On a fresh, empty dirty DB there are no dirty keys, so no
flush timer is ever armed and ueberdb2 no longer anchors Node's event loop. In
the packaged (.deb/systemd) production boot this exposes a startup window where
the loop has no referenced handle and the process exits cleanly (code 0) before
`server.listen()` binds the port — so the server never serves /health.

Symptom on develop: the Debian-package amd64 smoke test failed on three
consecutive pushes (#7966 ueberdb2 6.1.9->6.1.12, then #7965, #7967), with the
service logging the version banner then "Deactivated successfully" and the
health check on :9001 never connecting. Backend/Docker/downstream-smoke stayed
green because they keep the loop alive by other means; only the bare
fresh-empty-dirty-DB packaged boot hits the gap. ueberdb2 6.1.12 is the latest
published release, so there is no fixed version to roll forward to yet — pin
back to the last green release (6.1.9) on both src/ and bin/.

Also add a `pull_request` trigger to the Debian-package workflow (scoped to the
same production-footprint paths). The smoke step is the only check that catches
this "boots then exits before binding" class of regression, but it previously
ran only on push to develop — i.e. *after* merge — which is exactly why the
Dependabot bump turned develop red instead of being blocked at PR time. The
release/apt-publish jobs are tag-guarded, so PRs run the build+smoke job only.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:39:37 +01:00
forkivan
0c0f5a8ec4
PadManager: reject unreachable '.' and '..' pad ids (#7962)
* PadManager: reject unreachable '.' and '..' pad ids

isValidPadId accepted pad ids that consist only of URL dot-segments
('.' and '..'). Per the WHATWG URL standard a browser normalises
"/p/." to "/p/" and "/p/.." to "/", so such a pad can never be opened
or exported: the request arrives at "/p/" and Etherpad answers
"Cannot GET /p/". The pad is created in the database but is forever
unreachable.

Reject these ids in isValidPadId so the broken pads can no longer be
created, and add a regression test that fails without the fix.

* adminsettings: allow deleting legacy '.'/'..' pads

The isValidPadId tightening makes getPad() reject the pad ids '.' and
'..', which also blocks their deletion: a pad with such an id created
before the change still exists (doesPadExists is true), so the admin
deletePad handler takes the "healthy" branch where getPad() now throws.
The outer catch swallowed that error without emitting a terminal
results:deletePad, leaving an undeletable orphan in the admin UI.

Fall back to the existing raw key purge when getPad() throws, so these
pads can still be removed. Adds a regression test.

* tests: run the isValidPadId regression under the mocha suite

The original unit test lived in the vitest backend-new suite, but PadManager
loads DB, Pad and customError with CommonJS require() at import time. Under
vitest those require() calls are resolved by Node natively and fail on the .ts
sources ("Cannot find module '../utils/customError'"); vi.mock could not
intercept them, so the suite errored before any test ran. It also used a
top-level `await import`, which tripped tsc TS1378 under the project tsconfig.

Move the test to the mocha backend suite, which runs with --import=tsx and
resolves the .ts requires natively, so PadManager can be required directly with
no mocking. isValidPadId is a pure function and DB only connects lazily in
DB.init(), so loading the module has no side effects and no database is needed.
2026-06-16 11:13:02 +01:00
John McLear
099de84cd1
deps: resolve open Dependabot security alerts (#7967)
* deps: resolve open Dependabot security alerts

Bump transitive dependencies flagged by Dependabot via pnpm-workspace
overrides. Refreshes stale override floors that the advisory ranges have
since grown past, and adds overrides for newly-flagged packages:

- form-data  -> >=4.0.6  (GHSA-hmw2-7cc7-3qxx, high)
- ws         -> >=8.21.0 (GHSA-96hv-2xvq-fx4p / GHSA-58qx-3vcg-4xpx, high/med)
- esbuild    -> >=0.28.1 (GHSA-gv7w-rqvm-qjhr / GHSA-g7r4-m6w7-qqqr, high/low)
- basic-ftp  -> >=5.3.1  (GHSA-rpmf-866q-6p89, high) [stale floor: 5.3.0 still vuln]
- tar        -> >=7.5.16 (GHSA-vmf3-w455-68vh, med)  [stale floor: 7.5.11]
- js-yaml    -> >=4.2.0  (GHSA-h67p-54hq-rp68, med)  [stale floor: 4.1.1 now vuln]
- qs         -> >=6.15.2 (GHSA-q8mj-m7cp-5q26, med)  [stale floor: 6.14.2 still vuln]
- ip-address -> >=10.1.1 (GHSA-v2v4-37r5-5v8g, med)
- @babel/core-> >=7.29.6 (GHSA-4x5r-pxfx-6jf8, low)

ts-check, backend test-utils, and the vite/esbuild production build all
pass locally on Node 24.

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

* deps: cap basic-ftp override to 5.x to avoid major bump

Qodo flagged the open-ended `basic-ftp@<5.3.1: '>=5.3.1'` override as
resolving to 6.0.1 (a surprise major) on the runtime plugin-install path
(live-plugin-manager -> proxy-agent -> get-uri -> basic-ftp). The CVE fix
(5.3.1) exists on the 5.x line, so bound the override to '>=5.3.1 <6.0.0'
for the minimal, lowest-risk patch. Now resolves to basic-ftp@5.3.1.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:32:06 +01:00
dependabot[bot]
8a4e1feed8
build(deps): bump awalsh128/cache-apt-pkgs-action from 1.6.0 to 1.6.1 (#7963)
Bumps [awalsh128/cache-apt-pkgs-action](https://github.com/awalsh128/cache-apt-pkgs-action) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/awalsh128/cache-apt-pkgs-action/releases)
- [Commits](https://github.com/awalsh128/cache-apt-pkgs-action/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: awalsh128/cache-apt-pkgs-action
  dependency-version: 1.6.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-06-15 19:08:07 +01:00
dependabot[bot]
2fdf202089
build(deps): bump nodemailer from 8.0.11 to 9.0.0 (#7965)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.11 to 9.0.0.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.11...v9.0.0)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 9.0.0
  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-06-15 19:08:00 +01:00
dependabot[bot]
abdfa03f84
build(deps): bump ueberdb2 from 6.1.9 to 6.1.12 (#7966)
Bumps [ueberdb2](https://github.com/ether/ueberDB) from 6.1.9 to 6.1.12.
- [Changelog](https://github.com/ether/ueberDB/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ether/ueberDB/compare/v6.1.9...v6.1.12)

---
updated-dependencies:
- dependency-name: ueberdb2
  dependency-version: 6.1.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-06-15 19:07:47 +01:00
dependabot[bot]
c8e3248bcd
build(deps-dev): bump the dev-dependencies group with 6 updates (#7964)
Bumps the dev-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.0` |
| [eslint](https://github.com/eslint/eslint) | `10.4.1` | `10.5.0` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.8` | `4.1.9` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.2` | `0.5.3` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.78.0` | `7.79.0` |
| [oxc-minify](https://github.com/oxc-project/oxc/tree/HEAD/napi/minify) | `0.135.0` | `0.136.0` |


Updates `@playwright/test` from 1.60.0 to 1.61.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.0)

Updates `eslint` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.4.1...v10.5.0)

Updates `vitest` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/HEAD/packages/vitest)

Updates `eslint-plugin-react-refresh` from 0.5.2 to 0.5.3
- [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases)
- [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.5.2...v0.5.3)

Updates `react-hook-form` from 7.78.0 to 7.79.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.78.0...v7.79.0)

Updates `oxc-minify` from 0.135.0 to 0.136.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/napi/minify/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/crates_v0.136.0/napi/minify)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.61.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: eslint
  dependency-version: 10.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: vitest
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: eslint-plugin-react-refresh
  dependency-version: 0.5.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.79.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxc-minify
  dependency-version: 0.136.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-06-15 16:38:06 +01:00
translatewiki.net
4371af77c2
Localisation updates from https://translatewiki.net. 2026-06-15 14:03:51 +02:00
John McLear
2ba72de1de
feat(pad): suppress deletion token for durable identities + relabel recovery action (#7926) (#7930)
* feat(pad): suppress deletion token for durable identities; relabel recovery action (#7926)

Builds on the allowPadDeletionByAllUsers suppression with the rest of the
ideas discussed on the issue.

Server (handleClientReady):
- A creator's deletion token is now also withheld when they have a *durable*
  identity: authenticated (req.session.user with a username) AND the deployment
  pins that identity to a stable authorID via a getAuthorId hook. Only then does
  the creator path (author === revision-0 author) survive a cookie clear or a
  different device, making the recovery token redundant.
- This deliberately tightens the previous `requireAuthentication => always
  suppress` rule: without a getAuthorId hook the authorID still comes from the
  per-browser token cookie, so an authenticated user on a second device is NOT
  the creator. Withholding the token there would strand them, so they now keep
  getting one. SSO deployments using the documented getAuthorId pattern get the
  clean no-modal experience.
- New `canDeleteWithoutToken` clientVar (allowPadDeletionByAllUsers OR durable
  identity) drives the client label.

Client (pad_editor.ts):
- When canDeleteWithoutToken, the recovery-token disclosure summary is labelled
  plainly "Delete Pad" (reusing the already-translated pad.settings.deletePad
  key) instead of the jargon "Delete with token".

Tests (socketio.ts): anonymous -> token; allowPadDeletionByAllUsers -> none;
authenticated without a getAuthorId hook -> token; authenticated with one ->
none. Verified in a browser for both label/modal outcomes.

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

* fix(pad): render recovery disclosure for all sessions; hide it (not relabel) when no token is needed

Addresses Qodo review on #7930:

1. Token UI fully suppressed when not needed (was: only the summary relabelled).
   When canDeleteWithoutToken (allowPadDeletionByAllUsers or a durable identity),
   pad_editor.ts now hides the whole #delete-pad-with-token disclosure — label,
   token field and submit — so no deletion-token wording remains. With the plain
   "Delete Pad" button present this is also the cleaner UX.

2. Recovery disclosure no longer gated on !requireAuthentication. Because a token
   can now be issued under requireAuthentication when the deployment lacks a
   durable getAuthorId mapping, the template must render the recovery form there
   too — otherwise an authenticated creator gets a token with no UI to enter it
   on another device. It is rendered hidden by default and shown by the client
   only when canDeleteWithoutToken is false.

3. API.createPad aligned: also returns null deletionToken under
   allowPadDeletionByAllUsers, matching the socket/UI path.

Tests: deletePad.ts gains a createPad-under-allowPadDeletionByAllUsers case.
Verified live in Chromium: allowAll -> disclosure hidden, no modal; default
anonymous -> disclosure visible, modal shown.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:47:11 +01:00
dependabot[bot]
6a5ad2d90d
build(deps-dev): bump lucide-react in the dev-dependencies group (#7951)
Bumps the dev-dependencies group with 1 update: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


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

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-06-13 14:34:55 +01:00
John McLear
f79cabe74c
Env-var overrides for offline/air-gapped installs (update check, plugin catalog, updater) (#7917)
* feat(settings): env-var overrides for update-check/plugin-catalog/updater (offline installs)

Air-gapped and firewalled deployments could not disable Etherpad's outbound
calls (the hourly version check, the admin plugin catalogue, and the
self-updater) without editing settings.json inside the container image — the
shipped settings.json.docker hardcoded updates.tier and omitted the privacy
block entirely, so there was no env-var to flip.

Wire the relevant keys through the existing ${ENV:default} substitution in both
settings.json.docker and settings.json.template:

  - PRIVACY_UPDATE_CHECK            (privacy.updateCheck, default true)
  - PRIVACY_PLUGIN_CATALOG          (privacy.pluginCatalog, default true)
  - UPDATES_TIER                    (updates.tier, default notify; "off" = no calls)
  - UPDATES_SOURCE / UPDATES_CHANNEL / UPDATES_CHECK_INTERVAL_HOURS /
    UPDATES_GITHUB_REPO / UPDATES_REQUIRE_ADMIN_FOR_STATUS (docker)
  - UPDATE_SERVER                   (updateServer endpoint)

Document the full set in doc/docker.md (new "Updates & privacy" section) and
cross-link from doc/admin/updates.md. Add backend regression tests that parse
the shipped settings.json.docker and settings.json.template and assert the
overrides apply with correct boolean/numeric coercion, so a future edit that
drops the ${ENV} placeholders fails loudly.

Addresses #7911 (item 2). No code changes — config + docs + tests only.

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

* docs: address Qodo review — point to PRIVACY.md, clarify tier=off scope

- Outbound-call docs/comments referenced doc/privacy.md (the storage/logging
  doc); the canonical outbound-call inventory is repo-root PRIVACY.md, which the
  runtime messages in UpdateCheck.ts / Settings.ts also reference. Re-point
  settings.json.{template,docker} and doc/docker.md there.
- doc/admin/updates.md said updates.tier="off" means "no HTTP request will leave
  the instance", but the legacy UpdateCheck.ts call to ${updateServer}/info.json
  is gated by privacy.updateCheck, not updates.tier. Clarify that air-gapped
  installs must set PRIVACY_UPDATE_CHECK=false too.

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

* docs: link PRIVACY.md via absolute URL to satisfy VitePress dead-link check

PRIVACY.md lives at the repo root, outside the doc/ tree VitePress builds, so a
relative link to it (../PRIVACY.md / ../../PRIVACY.md) is flagged as a dead link
and fails `docs:build`. Use the absolute GitHub URL instead, matching how
doc/configuration.md already links settings.json.template.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:32:46 +01:00
dependabot[bot]
662678275a
build(deps): bump esbuild from 0.28.0 to 0.28.1 (#7952)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.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-06-12 21:29:42 +02:00
dependabot[bot]
8e4eb2646a
build(deps): bump semver from 7.8.3 to 7.8.4 (#7943)
Bumps [semver](https://github.com/npm/node-semver) from 7.8.3 to 7.8.4.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.8.3...v7.8.4)

---
updated-dependencies:
- dependency-name: semver
  dependency-version: 7.8.4
  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-06-12 12:42:25 +01:00
dependabot[bot]
e313366b49
build(deps): bump nodemailer and @types/nodemailer (#7950)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) and [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer). These dependencies needed to be updated together.

Updates `nodemailer` from 8.0.10 to 8.0.11
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.10...v8.0.11)

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

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  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-06-12 12:42:14 +01:00
dependabot[bot]
3cddff7b27
build(deps): bump mongodb from 7.2.0 to 7.3.0 (#7941)
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v7.2.0...v7.3.0)

---
updated-dependencies:
- dependency-name: mongodb
  dependency-version: 7.3.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-06-12 12:36:39 +01:00
dependabot[bot]
817ca2546e
build(deps-dev): bump @types/node in the dev-dependencies group (#7944)
Bumps the dev-dependencies group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.9.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-06-12 12:15:23 +01:00
dependabot[bot]
eefdfc44b9
build(deps): bump pdfkit from 0.19.0 to 0.19.1 (#7945)
Bumps [pdfkit](https://github.com/foliojs/pdfkit) from 0.19.0 to 0.19.1.
- [Release notes](https://github.com/foliojs/pdfkit/releases)
- [Changelog](https://github.com/foliojs/pdfkit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/foliojs/pdfkit/compare/v0.19.0...v0.19.1)

---
updated-dependencies:
- dependency-name: pdfkit
  dependency-version: 0.19.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-06-12 12:15:17 +01:00
John McLear
73a9b88bb4
test(timeslider): port legacy mocha specs to Playwright, retire orphaned suite (#7949)
* test(timeslider): port legacy mocha specs to Playwright, retire orphaned suite

The legacy src/tests/frontend/specs/ mocha suite is run by no CI workflow,
so its timeslider coverage was dead — a regression in the in-pad history UI
(#7659/#7946) sailed through CI. Port the still-meaningful cases to
frontend-new Playwright specs, re-targeted at the real in-pad UI (outer
banner / slider / export links that pad_mode.ts drives) rather than the
isolated ?embed=1 iframe the existing specs use:

- timeslider_revision_labels.spec.ts (from timeslider_labels.js): the
  #history-banner shows 'Version N' + a valid (non-NaN) date and timer, and
  both update when scrubbing to revision 0.
- timeslider_export_links.spec.ts (from timeslider_numeric_padID.js and the
  'checks the export url' case of timeslider_revisions.js): the outer export
  hrefs target /p/<pad>/<rev>/export/<type> for the viewed revision, including
  a numeric pad id, and follow the slider to revision 0.
- timeslider_deeplink.spec.ts (from the 'jumps to a revision given in the url'
  case): a #rev/N hash — and the legacy #N shortlink form — boots straight
  into history mode at that revision.

Delete the three now-ported legacy specs. Verified passing on Chromium and
Firefox. The star-marker case of timeslider_revisions.js is already covered by
timeslider_saved_revisions.spec.ts (#7948).

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

* test(timeslider): make deep-link spec robust on Firefox

Assert the canonical #rev/0 URL and the slider landing on revision 0 rather
than the #history-banner-rev label text. The banner label is populated via a
MutationObserver bridge that races the iframe load on the bootstrap path in
Firefox (it is already covered for the normal button-entry flow by
timeslider_revision_labels.spec.ts); the URL + slider value are the
deterministic signals that the deep link entered history at the right revision.

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

* test(timeslider): address Qodo review on #7949

- Pin locale to en-US in the revision-labels spec so the localized 'Version N'
  label and 'Saved <Month> <day>, <year>' date assertions are deterministic and
  the date stays Date-parseable, instead of depending on the runner's locale.
- Use a high-entropy numeric pad id (timestamp + random) in the export-links
  spec so reruns against a persistent DB can't collide on the same pad.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:14:39 +01:00
John McLear
a65e3b75a4
fix(pad): show saved-revision markers in in-pad history mode (#7946) (#7948)
* fix(pad): show saved-revision markers in in-pad history mode (#7946)

When #7659 moved the timeslider into the pad as an embedded iframe, the
user-facing control became the outer #history-slider-input range input.
The saved-revision stars are still drawn by broadcast_slider.ts into the
iframe's #ui-slider-bar, but that DOM is hidden in embed mode, and
pad_mode.ts bridged rev/max/value/timer/authors to the outer slider but
never the saved revisions. Result: clicking "Save Revision" appeared to
work but no markers showed in the timeslider (regression in 3.3.x).

Bridge the embedded timeslider's clientVars.savedRevisions onto the outer
slider as percentage-positioned star markers, rendered on first sync and
re-rendered when the slider max changes. Markers are a purely visual,
aria-hidden overlay (keyboard/SR users already reach any revision via the
slider + step buttons) with a click-to-seek convenience for mouse users.

Adds a frontend-new Playwright spec exercising the real user flow (save a
revision in the pad, enter in-pad history mode, assert a visible marker on
the outer slider). The only prior coverage lived in the legacy mocha
suite (src/tests/frontend/specs/timeslider_revisions.js) which no CI
workflow runs, and the modern timeslider specs drive the ?embed=1 iframe
directly and never exercise the outer history UI — so this regression was
invisible to CI.

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

* fix(pad): live saved-revision markers + Qodo review fixes (#7946)

Address Qodo review on #7948:

- Live updates (bug 1): the server's SAVE_REVISION handler never broadcast
  NEW_SAVEDREV, so the client handler that adds a star was dead and no open
  timeslider ever updated live. Wire it up: Pad.addSavedRevision returns the
  new revision (undefined on duplicate); handleSaveRevisionMessage broadcasts
  NEW_SAVEDREV to the pad room. pad_mode.ts now sources outer markers from the
  embedded slider's live #ui-slider-bar .star DOM (labels from clientVars) and
  observes that bar so a revision saved by a collaborator appears on an
  already-open history slider. Live editors ignore the unknown message type.

- Single-revision pad (bug 2): allow max === 0 so a revision saved at rev 0
  still renders a marker instead of being cleared by the old max <= 0 guard.

- Test rigor (bug 3): assert the marker's inline left percentage directly
  instead of falling back to a layout coordinate, which let left:0% pass.

Adds a two-client Playwright test for the live path (verified it fails with
the server broadcast removed). Backend Pad + pad API specs (88) still pass.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:12:36 +01:00
translatewiki.net
8b6a0a2f3a
Localisation updates from https://translatewiki.net. 2026-06-11 14:04:05 +02:00
Etherpad Release Bot
4fe94c9b68 Merge branch 'master' into develop 2026-06-10 10:03:40 +00:00
Etherpad Release Bot
59062ce11a bump version 2026-06-10 10:03:40 +00:00
Etherpad Release Bot
92e23bf7d4 Merge branch 'develop' 2026-06-10 10:03:40 +00:00
John McLear
fb7e7af6c9
chore(release): prep 3.3.1 — bump ueberdb2 to ^6.1.9 + changelog (#7939)
Bumps ueberdb2 6.1.8 -> 6.1.9 (postgres pool-error handling + TCP
keep-alive for #7878; redis/rethink connection-error handlers so a
dropped DB connection no longer crashes the process) and adds the
3.3.1 CHANGELOG entry required by bin/release.ts before the release
workflow can run.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:48:32 +01:00
John McLear
ddf84fa6f2
fix(admin): don't let one unreadable pad empty the Manage-pads list (#7935) (#7938)
* fix(admin): don't let one unreadable pad empty the Manage-pads list (#7935)

The admin /settings `padLoad` handler hydrates every pad via getPad() to
build the listing (the default lastEdited sort forces a full scan).
findKeys('pad:*','*:*:*') returns every key under the `pad:` prefix,
including legacy/foreign or migration-corrupted records — e.g. a value
stored as a JSON string rather than a pad object, which a botched
dirty.db -> PostgreSQL migration produces. Loading such a record makes
Pad.init throw `Cannot use 'in' operator to search for 'pool'`.

The handler had no try/catch, so one bad record rejected the whole
request: no `results:padLoad` was emitted (the SPA showed an empty
"No results" state forever — the reported symptom) and the unhandled
rejection could exit the server.

Make loadMeta resilient — a failed getPad() logs a warning and returns
zeroed metadata so the bad pad still surfaces for deletion instead of
hiding every other pad — and wrap the handler so it always emits a
terminal reply and never bubbles to the process-level handler.

Tests:
- backend: tests/backend/specs/admin/padLoadResilience.ts asserts a
  corrupt pad:* record no longer hides the readable pads (fails without
  the fix: no results:padLoad reply + server exit).
- e2e: tests/frontend-new/admin-spec/admin_pads_page.spec.ts covers
  create pad -> welcome-page recent-pads -> /admin Manage-pads UI.

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

* fix(admin): sanitize padLoad error logs to avoid leaking pad content

Qodo review: the error logs (and the error field returned to the SPA)
used `${err}` / err.message verbatim. For the corruption case the
TypeError message embeds the raw stored value ("...'pool' in <value>"),
which for a real pad can be document text — so logging it verbatim could
leak content, bloat logs, and let embedded newlines forge log lines.

Add a safeErr() helper (error name + single-line, 120-char-capped
message, control chars stripped) and use it in both the per-pad warning
and the outer handler error log, and for the error field emitted to the
client.

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

* fix(admin): make surfaced corrupt pads deletable from the admin UI

Qodo review: padLoad now surfaces unreadable pad:* records (zeroed
metadata) so admins can delete them, but deletePad's normal path
(doesPadExists -> getPad -> Pad.remove) can't touch such a record:
doesPadExists() returns false for a non-object value and getPad() throws,
so the surfaced pad was effectively undeletable and the handler emitted
nothing.

When doesPadExists() is false but a raw `pad:${id}` value is present,
fall back to a raw key purge: sweep sub-keys (revs/chat/deletionToken/…)
via findKeys, drop the readonly mapping, then padManager.removePad()
(which removes the main key + pad-list + cache entry). Always emit a
terminal results:deletePad reply (including for an already-absent id) so
the UI clears the row instead of silently doing nothing, and wrap the
handler so failures are logged (sanitized) rather than swallowed.

Adds a backend test asserting a surfaced corrupt pad can be deleted and
disappears from the listing.

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

* test(admin): assert corrupt-pad delete via listing, not raw db.get

The delete test probed `db.get('pad:<id>')` for null right after deletePad.
That passed on postgres but failed on the dirty backend (Windows CI):
ueberdb2's per-backend read/write buffering can return the just-removed
value immediately after remove(), so it asserted storage internals rather
than behaviour. The deletion is still durable (same db.remove() every pad
uses; the in-memory pad-list entry is dropped synchronously). Assert the
behavioural outcome instead — the corrupt pad disappears from the padLoad
listing while the good pad remains.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:46:37 +01:00
John McLear
f47626049c
fix(pad): don't issue a deletion token (or show its modal) when allowPadDeletionByAllUsers is on (#7929)
When `allowPadDeletionByAllUsers` is true, anyone can delete a pad with no
token at all (handlePadDelete's flagOk branch), so the one-time deletion
token is pointless and the "Save your pad deletion token" modal only
overwhelms users who will never need it.

Gate token issuance on `!settings.allowPadDeletionByAllUsers` so no token
reaches clientVars; the client's showDeletionTokenModalIfPresent() then
returns early and the modal never appears. No new setting — it derives
automatically from the existing one.

Closes #7926.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:55:13 +01:00
dependabot[bot]
1e7c4b8c21
build(deps): bump rate-limiter-flexible from 11.1.1 to 11.2.0 (#7934)
Bumps [rate-limiter-flexible](https://github.com/animir/node-rate-limiter-flexible) from 11.1.1 to 11.2.0.
- [Release notes](https://github.com/animir/node-rate-limiter-flexible/releases)
- [Commits](https://github.com/animir/node-rate-limiter-flexible/compare/v11.1.1...v11.2.0)

---
updated-dependencies:
- dependency-name: rate-limiter-flexible
  dependency-version: 11.2.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-06-10 09:54:21 +01:00
dependabot[bot]
ad2e8b18a9
build(deps): bump semver from 7.8.2 to 7.8.3 (#7933)
Bumps [semver](https://github.com/npm/node-semver) from 7.8.2 to 7.8.3.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.8.2...v7.8.3)

---
updated-dependencies:
- dependency-name: semver
  dependency-version: 7.8.3
  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-06-10 09:54:13 +01:00
dependabot[bot]
12bd199f94
build(deps-dev): bump the dev-dependencies group with 2 updates (#7932)
Bumps the dev-dependencies group with 2 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser).


Updates `@typescript-eslint/eslint-plugin` from 8.60.1 to 8.61.0
- [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.61.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.60.1 to 8.61.0
- [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.61.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.61.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.61.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-06-10 09:53:48 +01:00
John McLear
d828185efc
security: escape and integer-coerce numbered-list start attribute (GHSA-f7h5-v9hm-548j) (#7937)
The numbered-list branch of `domline.appendSpan` interpolated the line's
`start` attribute value into an `<ol start=...>` tag unquoted and unescaped,
then assigned the result to `node.innerHTML`. The value comes verbatim from
the attribute pool, which an attacker can populate via a crafted `.etherpad`
import (`ImportEtherpad.setPadRaw` validates attribute keys but not values).
A space-free value such as `1><svg/onload=...>` therefore broke out of the
tag and produced a live element, executing attacker JavaScript in the pad's
origin for every subsequent viewer of the pad or timeslider.

An `<ol>` start is only ever an integer, so coerce it with `Number.parseInt`,
omit the attribute entirely when the value is not a number, and HTML-escape +
quote it (the same `Security.escapeHTMLAttribute` treatment the sibling
`listType` class already gets, and that #7905 applied to the export sink).

Adds a backend regression test that drives the real domline render path
through jsdom and asserts the malicious value cannot produce a live element.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:53:26 +01:00
Stefan Müller
68d322957f
feat: Add test step to check etherpad works offline (#7936) 2026-06-10 09:53:16 +01:00
John McLear
5b146f0b03
fix(pad): show detected language in settings dropdown (#7925) (#7928)
* fix(pad): show detected language in settings dropdown (#7925)

When the UI language was auto-detected from the browser (no language
cookie and no pad-wide lang set), refreshMyViewControls() and
refreshPadSettingsControls() set the language dropdown to
`<option>.lang || 'en'`. Since the detected language lives only in
html10n (not in padOptions/effectiveOptions), the value was undefined
and the dropdown fell back to hardcoded English — even though the pad
UI itself rendered correctly in the detected language.

Fall back to html10n.getLanguage() before 'en' so the dropdown reflects
the language actually rendered. getLanguage() returns the matched
lowercase locale key, which matches the <option value> keys.

Adds a regression test that loads a pad under a de-DE browser locale and
asserts the dropdown reads "Deutsch" without any manual selection.

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

* test(language): assert German toolbar tooltip (Qodo #7928)

The detection-works precondition computed a boolean via locator.evaluate()
but never asserted it, so it could not fail. Assert the bold button's
parent title is the German "Fett (Strg-B)" with toHaveAttribute instead.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:23:01 +01:00
John McLear
67c0e391fd
fix(skin): paint the root canvas so iOS dark mode has no white status bar (#7606) (#7931)
The OP still saw a white strip above the dark pad on iOS Safari even with the
theme-color metas in place. theme-color only tints the address-bar chrome;
the status-bar safe area at the very top is painted by iOS from the ROOT
canvas background. The colibris background variants only colour inner
containers (#editorcontainerbox, body descendants), leaving <html> and <body>
transparent (computed rgba(0,0,0,0)) with color-scheme: normal — so the canvas
fell back to the UA light default (white). Android tints its chrome from
theme-color, which is why it looked fine there but iOS did not.

Paint the <html> root per toolbar variant with the toolbar colour (matching
theme-color and the OP's request that the bar match the toolbar) and set
color-scheme so the safe area, toolbar, and address bar are one seamless
colour. Verified in a mobile viewport: dark-OS root background is now
rgb(72,83,101) (#485365, == --super-dark-color, the toolbar colour); light-OS
stays white. Colours mirror skin_toolbar_colors.ts.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:13:11 +01:00
John McLear
b420cf4850
ci(downstream): robust per-client error handling in run-clients.sh (#7927)
* ci(downstream): robust per-client error handling in run-clients.sh (Qodo)

- Move clone/fetch/checkout inside the per-client guarded block with explicit
  '|| exit 1' on every step. set -e is suspended inside a subshell used as an
  '||' operand, so relying on it silently swallowed clone/checkout failures
  (and continued from the wrong cwd); explicit guards make one client's failure
  a per-client fail=1 while the loop continues, and the run exits non-zero.
- Stop suppressing fetch errors; fetch only when the pinned commit isn't already
  reachable, and surface the real error.
- Run manifest commands via 'bash -c' instead of 'eval' (trusted in-repo
  allowlist; avoids double-parsing / leaking into this script's shell).

Verified: two bogus clients -> both reported, loop continues, exit 1; happy
path (cli, no server) -> vectors pass, smoke skips, exit 0.

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

* ci(downstream): pin clients to their merged-commit SHAs

The three client Phase-2 PRs merged; bump each manifest ref from its
pre-merge PR-branch tip (now deleted / unfetchable) to its squash-merge
commit on main:
  etherpad-pad        -> 91620c6 (ether/pad#1)
  etherpad-cli-client -> ebc516e (ether/etherpad-cli-client#136)
  etherpad-desktop    -> ab83da6 (ether/etherpad-desktop#78)

Verified each merged main carries the test entry points
(vectors.rs/smoke.rs; test:vectors/test:smoke scripts).

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

* ci(downstream): run manifest commands under bash strict mode (Qodo)

bash -c spawns a fresh shell without -e/-u/-o pipefail, so a pipeline-stage
failure inside a client's vectorTest/smokeCmd could be masked. Use
'bash -euo pipefail -c' so those failures surface as a non-zero exit.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 14:31:05 +01:00
John McLear
e56a33efd0
ci(downstream): enable pad/cli/desktop smoke clients (Phase 2) (#7924)
Implements the per-kind orchestration (clone @ pinned ref, inject core's
freshly-generated wire-vectors fixture via ETHERPAD_WIRE_VECTORS, run each
client's vectorTest + smokeCmd against the booted server) in a testable
run-clients.sh, and flips the three manifest entries to enabled, pinned to the
commits that carry their Phase 2 tests:
  ether/pad#1, ether/etherpad-cli-client#136, ether/etherpad-desktop#78.

Validated end-to-end locally against a real core: all three clients' vectors +
live smoke pass. Refs should be bumped to the squash-merge commits once those
client PRs land.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:49:42 +01:00
John McLear
860ab68c04
test: downstream client compatibility gate (Phase 1) (#7923)
* docs: design for downstream client compatibility tests

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

* docs: Phase 1 implementation plan for downstream compat tests

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

* test(downstream): add golden wire-vector generator

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

* test(downstream): add committed golden wire-vectors fixture

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

* test(downstream): assert wire-vectors fixture stability + consistency

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

* test(downstream): pin socket.io handshake + USER_CHANGES sequence

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

* test(downstream): snapshot client-facing HTTP API shapes

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

* test(downstream): add client manifest (entries disabled pending Phase 2)

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

* ci(downstream): add downstream-smoke workflow (boot/self-check/teardown + manifest scaffold)

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

* ci(downstream): validate settings rewrite + ignore docs/** (Qodo)

Fail fast if the template's port/auth literals drift so a no-op sed can't
silently boot the smoke server on the wrong port/auth. Also ignore docs/**
(not just doc/**) so docs-only PRs don't trigger the boot job.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:40:58 +01:00
Etherpad Release Bot
67f7d914f4 Merge branch 'develop' 2026-06-09 08:54:33 +00:00
Etherpad Release Bot
9af3a4de77 Merge branch 'master' into develop 2026-06-09 08:54:33 +00:00
Etherpad Release Bot
bb2ef93a1b bump version 2026-06-09 08:54:32 +00:00
John McLear
62b9c711a2
docs(changelog): fold post-3.3.0-draft commits into the 3.3.0 notes (#7921)
The `# 3.3.0` changelog section was written before ~8 commits landed on
develop. #7909 (dark mode) and #7918/#7911 (offline Docker boot) were already
captured; this adds the remaining user-facing change and dependency bumps so
the 3.3.0 release notes are complete:

- Notable fixes: #7910 — Firefox authorship-colour flake (early keystrokes
  tagged author='' before the async userAuthor propagated, producing an
  unattributed insert the pad-corruption guard rejected).
- Dependencies: mysql2 →3.22.5 (#7915), undici →8.4.1 (#7914), pdfkit
  →0.19.0 (#7916), @radix-ui/react-switch →1.3.0 (#7913), and the extra
  dev-dependency group bump (#7912).

Localisation (translatewiki) is already covered by the existing entry.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:52:40 +01:00
John McLear
53407f090d
docs(release): document release procedure in AGENTS.MD; remove deprecated createRelease.sh (#7920)
* chore(release): remove deprecated bin/createRelease.sh

This script has carried a "DEPRECATED since Etherpad 1.7.0 (2018-08-17),
left here just for documentation" banner for years and is dead code:

- It authenticates to the GitHub API with the `?access_token=` query
  parameter, which GitHub removed in 2021 — every API call (token check,
  branch merge, release publish) now fails outright.
- It targets the old `ether/etherpad-lite` repo paths and calls
  `bin/buildForWindows.sh` / `make docs`, neither of which is how releases
  are built anymore.
- Nothing references it (no workflow, script, or doc).

The current release flow is the "Release etherpad" workflow
(.github/workflows/release.yml) driving bin/release.ts, then the tag-push
triggers handleRelease.yml + releaseEtherpad.yml. createRelease.sh only adds
confusion, so remove it.

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

* docs(agents): document the release procedure and docs publishing

Add a "Releasing" section to AGENTS.MD so maintainers have a single
reference for cutting a release, instead of reverse-engineering it from
bin/release.ts and the workflow files.

Covers:
- Prerequisites: the CHANGELOG `# X.Y.Z` guard, and the requirement that all
  four package.json files agree (release.ts reads the current version from
  src/package.json) — the desync that blocked the 3.3.0 release.
- The one-dispatch flow: "Release etherpad" -> bin/release.ts -> tag push,
  and what the vX.Y.Z tag auto-triggers (handleRelease GitHub Release,
  docker, snap-publish), plus the separate manual npm publish dispatch.
- Documentation: the two distinct kinds of doc work — per-PR doc/ updates in
  behaviour-change PRs, and the automated release-time versioned-docs publish
  into the ether.github.com sibling repo.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:32:25 +01:00
John McLear
b19ad89eb0
Fix offline/air-gapped Docker boot: stop pnpm self-provisioning the pinned version (#7911) (#7918)
* fix(docker): don't let pnpm self-provision a pinned version on offline boot (#7911)

The official Docker image installs pnpm directly via npm (corepack was dropped
for Node 25+). Standalone pnpm still honours the "packageManager" pin in
package.json: the image's pnpm intentionally lags that pin (pnpm 11.1.x enforces
a minimum-release-age policy the frozen-lockfile build can't satisfy), so pnpm
treats every invocation — including the informational `pnpm --version` probe
Etherpad runs at startup — as a request to download and run the pinned build.
Behind a corporate firewall / in an air-gapped install that download fails:

  [WARN] plugins - Failed to get pnpm version: Error: Command exited with
  code 1: pnpm --version

which is what #7911 reported.

Fix — neutralise the gap instead of closing it (closing it would break the
frozen-lockfile build on 11.1.x):

  - Dockerfile build stage sets `pnpm_config_pm_on_fail=ignore` (the pnpm 11
    successor to managePackageManagerVersions), inherited by the development and
    production runtime stages. pnpm then uses the installed pnpm instead of
    fetching the pinned one. It does not change which pnpm runs the build-time
    install, so the frozen-lockfile build is unaffected.
  - plugins.ts startup probe and the updater's pnpm-on-PATH checks run with the
    same flag, so the fix also covers non-Docker offline installs and the probe
    can never fail-loud.

Add a backend spec that fails CI if the offline guard is dropped while the image
pnpm differs from the package.json pin.

Verified with a standalone (non-corepack) pnpm: a "packageManager" mismatch
makes `pnpm --version` exit 1 by default (tries to fetch the pinned build), and
exit 0 reading the local version with pm_on_fail=ignore.

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

* test: scope pnpm offline-guard check to the runtime-inherited build stage

Address Qodo review: the regression spec matched ENV pnpm_config_pm_on_fail
anywhere in the Dockerfile, so it would still pass if the guard were removed
from the `build` stage (which the runtime stages inherit) but left in the
throwaway `adminbuild` stage — reintroducing the offline failure. Extract the
`build` stage block and assert the ENV is present there specifically.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:31:00 +01:00
John McLear
809b75ca22
fix(release): sync src + bin package.json to 3.2.0 so release dispatch targets 3.3.0 (#7919)
The Release workflow has failed on the last two dispatches with:

    Error: No changelog record for 3.4.0, please create changelog record
    at bin/release.ts:146

3.3.0 was never released (latest tag/release is v3.2.0). The repo already
carries a complete `# 3.3.0` changelog section, and the intent is to ship
3.3.0 next. A prior commit reverted the root and admin/ package.json back to
3.2.0 to set up a clean 3.2.0 -> 3.3.0 minor bump, but missed src/package.json
and bin/package.json, which were left at 3.3.0.

bin/release.ts reads the current version from src/package.json, so it computed
minor(3.3.0) = 3.4.0 and aborted on the missing 3.4.0 changelog. Syncing src
and bin back to 3.2.0 (matching root + admin) makes the next `minor` dispatch
compute 3.2.0 -> 3.3.0, which satisfies the existing changelog check.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:30:40 +01:00
SamTV12345
d09227b1e7 3.2.0 2026-06-08 22:19:50 +02:00
dependabot[bot]
54af41276c
build(deps): bump @radix-ui/react-switch from 1.2.6 to 1.3.0 (#7913)
Bumps [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) from 1.2.6 to 1.3.0.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.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-06-08 21:54:42 +02:00
dependabot[bot]
271ded5e49
build(deps): bump mysql2 from 3.22.4 to 3.22.5 (#7915)
Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.22.4 to 3.22.5.
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.22.4...v3.22.5)

---
updated-dependencies:
- dependency-name: mysql2
  dependency-version: 3.22.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-06-08 21:54:32 +02:00
dependabot[bot]
d6837f9013
build(deps): bump undici from 8.3.0 to 8.4.1 (#7914)
Bumps [undici](https://github.com/nodejs/undici) from 8.3.0 to 8.4.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.3.0...v8.4.1)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 8.4.1
  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-06-08 21:54:23 +02:00
John McLear
ef205bf2ac
fix(theme-color): dark address bar + no light-flash on dark-OS load (#7606) (#7909)
* fix(theme-color): emit media-scoped dark variant for iOS Safari (#7606)

The theme-color meta only had a single light value rendered server-side;
dark mode was applied purely by JS (skin_variants.ts) after page load.
iOS Safari colors the address bar at parse time and does not reliably
repaint when JS mutates the meta later, so dark-mode iPhone users kept a
white address bar above a dark toolbar (the green Chromium Playwright test
masked this because Chrome does honor the dynamic update).

Emit a prefers-color-scheme media-scoped pair server-side so the correct
color is chosen at first paint without JS:

  <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
  <meta name="theme-color" content="#485365" media="(prefers-color-scheme: dark)">

- Add SkinColors.darkToolbarColor() (reuses toolbarColorForTokens).
- Expose enableDarkMode via getPublicSettings so the templates can gate the
  dark variant on it (no dark variant when dark mode can't be reached).
- Apply to both pad.html and timeslider.html.
- updateThemeColorMeta now updates every theme-color meta so a manual
  #options-darkmode toggle still wins over the media scoping on
  desktop/Android.
- Backend + frontend tests updated to assert the media-scoped pair and the
  enableDarkMode-off case.

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

* fix(theme-color): prevent the light-mode flash on dark-OS load (#7606)

The theme-color meta fix corrects the address-bar tint, but dark-OS users
still saw the whole page painted light before the JS bundle ran and applied
the dark skin classes in postAceInit — a visible flash on every browser,
not just the mobile address bar.

Add a tiny blocking inline script in <head>, before the stylesheet, that
applies the dark skin classes to <html> synchronously during parse when the
client is in dark mode (matchMedia + no localStorage white-mode override).
The condition mirrors pad.ts's auto-switch, which still runs on init to wire
up the #options-darkmode toggle and theme the editor iframes (those don't
exist yet at parse time). Gated on the same enableDarkMode + colibris check
as the dark theme-color variant. Applied to pad.html and timeslider.html.

Verified in Chromium: at domcontentloaded a dark-OS client's <html> already
carries super-dark-editor/dark-background/super-dark-toolbar (no flash), and
a light-OS client is unaffected.

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

* docs(changelog): note the dark-mode address-bar + flash fix (#7606)

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

* fix(theme-color): guard pre-paint script on #skinvariantsbuilder; ignore updater state

Address PR review:
- Copilot: the inline pre-paint dark-mode script must skip the auto-dark
  switch on the #skinvariantsbuilder hash, matching pad.ts — otherwise it
  forces super-dark classes on a dark-OS client and fights the variants
  builder UI. Added the guard to pad.html and timeslider.html and a backend
  assertion so it can't regress.
- Qodo: ignore var/update-state.json (runtime updater cache) so the server
  run that regenerates it can't dirty the tree or be committed accidentally.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:54:08 +02:00
dependabot[bot]
c8350782dd
build(deps): bump pdfkit from 0.18.0 to 0.19.0 (#7916)
Bumps [pdfkit](https://github.com/foliojs/pdfkit) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/foliojs/pdfkit/releases)
- [Changelog](https://github.com/foliojs/pdfkit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/foliojs/pdfkit/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: pdfkit
  dependency-version: 0.19.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-06-08 20:41:41 +01:00
dependabot[bot]
2c64e85e90
build(deps-dev): bump the dev-dependencies group with 8 updates (#7912)
Bumps the dev-dependencies group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@types/jquery](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jquery) | `4.0.0` | `4.0.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `25.9.2` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-toast](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/toast) | `1.2.15` | `1.2.16` |
| [@radix-ui/react-visually-hidden](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/visually-hidden) | `1.2.4` | `1.2.5` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.16` | `19.2.17` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.77.0` | `7.78.0` |
| [oxc-minify](https://github.com/oxc-project/oxc/tree/HEAD/napi/minify) | `0.134.0` | `0.135.0` |


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

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

Updates `@radix-ui/react-dialog` from 1.1.15 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-toast` from 1.2.15 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/toast/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/toast)

Updates `@radix-ui/react-visually-hidden` from 1.2.4 to 1.2.5
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/visually-hidden/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/visually-hidden)

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

Updates `react-hook-form` from 7.77.0 to 7.78.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.77.0...v7.78.0)

Updates `oxc-minify` from 0.134.0 to 0.135.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/napi/minify/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/crates_v0.135.0/napi/minify)

---
updated-dependencies:
- dependency-name: "@types/jquery"
  dependency-version: 4.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.9.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@radix-ui/react-toast"
  dependency-version: 1.2.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@radix-ui/react-visually-hidden"
  dependency-version: 1.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.78.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxc-minify
  dependency-version: 0.135.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-06-08 20:41:25 +01:00