feat(admin): remove or restore a photo's background from the review queue (#281)
Adds the per-photo control that closes out the background-removal feature: each photo in the review queue now gets a "Remove background" or "Restore original" button, whichever matches its current state, and the button only appears when the server reports a sidecar is configured. The label is read from original_image_path alone rather than a second flag, so there is nothing that could disagree with what the button actually does.
draftsApi.ts's fetchDrafts now returns { drafts, backgroundRemoval } instead of a bare Draft[], matching the breaking change Task 6 made to GET /api/admin/item-drafts. DraftImage gains original_image_path, and a new setImageBackground(itemId, imageId, action) posts to the remove-background/restore-original endpoints, preferring the server's error message the same way publishDraft does.
Also updates docs/ops/image-background-removal-stack.md: the status line no longer says "evaluated, not adopted", since the feature is adopted here, and the closing "If this is adopted" section is replaced with "How the application uses it", describing the two real entry points (the drafting worker's default-on checkbox, and this per-photo control) and confirming that nothing in the feature deletes a file or a row.
Adds an e2e case asserting the button's label appears on a freshly submitted item's card, scoped to that card by the sender's note per #241. It is unrun in this environment — the local stack was not started, per standing instruction not to run start-local.ps1 or Playwright without the user's supervision.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,4 +98,23 @@ test.describe('The review queue', () => {
|
||||
await dialog.getByRole('button', { name: 'Cancel', exact: true }).click();
|
||||
await expect(card.getByText(/nobody chose this/)).toBeVisible();
|
||||
});
|
||||
|
||||
// The control that makes a poor cut survivable. Its label is its state:
|
||||
// "Remove background" until an original has been recorded, "Restore
|
||||
// original" afterwards, read from one field rather than two that could
|
||||
// disagree.
|
||||
//
|
||||
// Only the label is asserted, not a click. Pressing it would need a sidecar,
|
||||
// and a test that depends on a service taking forty seconds to start is
|
||||
// broken by construction — the swap itself is covered in the integration
|
||||
// suite against a stub.
|
||||
test('offers to remove the background on each photo', async ({ page, admin }) => {
|
||||
const note = `Cutout ${RUN}`;
|
||||
await submitAnItem(page, note);
|
||||
|
||||
await admin.open('Review queue');
|
||||
|
||||
const card = page.locator('.ant-card').filter({ hasText: note });
|
||||
await expect(card.getByRole('button', { name: 'Remove background' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user