PageDown was broken — it moved the caret to the last visible line of
the current viewport instead of advancing by one page. This caused it
to get "stuck" at the bottom of the viewport.
The old code set the caret to oldVisibleLineRange[1] - 1 (the last
visible line), which was essentially a no-op for scrolling. The fix
mirrors the PageUp logic: advance/retreat by numberOfLinesInViewport.
Also simplified the clamping logic for both selStart and selEnd.
Fixes: https://github.com/ether/etherpad-lite/issues/6710
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: filter already-deleted sessions when deleting a group
deleteSession uses setSub(..., undefined) to remove session references
from group2sessions and author2sessions, but this can leave null entries
in the sessionIDs object. When deleteGroup later iterates Object.keys
of sessionIDs and calls deleteSession on each, it throws "sessionID
does not exist" for the already-deleted sessions.
Now deleteGroup filters out null/falsy session entries before attempting
to delete them.
Fixes: https://github.com/ether/etherpad-lite/issues/5798
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression test for deleteGroup after deleteSession (#5798)
Creates a group, author, and session, then deletes the session first,
then deletes the group. Without the fix, deleteGroup would throw
"sessionID does not exist" when encountering the null session entry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an ordered list followed directly after an unordered list (no blank
line between), all OL items showed "1" instead of incrementing. This was
because renumberList's applyNumberList function counted bullet items in
the position counter, so the first OL item got start=3 (after 2 bullet
items) instead of start=1, preventing the CSS counter-reset class from
being applied.
The fix resets the position counter when the list type changes at the
same level (e.g., bullet -> number).
Fixes: https://github.com/ether/etherpad-lite/issues/5160
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: wait for server confirmation before navigating after pad delete
The delete pad handler navigated to '/' immediately after sending the
PAD_DELETE message. Firefox (and some mobile Chrome) would close the
WebSocket before the message reached the server, causing the delete to
silently fail.
Now the client waits for the server's {disconnect: 'deleted'} response
before navigating. Also awaits pad.remove() on the server side to
ensure the operation completes before the response is sent.
Fixes: https://github.com/ether/etherpad-lite/issues/7306
Fixes: https://github.com/ether/etherpad-lite/issues/7311
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle non-creator delete and add timeout fallback
- Listen for 'shout' event to show error when non-creator tries to
delete (server sends shoutMessage instead of deleting)
- Add 5-second timeout fallback in case the server doesn't respond
(socket dropped, server crashed, etc.)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When HTML containing <li> elements without a wrapping <ul> or <ol> is
pasted (e.g., from ChatGPT), the contentcollector crashes with
"TypeError: lineAttributes.list is undefined" because it assumes
_enterList() was already called by a parent list element.
The fix defaults bare <li> elements to bullet1 list type and properly
sets oldListTypeOrNull so the list state is cleaned up after the <li>
is processed. Also guards the .indexOf() call on lineAttributes.list.
Fixes: https://github.com/ether/etherpad-lite/issues/6665
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Drop webkit from CI workflow and Playwright config (Chrome + Firefox
are the supported browsers)
- Set retries: 2 in CI to handle intermittent failures from timing
sensitive operations (list attribute clearing, server restarts)
- Fix clearAuthorship helper to use force:true to bypass toolbar-overlay
div that intermittently intercepts clicks after text selection
- Fix admin restartEtherpad helper: increase poll intervals, add
explicit timeout, use toHaveValue with timeout instead of toBeEmpty
- Convert clear_authorship_color tests to use Playwright auto-retry
assertions (toHaveAttribute) instead of one-shot getAttribute calls
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Enforce 2-space indentation across codebase
Convert all 4-space indented source files to 2-space to match
.editorconfig and project contributor guidelines.
74 files converted: admin UI components, type definitions, security
modules, test files, helpers, and utilities.
No functional changes — 2882 insertions, 2882 deletions (pure
whitespace).
Fixes#7353
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Limit admin tests to chromium and firefox
Webkit is already tested in the dedicated frontend-tests workflow.
Running it again in admin tests causes flaky failures due to slow
socket connections and external API timeouts on webkit CI runners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The settings textarea content is populated asynchronously via socket.
On slow CI (especially Node 20 + Firefox), the default 20s timeout
isn't enough. Increase to 30s for all toBeEmpty checks.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix flaky undo keypress test
Each Ctrl+Z may only undo one keystroke depending on how Etherpad
batches undo operations (varies between dev and prod mode). Loop
Ctrl+Z presses until content is restored instead of assuming one
press undoes everything.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix flaky admin restart test causing cascading failures
restartEtherpad used a hardcoded 500ms wait which wasn't enough for
the server to restart on slow CI. Subsequent tests got
ERR_CONNECTION_REFUSED because the server was still down.
- Poll the server until it responds instead of hardcoded timeout
- Re-login after restart since the session cookie is lost
- Remove unnecessary waitForTimeout(5000) at end of test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Speed up restart poll: accept any response, poll at 500ms
The previous check required response.ok() (2xx) but the server
returns redirects (3xx) which caused the loop to run all 30 seconds.
Accept any non-zero status and reduce poll interval to 500ms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix loginToAdmin: navigate to /admin/login directly
The admin SPA only shows the login form at /admin/login. Navigating to
/admin/ loads the HomePage route which doesn't have the login fields.
After a server restart the session is lost, but the SPA doesn't
automatically redirect to /admin/login, causing the test to timeout
waiting for input[name="username"].
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix plugin search test: wait for results, increase timeout
The search is debounce-triggered (500ms), not Enter-triggered. The
toHaveCount(1) check passed on the "not found" row before results
loaded. Removed the Enter press and count check, increased timeout
to 30s since the search depends on an external API call to
static.etherpad.org.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix plugin search: normalize numeric keys from registry
The plugin registry at static.etherpad.org/plugins.json changed format
from {ep_name: {data}} to {index: {name: "ep_name", data}}. The search
code iterated keys expecting plugin names starting with "ep_", but got
numeric keys like "41", skipping all plugins. Normalize the data after
fetching to use plugin names as keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Pin plugins to last-known-good versions in backend tests
Pin ep_font_size@0.4.65, ep_headings2@0.2.76, ep_markdown@10.0.1
to the versions that passed on March 31. The newer versions cause
a template crash: Cannot read properties of undefined (reading
'indexOf') at pad.html:67 in toolbar.menu().
This will help narrow down which plugin update is the culprit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Unpin ep_markdown, 1.0.8 is latest and code-identical to 10.0.1
Only ep_font_size@0.4.65 and ep_headings2@0.2.76 remain pinned to
narrow down which plugin update causes the toolbar template crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use pnpm instead of gnpm for plugin install in backend tests
gnpm ignores version pins — it reports installing the pinned version
but the plugin loader picks up the latest from its store. Switching
to pnpm for the plugin install step so version pins actually work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use gnpm exec pnpm for plugin install to bypass gnpm caching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove ep_hash_auth from backend test plugin list
ep_hash_auth blocks unauthenticated requests, causing 28 backend tests
to get 500 Internal Server Error when accessing pads. The tests don't
provide credentials, so any auth plugin will break them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ESM/CJS interop for Settings module and harden toolbar
Plugins use require('ep_etherpad-lite/node/utils/Settings') (CJS) but
Settings.ts uses export default (ESM). With tsx, CJS require puts the
default export under .default, so settings.toolbar is undefined and
ep_font_size crashes with "Cannot read properties of undefined
(reading 'indexOf')" when rendering pad.html.
Two fixes:
- Settings.ts: add property getters on module.exports so CJS consumers
can access settings properties directly
- toolbar.ts: guard against undefined buttons array to prevent crashes
if Settings interop doesn't propagate through gnpm's plugin_packages
Tested locally: 735 passing, 0 failing with all plugins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix flaky unordered_list and undo tests
- unordered_list: re-query the DOM element after clearPadContent()
instead of holding a stale reference that detaches when the editor
rebuilds the DOM.
- undo: remove hardcoded waitForTimeout(1000) and re-query the element
so Playwright's auto-retry on toHaveText handles the timing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix flaky tests: wait for DOM settle, undo all keystrokes
- unordered_list: wait for DOM to settle after clearPadContent() with
toHaveCount assertion, then use click() instead of selectText() which
races with async DOM rebuilds.
- undo: press Ctrl+Z three times since writeToPad('foo') produces 3
keystrokes and each Ctrl+Z only undoes one.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix undo keypress test: don't clear pad before typing
Clearing the pad added an undoable operation to the history, so Ctrl+Z
could undo past the typing and restore the original welcome text.
Simplified to match the button-based undo test: type on existing
content and undo once, since Etherpad batches rapid keystrokes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix undo keypress test: restore clear step, mirror button test
writeToPad needs clearPadContent first to ensure focus is in the
editor iframe. Without it, keyboard.type() doesn't reach the editor.
Now mirrors the button-based undo test exactly, just using Ctrl+Z
instead of clicking the undo button, and without the hardcoded
waitForTimeout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix frontend test failures across all browsers
- Fix home button using fragile relative URL (window.location.href +
"/../..") that WebKit doesn't resolve correctly. Use
window.location.origin instead.
- Wait for #editorcontainer.initialized in goToNewPad/goToPad/
appendQueryParams so toolbar, chat, and cookie handlers are fully
set up before tests interact with them.
- Clear cookies in chat test beforeEach to prevent chatAndUsers cookie
from prior tests disabling the sticky chat checkbox.
- Wait for navigation to complete in editbar home button test.
Fixes#7405
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Run frontend tests on pull requests
Playwright runs locally and doesn't need Sauce Labs secrets, so
there's no reason to limit frontend tests to push events only.
Also remove stale Sauce Labs references from workflow names/comments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix sticky chat test: use click() instead of check()/uncheck()
The stickToScreen() handler manages checkbox state internally with its
own toggle logic and a setTimeout. Playwright's check()/uncheck()
methods verify state after clicking, but race with the async toggle,
causing "Clicking the checkbox did not change its state" errors.
Using click() avoids this — the waitForSelector calls already verify
the final state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix sticky chat handler and reduce parallel workers
- Remove force:true from sticky chat checkbox clicks — it can bypass
jQuery event handlers preventing stickToScreen() from firing.
- Wait for chatbox stickyChat class instead of checkbox state, since
stickToScreen() manages the checkbox asynchronously via setTimeout.
- Reduce workers from 5 to 2 to avoid overloading the single Etherpad
server instance, which causes goToNewPad timeouts on CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Clean up workflows: remove Sauce Labs, load test push-only
- Remove all Sauce Labs references (steps, comments, secrets) from
frontend test workflows — Playwright replaced Sauce Labs
- Remove unused set-output steps and GIT_HASH exports
- Remove stale commented-out code from admin tests
- Restrict load test to push events only (no need on PRs)
- Fix artifact names to not reference undefined matrix.node
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix sticky chat test: click label instead of checkbox
The label element intercepts pointer events on the checkbox (reported
by Webkit). On Chrome/Firefox the checkbox is "not stable" due to
animations. Clicking the label is how a real user interacts with it
and properly triggers the jQuery click handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix home button to preserve subpath installations
Use URL API to resolve '../..' relative to current URL instead of
hardcoding origin + '/'. This preserves any configured base path
(e.g. /etherpad) for reverse-proxy installations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set retries to 0 so test failures are reported honestly. With retries: 2,
tests could fail twice and still pass on the third attempt, hiding real
bugs as "flaky" tests that count as passing.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the 2020 workaround that disabled nice-select on Safari due to
a position:fixed + overflow:hidden rendering bug. This bug has been
fixed in modern WebKit, and disabling nice-select meant Safari/WebKit
users got native selects while tests expected the custom dropdowns,
causing all font_type and language tests to fail on webkit.
Fixes#7405
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add AGENTS.MD for AI and developer guidance
* docs: update project name from Etherpad Lite to Etherpad
* docs: fix incorrect test directory path in AGENTS.MD
* docs: correct test stack description in AGENTS.MD (Mocha is primary)
* docs: fix incorrect easysync documentation path in AGENTS.MD
* chore: add .pr_agent.toml to enable automatic PR review/description on push
* docs: remove nodejs version from README and AGENTS.MD (prefer package.json)
* docs: standardize all indentation to 2 spaces
* chore: update src/package.json node engine to match root (>=20.0.0)