From fe968265ac2991cb919e026dd0651c7785c3bc6e Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 16:47:57 +0100 Subject: [PATCH 1/2] feat(colors): add padOptions.fadeInactiveAuthorColors toggle Adds a new pad option fadeInactiveAuthorColors (default true). When false, each author's background color stays at their chosen value instead of fading toward white as the author goes inactive. Settable server-side via settings.json / settings.json.docker and per-pad via the ?fadeInactiveAuthorColors=false URL parameter. Motivation (from issue): a user who picks a light color can become visually indistinguishable after the fade, and switching machines multiplies the effective color count because each session fades its own near-white variant. Default is unchanged (true) so existing deployments keep legacy behavior. Closes #7138 Co-Authored-By: Claude Opus 4.7 (1M context) --- settings.json.docker | 3 ++- settings.json.template | 8 ++++++- src/node/utils/Settings.ts | 2 ++ src/static/js/ace2_inner.ts | 8 ++++++- src/static/js/pad.ts | 7 ++++++ src/tests/backend/specs/settings.ts | 11 ++++++++++ .../specs/inactive_color_fade.spec.ts | 22 +++++++++++++++++++ 7 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/tests/frontend-new/specs/inactive_color_fade.spec.ts diff --git a/settings.json.docker b/settings.json.docker index 8fdd51de01e..fa26c199d38 100644 --- a/settings.json.docker +++ b/settings.json.docker @@ -288,7 +288,8 @@ "rtl": "${PAD_OPTIONS_RTL:false}", "alwaysShowChat": "${PAD_OPTIONS_ALWAYS_SHOW_CHAT:false}", "chatAndUsers": "${PAD_OPTIONS_CHAT_AND_USERS:false}", - "lang": "${PAD_OPTIONS_LANG:null}" + "lang": "${PAD_OPTIONS_LANG:null}", + "fadeInactiveAuthorColors": "${PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS:true}" }, /* diff --git a/settings.json.template b/settings.json.template index 0d1493c2b40..d05b9360a96 100644 --- a/settings.json.template +++ b/settings.json.template @@ -261,7 +261,13 @@ "rtl": false, "alwaysShowChat": false, "chatAndUsers": false, - "lang": null + "lang": null, + /* + * When true (default), each author's caret/background color fades toward white + * as the author goes inactive. Set to false if users pick light colors and the + * faded variants become visually indistinguishable. + */ + "fadeInactiveAuthorColors": true }, /* diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts index 0b250e494c3..5bd1e5d47f9 100644 --- a/src/node/utils/Settings.ts +++ b/src/node/utils/Settings.ts @@ -203,6 +203,7 @@ export type SettingsType = { alwaysShowChat: boolean, chatAndUsers: boolean, lang: string | null, + fadeInactiveAuthorColors: boolean, }, enableMetrics: boolean, padShortcutEnabled: { @@ -410,6 +411,7 @@ const settings: SettingsType = { alwaysShowChat: false, chatAndUsers: false, lang: null, + fadeInactiveAuthorColors: true, }, /** * Wether to enable the /stats endpoint. The functionality in the admin menu is untouched for this. diff --git a/src/static/js/ace2_inner.ts b/src/static/js/ace2_inner.ts index 3ca880a9484..b40e2026651 100644 --- a/src/static/js/ace2_inner.ts +++ b/src/static/js/ace2_inner.ts @@ -236,7 +236,13 @@ function Ace2Inner(editorInfo, cssManagers) { cssManagers.parent.removeSelectorStyle(authorSelector); } else if (info.bgcolor) { let bgcolor = info.bgcolor; - if ((typeof info.fade) === 'number') { + // padOptions.fadeInactiveAuthorColors (default true) controls whether the author + // background fades toward white as the author goes inactive. Integrators can set + // it to false server-side when users pick light colors that become indistinguishable + // from the faded variants. + const fadeEnabled = window.clientVars.padOptions == null || + window.clientVars.padOptions.fadeInactiveAuthorColors !== false; + if (fadeEnabled && (typeof info.fade) === 'number') { bgcolor = fadeColor(bgcolor, info.fade); } const textColor = diff --git a/src/static/js/pad.ts b/src/static/js/pad.ts index d9698f5e776..641b1c057b8 100644 --- a/src/static/js/pad.ts +++ b/src/static/js/pad.ts @@ -69,6 +69,13 @@ const getParameters = [ $('#clearAuthorship').hide(); }, }, + { + name: 'fadeInactiveAuthorColors', + checkVal: 'false', + callback: (val) => { + if (clientVars.padOptions) clientVars.padOptions.fadeInactiveAuthorColors = false; + }, + }, { name: 'showControls', checkVal: 'true', diff --git a/src/tests/backend/specs/settings.ts b/src/tests/backend/specs/settings.ts index 3f836ae404a..e89531b0fdb 100644 --- a/src/tests/backend/specs/settings.ts +++ b/src/tests/backend/specs/settings.ts @@ -147,4 +147,15 @@ describe(__filename, function () { } }); }); + + // Regression test for https://github.com/ether/etherpad/issues/7138. + // padOptions.fadeInactiveAuthorColors must default to true so existing + // installations keep the legacy fade-on-inactive behavior, and must be + // overridable via PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS in docker. + describe('padOptions.fadeInactiveAuthorColors (issue #7138)', function () { + it('defaults to true so existing deployments are unchanged', function () { + const settings = require('../../../node/utils/Settings'); + assert.strictEqual(settings.padOptions.fadeInactiveAuthorColors, true); + }); + }); }); diff --git a/src/tests/frontend-new/specs/inactive_color_fade.spec.ts b/src/tests/frontend-new/specs/inactive_color_fade.spec.ts new file mode 100644 index 00000000000..7cd17cffed2 --- /dev/null +++ b/src/tests/frontend-new/specs/inactive_color_fade.spec.ts @@ -0,0 +1,22 @@ +import {expect, test} from "@playwright/test"; +import {appendQueryParams, goToNewPad} from "../helper/padHelper"; + +test.beforeEach(async ({page, browser}) => { + const context = await browser.newContext(); + await context.clearCookies(); + await goToNewPad(page); +}); + +test.describe('fadeInactiveAuthorColors URL parameter (issue #7138)', function () { + test('defaults to true (legacy fade behavior preserved)', async function ({page}) { + const fade = await page.evaluate(() => (window as any).clientVars?.padOptions?.fadeInactiveAuthorColors); + expect(fade).toBe(true); + }); + + test('fadeInactiveAuthorColors=false disables the fade', async function ({page}) { + await appendQueryParams(page, {fadeInactiveAuthorColors: 'false'}); + const fade = await page.evaluate( + () => (window as any).clientVars?.padOptions?.fadeInactiveAuthorColors); + expect(fade).toBe(false); + }); +}); From c534518467cd694ab1e2a1a55d9026e06037d476 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 18:37:46 +0100 Subject: [PATCH 2/2] fix(7138): document PAD_OPTIONS env var, drop unused browser context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Qodo review feedback on #7554: 1. doc/docker.md's Pad Options env-var table was missing PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS. Users following the docs had no way to discover the new toggle — add the entry with its description and default. 2. The Playwright spec's `browser.newContext() + clearCookies()` pattern was ineffective — tests navigate with the `page` fixture from the default context, so clearing cookies on a separate context is a no-op and the context leaks. Replace with `page.context().clearCookies()`. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/docker.md | 1 + src/tests/frontend-new/specs/inactive_color_fade.spec.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/docker.md b/doc/docker.md index 71af392360f..92a72b77a9c 100644 --- a/doc/docker.md +++ b/doc/docker.md @@ -109,6 +109,7 @@ If your database needs additional settings, you will have to use a personalized | `PAD_OPTIONS_ALWAYS_SHOW_CHAT` | | `false` | | `PAD_OPTIONS_CHAT_AND_USERS` | | `false` | | `PAD_OPTIONS_LANG` | | `null` | +| `PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS` | Fade each author's caret/background toward white as they go inactive. Set to `false` if users pick light colors that become indistinguishable from the faded variants. | `true` | ### Shortcuts diff --git a/src/tests/frontend-new/specs/inactive_color_fade.spec.ts b/src/tests/frontend-new/specs/inactive_color_fade.spec.ts index 7cd17cffed2..f28c7cc35e5 100644 --- a/src/tests/frontend-new/specs/inactive_color_fade.spec.ts +++ b/src/tests/frontend-new/specs/inactive_color_fade.spec.ts @@ -1,9 +1,11 @@ import {expect, test} from "@playwright/test"; import {appendQueryParams, goToNewPad} from "../helper/padHelper"; -test.beforeEach(async ({page, browser}) => { - const context = await browser.newContext(); - await context.clearCookies(); +test.beforeEach(async ({page}) => { + // clearCookies on the page's own context — `browser.newContext()` + // creates a separate context that the `page` fixture doesn't use, + // so clearing cookies on it is a no-op (Qodo review feedback). + await page.context().clearCookies(); await goToNewPad(page); });