feat(acks): surface 999 rejections via sidebar badge + table sort
The 1,156 accepted acks were burying the 5 rejections — the operator had to scroll the whole table to find them. Two changes: 1. Sidebar '999 ACKs' nav item gets a warning-toned badge showing the total rejected segment count from aggregates.rejected_count (mirrors the Reconciliation unmatched badge pattern). One number, visible from any page. 2. Acks page table now sorts rejected rows to the top, then newest-id-first. All 5 rejections fit on page 1 since they're a tiny fraction of the total — no pagination needed. New files: - src/hooks/useAckStats.ts (lightweight aggregates-only fetch) - src/hooks/useAckStats.test.tsx (2 tests) - src/components/Sidebar.test.tsx (3 tests) Pre-existing baseline: 10 frontend failures (api.test.ts exportBatch837, tail-stream.test.ts acks/ta1_acks targeting, Inbox/InboxHeader copy). Unchanged.
This commit is contained in:
@@ -166,6 +166,69 @@ describe("Acks", () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2026-07-02: rejections were getting buried inside the accepted acks
|
||||
// (1,156 accepted + 5 rejected) — the operator had to scroll the
|
||||
// entire table to find the 5 they cared about. Fix: client-side sort
|
||||
// bubbles rejected rows to the top of page 1. Within each group
|
||||
// (rejected vs accepted) newest-id-first is preserved.
|
||||
// -------------------------------------------------------------------------
|
||||
it("test_rejected_acks_float_to_top_of_table", async () => {
|
||||
(api.listAcks as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 100,
|
||||
sourceBatchId: "b-newest-accepted",
|
||||
acceptedCount: 1,
|
||||
rejectedCount: 0,
|
||||
receivedCount: 1,
|
||||
ackCode: "A",
|
||||
parsedAt: "2026-07-01T12:00:00Z",
|
||||
},
|
||||
{
|
||||
id: 99,
|
||||
sourceBatchId: "b-rejected",
|
||||
acceptedCount: 0,
|
||||
rejectedCount: 1,
|
||||
receivedCount: 1,
|
||||
ackCode: "R",
|
||||
parsedAt: "2026-07-01T11:00:00Z",
|
||||
},
|
||||
{
|
||||
id: 98,
|
||||
sourceBatchId: "b-old-accepted",
|
||||
acceptedCount: 1,
|
||||
rejectedCount: 0,
|
||||
receivedCount: 1,
|
||||
ackCode: "A",
|
||||
parsedAt: "2026-06-25T12:00:00Z",
|
||||
},
|
||||
],
|
||||
total: 3,
|
||||
returned: 3,
|
||||
has_more: false,
|
||||
});
|
||||
|
||||
const { unmount } = renderIntoContainer(React.createElement(Acks));
|
||||
await waitForText("100");
|
||||
|
||||
// The first table row must be the rejected one (id=99), even though
|
||||
// id=100 has a higher id. After that, accepted rows sort newest-first
|
||||
// (id=100 before id=98).
|
||||
const tableRows = Array.from(
|
||||
document.querySelectorAll("table tbody tr"),
|
||||
).filter((row) => {
|
||||
// Exclude any TA1-section rows by requiring the sourceBatchId
|
||||
// pattern (TA1 rows have a different layout).
|
||||
return row.textContent?.includes("b-");
|
||||
});
|
||||
expect(tableRows.length).toBe(3);
|
||||
expect(tableRows[0]?.textContent).toContain("b-rejected");
|
||||
expect(tableRows[1]?.textContent).toContain("b-newest-accepted");
|
||||
expect(tableRows[2]?.textContent).toContain("b-old-accepted");
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("test_initial_render_shows_no_selection_highlight", async () => {
|
||||
// Three rows in the fixture so there's plenty to select; the test
|
||||
// asserts the *initial* state has no row marked selected.
|
||||
|
||||
Reference in New Issue
Block a user