From 9d851940aaf80ee86cbb9703827daded6077303c Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 15 Jun 2026 10:33:16 +0100 Subject: [PATCH 1/2] fix(pad): keep token-less Delete pad reachable without pad-wide settings (#7959) The token-less "Delete pad" button (#delete-pad) was nested inside the enablePadWideSettings-gated pad-settings section, so disabling pad-wide settings removed the only way to delete a pad without a recovery token. Combined with #7926 hiding the token disclosure when deletion needs no token (e.g. allowPadDeletionByAllUsers), a user who was allowed to delete could be left with no deletion UI at all. Pad deletion is unrelated to pad-wide settings, so: - Move #delete-pad out of the enablePadWideSettings block in pad.html; it is now always rendered and hidden by default. - Add a canDeletePad clientVar (isCreator || allowPadDeletionByAllUsers) and drive the button's visibility from it in pad_editor.ts, mirroring the existing canDeleteWithoutToken handling for the token disclosure. The two controls are now mutually coherent and neither depends on enablePadWideSettings: the plain button shows when this session can delete without a token, the recovery-token disclosure shows otherwise. Tests: - backend padDeletionUiPlacement.ts: #delete-pad is rendered with enablePadWideSettings both on and off (fails without the template move). - backend socketio.ts: canDeletePad reflects the creator/allow-all matrix, including a non-creator who only gains it under allowPadDeletionByAllUsers. - frontend pad_settings.spec.ts: asserts #delete-pad is no longer a descendant of #pad-settings-section. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/node/handler/PadMessageHandler.ts | 7 +++ src/static/js/pad_editor.ts | 7 +++ src/templates/pad.html | 14 +++--- .../backend/specs/padDeletionUiPlacement.ts | 44 ++++++++++++++++++ src/tests/backend/specs/socketio.ts | 45 +++++++++++++++++++ .../frontend-new/specs/pad_settings.spec.ts | 5 ++- 6 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/tests/backend/specs/padDeletionUiPlacement.ts diff --git a/src/node/handler/PadMessageHandler.ts b/src/node/handler/PadMessageHandler.ts index 4f18693cc1b..c8084f6d95c 100644 --- a/src/node/handler/PadMessageHandler.ts +++ b/src/node/handler/PadMessageHandler.ts @@ -1321,6 +1321,12 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => { const hasGetAuthorIdHook = (plugins.hooks.getAuthorId || []).length > 0; const hasDurableIdentity = hasGetAuthorIdHook && !!(user && user.username); const canDeleteWithoutToken = settings.allowPadDeletionByAllUsers || hasDurableIdentity; + // Whether this session may delete the pad with no token at all: the creator + // on this device (creator-cookie still present), or any user when the + // instance opted everyone in. Drives the plain "Delete pad" button, which is + // independent of enablePadWideSettings (issue #7959) — deletion is not a + // pad-wide setting and must stay reachable when that section is disabled. + const canDeletePad = isCreator || settings.allowPadDeletionByAllUsers; const padDeletionToken = isCreator && !canDeleteWithoutToken ? await padDeletionManager.createDeletionTokenIfAbsent(sessionInfo.padId) @@ -1346,6 +1352,7 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => { // redundant, so the client labels the action "Delete Pad" instead of // "Delete with token" (issue #7926). See showDeletionTokenModalIfPresent. canDeleteWithoutToken, + canDeletePad, // Allow-listed copy — settings.privacyBanner could carry extra nested // keys from a hand-edited settings.json; sending those by reference // would leak them to every browser. See getPublicPrivacyBanner(). diff --git a/src/static/js/pad_editor.ts b/src/static/js/pad_editor.ts index 0382a813a00..bd721e62cd8 100644 --- a/src/static/js/pad_editor.ts +++ b/src/static/js/pad_editor.ts @@ -159,6 +159,13 @@ const padeditor = (() => { $('#delete-pad-with-token').prop( 'hidden', !!(window as any).clientVars?.canDeleteWithoutToken); + // The plain "Delete pad" button is shown whenever this session can delete + // without a token (creator on this device, or allowPadDeletionByAllUsers). + // It is independent of pad-wide settings so it stays reachable when that + // section is disabled (issue #7959). + $('#delete-pad').prop( + 'hidden', !(window as any).clientVars?.canDeletePad); + // delete pad using a recovery token (second device / no creator cookie) $('#delete-pad-token-submit').on('click', () => { const token = String($('#delete-pad-token-input').val() || '').trim(); diff --git a/src/templates/pad.html b/src/templates/pad.html index 27f21948c55..1a1efe19b3d 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -391,13 +391,17 @@

<% e.end_block(); %> - <% } %> - + +