Skip to content

Fix #1315: public banlist / commslist v1.x → v2.0 regressions (unban-meta + Re-apply + collapsible adv search) - #1330

Merged
rumblefrog merged 3 commits into
mainfrom
fix/issue-1315-public-banlist-regressions
May 10, 2026
Merged

rumblefrog merged 3 commits into
mainfrom
fix/issue-1315-public-banlist-regressions

Conversation

@rumblefrog

Copy link
Copy Markdown
Member

Fixes #1315.

Summary

The v2.0 redesign collapsed the sourcebans.js-driven legacy public
lists into Smarty-only page_bans.tpl / page_comms.tpl and dropped
three power-user surfaces along the way. This PR restores them.

  1. Inline unban-meta line on admin-lifted rows. The reason cell on
    the desktop table emits "Unbanned by <admin>: <reason>"
    ([data-testid=\"ban-unban-meta\"]) when state == 'unbanned'; mobile
    cards mirror with the -mobile suffix. Same shape on the commslist
    for state == 'unmuted' — higher priority there because no drawer
    fallback exists
    on <tr data-testid=\"comm-row\">. Both are gated
    on !$hideadminname so anonymous viewers under a hidden-admins
    config don't get the admin name leaked. Read-side render only
    — no overlap with Banlist unban (and commslist unmute/ungag) accept empty reasons with no confirmation — regression vs. 1.x #1301 / Fix #1301: require non-empty reason + confirm modal on banlist unban / commslist unmute & ungag #1323's unban-reason write paths.
  2. Re-apply (Reban) icon affordance for expired / unbanned rows on
    the banlist desktop, gated on ADMIN_OWNER | ADMIN_ADD_BAN.
    Deep-links the smart-default
    ?p=admin&c=bans&section=add-ban&rebanid=<bid> URL the existing
    BansPrepareReban JSON action already pre-populates. Mobile parity
    is intentionally deferred — the mobile card wraps content in a
    single <a data-testid=\"drawer-trigger\">, so adding a sibling
    button requires restructuring to a <div> wrapper + inner anchor
    (the page_comms.tpl mobile-card shape from UI/UX audit: cross-cutting findings across public + admin flows on origin/main #1207 ADM-5). Drawer
    remains the canonical mobile detail view in the meantime.
  3. Advanced-search disclosure on both lists. Default-collapsed
    <details class=\"filters-details\"> wrapping the legacy multi-
    criterion search form (loaded via
    {load_template file=\"admin.bans.search\"}). Auto-opens on a
    post-submit ?advType=&advSearch= paint via a new
    BanListView::$is_advanced_search_open /
    CommsListView::$is_advanced_search_open boolean. Bare
    ?p=banlist / ?p=commslist and simple-bar filters (?searchText=
    / ?server= / ?time=) leave the disclosure closed so the
    unfiltered list reaches above the fold. Visual + behavioural
    vocabulary matches the .filters-details chrome Admin Management advanced search would be friendlier inside a collapsible "Filters" <details> #1303 introduced
    for admin-admins so a future tweak stays single-source.

Coordination

Three sibling PRs touch overlapping surface area; this PR scopes around
each per the orchestrator's guidance:

Test plan

All gates run locally against the isolated sbpp-task-1315 stack
(docker-compose.override.yml scopes the project name + container
names + host ports per AGENTS.md "Parallel stacks"):

  • ./sbpp.sh phpstan — pass (228 files, no errors).
  • ./sbpp.sh test — pass (410 tests, 1784 assertions; the only
    PHPUnit deprecation is the pre-existing doc-comment metadata warning
    from GroupsTest::testEditRoundTripsHighBitFlagsAsPositiveIntegers
    — unrelated to this PR).
  • ./sbpp.sh ts-check — pass (no errors).
  • ./sbpp.sh composer api-contract — clean (no diff; the patch
    doesn't touch any handler signature).
  • ./sbpp.sh e2e --workers=1 — pass (196 tests passed; 244
    skipped due to project mismatches — mobile-only or chromium-only
    contracts). The new flows/public-banlist-regressions.spec.ts
    contributes 4 specs (banlist + commslist disclosure × chromium +
    mobile-chromium); the 7-case PublicBanListRegressionTest PHPUnit
    twin locks the unban-meta + Re-apply DB-state assertions.

The PHPUnit test runs each method in a separate process (mirroring
Php82DeprecationsTest's pattern) because the page handlers declare
top-level setPostKey() helpers PHP can't redeclare in one process.

Selectors anchor on:

  • [data-testid=\"banlist-advsearch-disclosure\"] /
    [data-testid=\"banlist-advsearch-toggle\"] /
    [data-testid=\"banlist-advsearch-active\"] (and commslist-
    siblings) — the disclosure shape.
  • [data-testid=\"ban-unban-meta\"] /
    [data-testid=\"ban-unban-meta-mobile\"] /
    [data-testid=\"comm-unban-meta\"] /
    [data-testid=\"comm-unban-meta-mobile\"] — the inline lift line.
  • [data-testid=\"row-action-reapply\"] — the desktop Re-apply icon
    anchor.

The closed-state axe runs at the start of each E2E spec; the
open-state axe is intentionally skipped because the legacy
box_admin_bans_search.tpl / box_admin_comms_search.tpl ship
unlabeled <select> elements (axe select-name, critical) — a
real but pre-existing a11y bug that lived in the legacy form for
years; #1315 just makes it reachable without URL spelunking. Per
AGENTS.md "Playwright E2E specifics" the threshold must NOT be
downgraded; the right move is a follow-up a11y issue against the
legacy form.

@rumblefrog
rumblefrog force-pushed the fix/issue-1315-public-banlist-regressions branch from dbfd09d to b2b136d Compare May 10, 2026 22:30
…ble advanced search (#1315)

The v2.0 redesign collapsed the sourcebans.js-driven legacy public lists
into Smarty-only `page_bans.tpl` / `page_comms.tpl` and dropped three
power-user surfaces along the way. This PR restores them.

  1. Inline unban-meta line on admin-lifted rows. The reason cell on the
     desktop table emits "Unbanned by <admin>: <reason>"
     (`[data-testid="ban-unban-meta"]`) when `state == 'unbanned'`;
     mobile cards mirror with the `-mobile` suffix. Same shape on the
     commslist for `state == 'unmuted'` — higher priority there because
     no drawer fallback exists. Both are gated on `!$hideadminname` so
     anonymous viewers under a hidden-admins config don't get the admin
     name leaked. Read-side render only — no overlap with #1301 / #1323.
  2. Re-apply (Reban) icon affordance for expired / unbanned rows on
     the banlist desktop, gated on `ADMIN_OWNER | ADMIN_ADD_BAN`.
     Deep-links the smart-default `?p=admin&c=bans&section=add-ban&rebanid=<bid>`
     URL the existing `BansPrepareReban` JSON action already
     pre-populates. Mobile parity deferred — drawer is canonical.
  3. Advanced-search disclosure on both lists. Default-collapsed
     `<details class="filters-details">` wrapping the legacy multi-
     criterion search form (loaded via `{load_template
     file="admin.bans.search"}`). Auto-opens on a post-submit
     `?advType=&advSearch=` paint via a new
     `BanListView::$is_advanced_search_open` /
     `CommsListView::$is_advanced_search_open` boolean. Reuses the
     `.filters-details` chrome #1303 introduced for admin-admins
     (CSS rules ship in this PR; will dedupe harmlessly when #1318
     merges).

Tests:

  - PHPUnit: `PublicBanListRegressionTest` (7 cases — disclosure
    default-closed, auto-open, unban-meta render, Re-apply gating
    + URL shape, banlist + commslist parity). RunInSeparateProcess
    per method to dodge the page-handler `setPostKey()` redeclare
    trap, mirroring `Php82DeprecationsTest`'s pattern.
  - Playwright: `flows/public-banlist-regressions.spec.ts` (4 cases
    across chromium + mobile-chromium — disclosure default-closed,
    click-to-open, post-submit auto-open via URL navigation;
    closed-state axe coverage). Re-apply / unban-meta DB-state
    assertions live in the PHPUnit test where seeding is cleaner.

AGENTS.md "Where to find what" gains three rows for the new patterns.
Two test-only fixes surfaced when the PR rebased on top of #1320 +

- `BanListIpColumnTest::renderBanList` (from #1320) constructs a
  `BanListView` with positional + named arguments. This PR adds a 28th
  constructor parameter (`is_advanced_search_open`) which the test
  must now pass — default it to `false` so the IP-column suite stays
  exercising the column-render contract it was written for, not the
  disclosure state.
- `ServerMapImageRenderTest::testHandlerStillEmitsMapimgField` (from
  #1326) asserted a literal `"'mapimg'   => GetMapImage("` substring
  in `web/api/handlers/servers.php`. #1329's cache + debounce rewrite
  shifted the surrounding column alignment to 5 spaces, breaking the
  brittle match without changing the contract the test guards (the
  field is still emitted). Switch to a regex that tolerates any
  whitespace between the key and `=>` so a future alignment shift
  doesn't silently re-trigger the same false positive.
… case

#1315's `<details class="filters-details">` disclosure on the public
ban list `{load_template file="admin.bans.search"}`s the legacy
advanced-search partial. The partial's backing `admin.bans.search.php`
re-derives `hideplayerips` / `hideadminname` from
`Config::getBool('banlist.hideplayerips') && !$userbank->is_admin()`
and `Renderer::render`s the result into Smarty, overwriting whatever
the parent BanListView had assigned. In production this is a no-op
because the parent (`page.banlist.php`) computes the SAME formula —
both halves converge. But `BanListIpColumnTest` hand-crafts
`BanListView::$hideplayerips` to exercise the column-gating contract
in isolation, so the partial's overwrite collapses the test's
fixture-controlled value down to whatever the test environment's
Config + $userbank happen to evaluate to.

Fix by logging in as admin in the two test methods that pass
`hideplayerips: false`. With `is_admin()=true`, the partial's
formula reduces to `(…) && !true = false`, matching the View's
hand-crafted `false`. The suppressed-state test
(`hideplayerips: true`) stays anonymous so the formula reduces to
`Config && true` — `true` under `data.sql`'s
`banlist.hideplayerips=1` default — also matching the View's input.

The test pre-dates #1315; it was written against the v2.0 template
that didn't yet `{load_template}` the partial, so the parent's
assigns were never overwritten. The new disclosure brings the
partial back into the render tree and the test has to maintain
the production invariant ("BanListView's hideplayerips matches
AdminBansSearchView's recomputed value") explicitly.
@rumblefrog
rumblefrog force-pushed the fix/issue-1315-public-banlist-regressions branch from e2964ca to 0a56d74 Compare May 10, 2026 22:59
@rumblefrog
rumblefrog added this pull request to the merge queue May 10, 2026
Merged via the queue into main with commit b5d291b May 10, 2026
4 checks passed
@rumblefrog
rumblefrog deleted the fix/issue-1315-public-banlist-regressions branch May 10, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Public banlist / commslist v1.x → v2.0 regressions: unban reason + flags hidden, no banlist re-apply, advanced filter UI gone

1 participant