- ${isReview ? `
` : ''}
+ ${mode === 'browse' ? '' : `
`}
@@ -2374,15 +2407,20 @@
function updateSelectionCount(tab) {
const ids = getSelectedIds(tab);
const countEl = document.getElementById(tab === 'review' ? 'selectedCountReview' : 'selectedCountAll');
- const approveBtn = document.getElementById(tab === 'review' ? 'batchApproveBtn' : 'batchApproveBtnAll');
- const rejectBtn = document.getElementById(tab === 'review' ? 'batchRejectBtn' : 'batchRejectBtnAll');
+ // Approve / Reject buttons exist only in the Review tab — All Items
+ // batch bar (issue #129) deliberately omits them (status changes belong
+ // to per-row buttons or the keyboard workflow in Review).
+ const approveBtn = tab === 'review' ? document.getElementById('batchApproveBtn') : null;
+ const rejectBtn = tab === 'review' ? document.getElementById('batchRejectBtn') : null;
if (countEl) countEl.textContent = ids.length > 0 ? `${ids.length} selected` : '';
if (approveBtn) approveBtn.disabled = ids.length === 0;
if (rejectBtn) rejectBtn.disabled = ids.length === 0;
- // Bulk-edit buttons (issue #62) — enable when at least one item ticked.
- ['batchMoveCategoryBtn', 'batchMoveDomainBtn', 'batchAddTagBtn', 'batchRemoveTagBtn', 'batchSetAudienceBtn'].forEach(id => {
- const btn = document.getElementById(id);
+ // Bulk-edit buttons (issue #62 + #129) — Review tab uses bare IDs;
+ // All Items tab adds the "All" suffix. Each tab toggles its own set.
+ const suffix = tab === 'review' ? '' : 'All';
+ ['batchMoveCategoryBtn', 'batchMoveDomainBtn', 'batchAddTagBtn', 'batchRemoveTagBtn', 'batchSetAudienceBtn'].forEach(base => {
+ const btn = document.getElementById(base + suffix);
if (btn) btn.disabled = ids.length === 0;
});
}
diff --git a/tests/test_admin_memory_page_all_items_batch_bar.py b/tests/test_admin_memory_page_all_items_batch_bar.py
new file mode 100644
index 0000000..6e442a1
--- /dev/null
+++ b/tests/test_admin_memory_page_all_items_batch_bar.py
@@ -0,0 +1,84 @@
+"""GET /admin/corporate-memory page — All Items tab batch bar (issue #129).
+
+Follow-up to #62 / PR #126 which shipped the bulk-edit batch bar in the
+Review tab only. This test guards the symmetric bar on the All Items tab:
+
+- batch-bar block visible on page render (regardless of pending count)
+- the 5 bulk-edit actions ship with distinct ``*BtnAll`` IDs so they don't
+ collide with the Review tab's bare-ID buttons
+- Approve / Reject are intentionally absent — those stay scoped to Review
+ per the issue's scope decision
+"""
+
+from __future__ import annotations
+
+
+def _auth(token: str) -> dict:
+ return {"Authorization": f"Bearer {token}"}
+
+
+class TestAllItemsBatchBar:
+ def test_admin_page_renders_all_items_batch_bar(self, seeded_app):
+ c = seeded_app["client"]
+ token = seeded_app["admin_token"]
+ resp = c.get("/admin/corporate-memory", headers=_auth(token))
+ assert resp.status_code == 200
+ body = resp.text
+
+ # All five bulk-edit buttons present with the All-suffix IDs the JS
+ # plumbing (`updateSelectionCount('all')`) toggles.
+ for btn_id in (
+ "batchMoveCategoryBtnAll",
+ "batchMoveDomainBtnAll",
+ "batchAddTagBtnAll",
+ "batchRemoveTagBtnAll",
+ "batchSetAudienceBtnAll",
+ ):
+ assert f'id="{btn_id}"' in body, f"missing button id={btn_id}"
+
+ # Select-all checkbox + count span scoped to All Items.
+ assert 'id="selectAllAll"' in body
+ assert 'id="selectedCountAll"' in body
+ assert "toggleSelectAll('all')" in body
+
+ def test_all_items_bar_omits_approve_reject(self, seeded_app):
+ """Approve / Reject are Review-only by design (issue #129 scope)."""
+ c = seeded_app["client"]
+ token = seeded_app["admin_token"]
+ resp = c.get("/admin/corporate-memory", headers=_auth(token))
+ assert resp.status_code == 200
+ body = resp.text
+
+ # Bare-suffix Review buttons stay; *BtnAll variants of approve/reject
+ # must NOT appear — otherwise the JS in updateSelectionCount('all')
+ # would silently enable a status-change action the All-tab UX hasn't
+ # signed off on.
+ assert 'id="batchApproveBtn"' in body # Review tab still has it
+ assert 'id="batchApproveBtnAll"' not in body
+ assert 'id="batchRejectBtnAll"' not in body
+
+ def test_browse_tab_omits_row_checkbox(self, seeded_app):
+ """Regression guard for the adversarial-review finding: widening
+ ``renderItemCard``'s checkbox gate from "Review-only" to "Review or
+ All" must NOT also render the checkbox in the Browse tab — Browse
+ has no batch bar of its own, so an orphan checkbox there would
+ fire ``updateSelectionCount('all')`` against an invisible tab and
+ confuse the UX.
+
+ The renderItemCard signature is now a ``mode`` enum
+ (``'review' | 'all' | 'browse'``); the Browse path passes
+ ``'browse'`` and the function early-returns no checkbox markup
+ for that mode. Both invariants are checked at template level so
+ a future refactor can't silently regress either.
+ """
+ c = seeded_app["client"]
+ token = seeded_app["admin_token"]
+ resp = c.get("/admin/corporate-memory", headers=_auth(token))
+ assert resp.status_code == 200
+ body = resp.text
+
+ # Browse-tab call site uses the 'browse' mode literal.
+ assert "renderItemCard(it, idx, 'browse')" in body
+ # And renderItemCard's checkbox is gated on `mode === 'browse'` so
+ # the input element is omitted entirely on that branch.
+ assert "mode === 'browse' ? ''" in body