Files
Stirling-PDF/app
EthanHealy01 f6661a8f87 Failure action slots, resolve transition, and the bell that renders them (Review Flow PR 5a) (#7761)
Review Flow PR 5a — the first half of #7479, which stays open for
reference until both halves land. This PR is the ranking and the
bookkeeping; #7762 adds the retry handlers. Merging both reproduces
#7479's diff byte-for-byte.

## What's added

**The action slot model (backend).** `FailureActionSlot` ranks each of a
kind's offers as its `RESOLUTION`, `SECONDARY` or `OVERFLOW`.
`FailureKind` now declares placement per offer — the password-protected
kind names `DECRYPT_AND_RETRY` as its resolution, `UNKNOWN` leads with a
plain `RETRY` — and `FailureActionId` gains those two ids. The
declarations are data; their client handlers arrive in the follow-up, so
this build withholds them with a reason rather than rendering unwired
buttons (the same forward-compatibility #7478 relied on).

**A resolve transition.** `POST /api/v1/notifications/{id}/resolved`
lets a client report a failure fixed. `NotificationSource.parse` turns a
qualified notification id back into the source that owns it, and
`FileRunEventService` folds the resolution into the incident rather than
deleting it.

**`viewerReviewsTeam` on the list response.** A member sees only rows
whose document this browser holds — they can neither open nor fix
anything else — while a team reviewer keeps every row.

**The bell renders the ranking** (`promoteActions`): one primary button,
at most one secondary, the rest in an overflow menu beside **Copy log**.
The row's body is the kind's own sentence; the raw failure message moves
into the menu.

**Read state is a timestamp, not a row id.** `readThroughAt` replaces
`lastSeenId`: when a resolved or dismissed row leaves the list, the rows
below it stay read instead of re-lighting the badge.

## How to test

Needs a proprietary or SaaS build with login enabled (`task dev:all`,
sign in).

1. **Create a failure.** Add a password-protected PDF to the editor and
choose **Skip for now**; the upload's policy run fails on it.
2. **Open the bell.** The row reads the kind's sentence, not a stack
trace. Its primary button is **View file** — the server offers Decrypt
and retry as the resolution, but this build withholds it (handler lands
in the follow-up), so the best renderable offer is promoted instead.
3. **Open the row's ⋯ menu.** View in processor and Dismiss sit there,
along with **Copy log**, which copies the raw message.
4. **Check the read marker survives a departure.** With two failures,
open the bell (badge clears), dismiss the newer row, and refresh: the
badge stays dark. On main, the marker held the departed row's id and the
older row re-read as unread.
5. **Member visibility.** As a plain member, a failure recorded from
another browser does not appear in the bell; as a team reviewer it does.
6. **Resolve endpoint.** `POST
/api/v1/notifications/failure-{eventId}/resolved` as the owner removes
the row on the next poll; `NotificationResolveTest` pins refusal for a
non-owner, an unknown id, and a foreign prefix.

## Migration

None.
2026-09-01 13:25:19 +00:00
..