Skip to content

feat: add bulk rotate and background removal actions - #173

Open
bn326160 wants to merge 1 commit into
Anyesh:mainfrom
bn326160:feat/bulk-rotate-remove-background
Open

feat: add bulk rotate and background removal actions#173
bn326160 wants to merge 1 commit into
Anyesh:mainfrom
bn326160:feat/bulk-rotate-remove-background

Conversation

@bn326160

Copy link
Copy Markdown

Description

Adds two new bulk actions to the wardrobe grid's multi-select toolbar, mirroring the two single-item actions already in the item detail dialog (rotate left/right, remove background):

  • Bulk rotate (POST /items/bulk/rotate) - applies one chosen direction (cw/ccw) to every selected item, synchronously, following the same pattern as the existing bulk/delete (rotation is a fast in-process PIL op, so no queue is needed).
  • Bulk remove background (POST /items/bulk/remove-background) - queues one job per selected item via the existing arq worker, following the same pattern as the existing bulk/analyze, since background removal is CPU-heavy (rembg) and doing many of them synchronously in a single request risks a timeout. Adds a new remove_item_background_job worker function, registered alongside tag_item_image on the existing arq:tagging queue.

No new bulk-selection framework was introduced - both endpoints resolve item_ids/select_all/filters/excluded_ids the same way bulk/delete and bulk/analyze already do.

Also includes a small bug fix found while building and manually testing this: the new remove-background endpoint sets item.status = processing to drive the existing polling spinner, but initially didn't clear item.ai_started_at - unlike every other call site in the codebase that enters "processing". For an already-tagged item, this leaked its original tagging timestamp into the frontend's "elapsed analyzing time" display, showing wildly wrong durations (tens of minutes) for what is actually a multi-second job. Fixed, and covered by a regression test.

Related Issue

None filed beforehand - this came out of a conversation while trying the app out for the first time.

Type of Change

  • New feature (non-breaking change that adds functionality)

Checklist

  • I have read the CONTRIBUTING guide
  • My code follows the project's coding style
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally
  • I have updated documentation as needed (no docs needed a change beyond the code itself)
  • My changes don't introduce new warnings or errors

Testing

Test Environment

  • Docker Compose (dev overlay: docker-compose.yml + docker-compose.dev.yml)
  • Local development

Tests Performed

  • Added backend/tests/test_bulk_rotate_and_remove_background.py (13 tests) covering both new endpoints and the new worker job, including a regression test for the ai_started_at bug.
  • cd backend && pytest - 515 passed, 0 failed.
  • cd backend && ruff check . && ruff format --check . - clean.
  • python backend/scripts/check_migration_heads.py - OK (no migration needed for this change).
  • cd frontend && npm test - 144 passed.
  • cd frontend && npm run lint - clean (only pre-existing warnings unrelated to this change).
  • cd frontend && npx tsc --noEmit - clean.
  • cd frontend && npm run i18n:check - clean across all 8 locales.
  • Manually verified end-to-end against a real local instance with real cataloged items: rotated an item via the API and visually confirmed the image actually rotated (and rotated it back), queued background removal on real items and watched the worker process them, confirming original_image_path gets backed up and the resulting image has the requested solid background.

Additional Notes

  • The new i18n keys were added with the same English copy duplicated across all locale files (de/fr/it/ja/ko/zh-CN/zh-TW) to satisfy i18n:check's parity gate, since I'm not a native speaker of those languages - happy to have a translator improve the actual wording.
  • No Alembic migration: the queued remove-background job reuses the existing item.status field instead of adding a new column, so the existing polling/spinner UI works with no extra frontend plumbing. One accepted tradeoff from this: the spinner's caption during a background-removal job shows the generic "Queued" text rather than something like "Removing background..." (it no longer shows the wrong elapsed-time text after the ai_started_at fix, just a generic label). A processing_reason column would be the natural follow-up if this turns out to be confusing in practice - didn't want to add it preemptively for a cosmetic label.

Extends the existing multi-select bulk toolbar (which already had
delete/re-analyze) with two new actions mirroring the single-item
rotate and remove-background buttons already in the item detail
dialog:

- POST /items/bulk/rotate: synchronous, like bulk/delete, since
  rotation is a fast in-process PIL op.
- POST /items/bulk/remove-background: queued via the existing arq
  worker, like bulk/analyze, since background removal is CPU-slow
  (rembg) and doing 30 of them synchronously in one request risks a
  timeout. Adds a new remove_item_background_job worker function.

Also fixes a bug caught while building this: the new remove-background
endpoint set item.status = processing to drive the existing polling
spinner, but didn't clear item.ai_started_at like every other
"enter processing" call site does. For an already-tagged item this
leaked its original tagging timestamp into the frontend's "elapsed
analyzing time" display, showing wildly wrong durations for what is
actually a multi-second job.
@Anyesh Anyesh self-assigned this Aug 22, 2026
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.

2 participants