Skip to content

Fix Recycle Bin 'Empty bin' truncation past 200 items - #101

Merged
epeicher merged 2 commits into
trunkfrom
fix/97-recycle-bin-empty-truncation
May 7, 2026
Merged

Fix Recycle Bin 'Empty bin' truncation past 200 items#101
epeicher merged 2 commits into
trunkfrom
fix/97-recycle-bin-empty-truncation

Conversation

@epeicher

@epeicher epeicher commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

desktop_mode_recycle_bin_empty() previously fetched one page of 200 items and returned success even when more items remained. Users with a >200-item bin saw a "success" toast next to a still-non-empty bin.

The 200-item per-call cap is intentional (PHP timeout protection on huge bins), so the fix keeps the cap but makes it filterable, has the client iterate until done, and surfaces progress to the user.

Changes

Server (includes/recycle-bin/store.php)

  • desktop_mode_recycle_bin_empty() now reads its chunk cap via the new desktop_mode_recycle_bin_empty_chunk_size filter (default 200, floored to 1).
  • Doc comments expanded so callers know the function is one-chunk-per-call by design.

Client (new src/recycle-bin/empty-loop.ts + updates to src/recycle-bin/index.ts)

  • New runEmptyLoop() driver that calls the server until remaining === 0, bails on no-progress (purged === 0 && skipped > 0 means every leftover is capability-blocked), with a 1000-iteration safety ceiling.
  • handleEmpty() now delegates to runEmptyLoop. While running, the Empty bin button gets disabled + aria-busy and its label cycles "Emptying..." then "Emptying... N of M". Slotted dashicon preserved by mutating only a trailing span.

Tests

  • tests/phpunit/tests/recycleBinStore.php (new): 250-item case (single call leaves remaining > 0), iterate-to-empty case, filter honored, floor-to-1.
  • tests/vitest/recycle-bin-empty-loop.test.ts (new): 250-item-with-200-chunk, single-call completion, progress callback ordering, no-progress bail, partial-skip iteration, iteration-cap guard.

Docs

  • docs/hooks-reference.md: new filter entry next to the other recycle-bin filters.

Verified

  • npm run build clean
  • npm run lint clean
  • tsc --noEmit clean
  • npm run test:js: 88 files / 809 tests pass (includes 6 new vitest cases)
  • PHP unit tests not run locally; covered by CI

Open question

Used @since 0.21.1 for the new filter to match the aspirational versioning already in store.php (the file uses 0.19.0/0.21.0 even though the plugin is at 0.7.2). Easy to change if a different label is preferred.

Closes #97

Open WordPress Playground Preview

The server caps `desktop_mode_recycle_bin_empty()` at one chunk per
call so PHP doesn't time out on huge bins. The client was calling
the endpoint exactly once and treating the result as final, so on a
250-item bin the user saw "emptied" with 50 items still in the bin.

* Keep the server cap, but make it filterable via
  `desktop_mode_recycle_bin_empty_chunk_size` (default 200) for
  sites with larger PHP execution budgets.
* Move the iteration to the client. New `runEmptyLoop()` driver
  calls the endpoint until `remaining` hits zero, bails when no
  progress is possible (every leftover item capability-blocked),
  and surfaces progress to the Empty bin button so the user sees
  "Emptying... 200 of 500" instead of a frozen UI.
* Add PHP coverage for the chunk cap, the iterate-to-empty pattern,
  and the new filter.
* Add vitest coverage for the loop driver: completion, early-exit
  on partial-skip, and the iteration-cap guard.
* Document the new filter in `docs/hooks-reference.md`.

Fixes #97
@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

Two related fixes so the recycle-bin dock badge updates the moment
the bin is drained, instead of lingering with the pre-empty count
until the post-empty refresh round-trip lands.

1. badge.ts: route module-level state through createSharedStore.

   `_current`, `_seenTs`, `_started`, and `_countUrl` were
   plain `let`s. The module is imported by both the always-on
   shell bundle (`desktop.js`) and the lazy bin bundle
   (`recycle-bin.js`), so each bundle compiled its own copy
   and mutations from one were invisible to the other. The bin
   window's `setRecycleBinBadge` calls would update the bin
   bundle's `_current`, while the desktop bundle's
   WINDOW_CLOSED lifecycle handler would repaint the dock from
   its own stale `_current`. AGENTS.md's "Cross-bundle state"
   section flags this exact pattern as non-negotiable.

2. recycle-bin/index.ts: optimistic zero on full empty.

   `handleEmpty` now calls `setRecycleBinBadge(0)` as soon as
   `runEmptyLoop` reports `stoppedBecause: 'empty'`, before the
   subsequent `refresh()` reconciles authoritatively. Removes
   the visible lag during the post-empty REST round-trip.
@epeicher
epeicher merged commit 1b4d769 into trunk May 7, 2026
5 checks passed
@epeicher
epeicher deleted the fix/97-recycle-bin-empty-truncation branch May 7, 2026 15:19
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.

Recycle Bin 'Empty bin' silently stops after 200 items

1 participant