Skip to content

fix(mods): restore Delete affordance with confirm-reason dialog (#1397) - #1400

Merged
rumblefrog merged 1 commit into
mainfrom
fix/1397-mod-delete-confirm
May 17, 2026
Merged

fix(mods): restore Delete affordance with confirm-reason dialog (#1397)#1400
rumblefrog merged 1 commit into
mainfrom
fix/1397-mod-delete-confirm

Conversation

@rumblefrog

Copy link
Copy Markdown
Member

Fixes #1397.

Summary

  • Replaces the dead onclick="RemoveMod(this.dataset.modName, this.dataset.modId);" on every mod-row trash button (an unguarded reference to a v1.x sourcebans.js helper deleted at Modernize the default web panel theme (2026 redesign) #1123 D1) with the canonical data-action="mod-delete" + <dialog id="mod-delete-dialog"> confirm + optional-reason flow. Mirrors the fix(brand): ship default themes/default/logos/sbpp_logo.png brand mark #1352 admins-delete shape byte-for-byte modulo the action name + row testid + counter element — same anti-pattern, same fix shape, just on a sibling surface.
  • Extends api_mods_remove to accept an optional ureason (trimmed, audit-log suffix omitted when empty). Symmetric with api_admins_remove / api_bans_unban / api_comms_unblock. v2.0 4-line header replaces the legacy 12-line block on mods.php.
  • Adds the missing audit trail: pre-fix the (broken) Delete affordance never logged anything; post-fix the audit log carries MOD (X) has been deleted. with an optional Reason: … suffix when the operator filled in the dialog's textarea.

Test plan

  • ./sbpp.sh phpstan (level 5, dba) — clean
  • ./sbpp.sh test (651 PHPUnit tests, includes new Sbpp\Tests\Integration\ModsDeleteDialogTest + extended Sbpp\Tests\Api\Mods with testRemoveAppendsReasonToAuditLog / testRemoveOmitsReasonSuffixWhenEmpty / testRemoveAcceptsMissingReasonParam)
  • ./sbpp.sh ts-check — clean
  • ./sbpp.sh composer api-contract — regenerated; the docblock on api_mods_remove tightened the ApiModsRemoveResponse typedef.
  • CI=1 ./sbpp.sh e2e (266 passed, includes new mod-delete-confirm.spec.ts — covers confirm-with-reason flow + optional-empty-reason variant; both assert no uncaught console errors, audit-log content, in-place row removal, and count-badge decrement)
  • Plugin build: not applicable (no .sp touched)
  • Prod Docker image: release-only gate; no docker/ files touched

Files changed

  • AGENTS.md — extends the onclick="<Helper>(...)" anti-pattern callout with the sister RemoveMod shape, and adds #mod-delete-dialog as the fourth canonical reference under "Add a confirm + reason modal for an irreversible row-level action".
  • web/themes/default/page_admin_mods_list.tpl — dead onclickdata-action; new <dialog> + inline page-tail script; {$mod_count} wrapped in <span data-testid="mod-count">.
  • web/api/handlers/mods.phpapi_mods_remove accepts ureason; v2.0 header replaces legacy block.
  • web/scripts/api-contract.js — regenerated from the tightened docblock.
  • web/tests/api/ModsTest.php — three new ureason-coverage tests + helper latestLogMessage.
  • web/tests/integration/ModsDeleteDialogTest.php (new) — seven per-attribute regression assertions mirroring AdminsDeleteDialogTest.
  • web/tests/e2e/specs/flows/mod-delete-confirm.spec.ts (new) — end-to-end happy path + optional-reason variant.

The trash-can button on `?p=admin&c=mods` carried an unguarded
`onclick="RemoveMod(this.dataset.modName, this.dataset.modId);"`
that referenced a v1.x sourcebans.js helper deleted at #1123 D1.
Every click threw `ReferenceError: RemoveMod is not defined` and
the delete never fired — from the operator's POV "Delete just
doesn't work."

This is the loud sister of #1352's admins-delete bug. Same
anti-pattern (inline `onclick` calling a long-removed helper), same
structural fix (mirror the canonical `data-action` + `<dialog>` +
inline page-tail dispatcher shape from `page_admin_admins_list.tpl`,
route through `Actions.ModsRemove`, capture an optional reason for
the audit log).

Server: `api_mods_remove` now accepts an optional `ureason`, trims
it, and appends `Reason: <reason>` to the audit-log body when
non-empty — symmetric with `api_admins_remove` / `api_bans_unban`
/ `api_comms_unblock`. Empty / omitted reason flows through
unchanged so the no-JS / no-dispatcher fallback still works.

Chrome: the page-tail script flips the busy contract on the
submit button (`window.SBPP.setBusy` with local fallback for
themes that strip theme.js), reads / decrements the new
`[data-testid="mod-count"]` badge, removes the row in place, and
fires `window.SBPP.showToast` for confirmation. Cancel closes
without firing the API; missing dispatcher falls back to the mods
list landing (no legacy GET handler for `o=remove`).

Test plan
---------
- ./sbpp.sh phpstan                     ✓
- ./sbpp.sh test                        ✓ (651 tests, includes
                                            new ModsDeleteDialogTest
                                            + extended ModsTest)
- ./sbpp.sh ts-check                    ✓
- ./sbpp.sh composer api-contract       ✓ (regenerated +
                                            tightened typedef)
- CI=1 ./sbpp.sh e2e                    ✓ (266 passed, includes
                                            new mod-delete-confirm
                                            spec — 2 tests covering
                                            confirm-with-reason +
                                            optional-empty-reason)

Mirrors #1352's admins-delete framing; the only delta is the optional-reason
contract is the same shape (mods being a lifecycle action, not a
moderation flip — same justification as admins-delete).
@rumblefrog
rumblefrog added this pull request to the merge queue May 17, 2026
Merged via the queue into main with commit a84f435 May 17, 2026
6 checks passed
@rumblefrog
rumblefrog deleted the fix/1397-mod-delete-confirm branch May 17, 2026 18:49
@github-actions github-actions Bot locked and limited conversation to collaborators May 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2.0 rc3 - Manage Mods - Can't delete a mod

1 participant