# Progress, rewards, and review (/docs/use/progress)



When a member sends a quiz answer, a link or a photo, it isn't always complete straight away. Domino may still be checking it, or waiting for someone on your team to look at it. This page covers what members see while they wait, how your team reviews and confirms activity, and what your app can show.

For the model behind this guide, read [Quests](/docs/concepts/quests) and [Rewards](/docs/concepts/rewards). See [Compose a campaign](/docs/composition) for a complete example.

```mermaid
stateDiagram-v2
    state "Checking evidence" as Checking
    state "Waiting for a reviewer" as Review
    state "Recovery issue" as Recovery
    [*] --> Submitted
    Submitted --> Submitted: Rules changed, review and submit again
    Submitted --> Checking
    Submitted --> Review: Quest requires human review
    Checking --> Recovery: Technical failure
    Recovery --> Checking: Inspect the error, then retry
    Checking --> Complete: Evidence verified
    Review --> Complete: Evidence qualifies
    Review --> Rejected: Evidence does not qualify
    Complete --> [*]: Server applies reward rules
```

## Review a submission [#review-a-submission]

When a quest requires human review, an authorized reviewer decides whether the submitted evidence qualifies, such as whether a booth selfie really shows your booth. Make that decision from the evidence and the rules that applied to the attempt.

> [!WARNING] A technical failure is not a failed activity
> If verification fails because of a technical problem, treat it as a recovery issue rather than proof that the person failed the activity. Inspect the error before retrying verification. A technical failure does not mean the submission should be rejected.

## Confirm a staff quest [#confirm-a-staff-quest]

Some quests are completed by staff rather than by the member, for example checking someone in at a desk.

### 1. Open the member's page [#1-open-the-members-page]

In Console, open the member's page and find the quest.

### 2. Confirm the activity [#2-confirm-the-activity]

Choose **Confirm activity**, then **Confirm completion**. Domino queues the attempt and applies the quest's rules as for any other submission.

The button appears only for quests that staff confirm, and only to people with confirm permission. A [banned member](/docs/use/member-bans)'s page shows **Banned** at the top, and confirming activity for them is refused until they are unbanned.

## Updates in Console [#updates-in-console]

Open a bookmarked project page to return directly to that project. Console loads its page data alongside the access check and shows it only after access is confirmed. A link without a project first loads your available projects.

Overview, Activity, Members, and Rewards refresh every 10 seconds while Console is visible. Tracker status and reward deliveries also refresh while their views are open. Opening a tracker stops refreshing the hidden tracker list and follows the selected tracker's status instead.

Other pages, including quest editing, refresh after Console actions or when you return to the tab and the cached data is stale. They do not refresh on a timer. Reload the page to fetch changes made elsewhere immediately. Discord connection status and deployment updates have their own live update behavior.

## Completion and rewards [#completion-and-rewards]

* **The server decides.** Domino records completion and applies the quest's reward rules on the server. The app displays the resulting state.
* **The browser never grants.** A browser should never grant itself points or declare verification successful.
* **The completer earns the quest reward.** A quest reward belongs to the member who completes it.
* **Referral rewards are separate.** A [referral program](/docs/build/referrals) can separately reward their inviter, reward both people when the referral qualifies, or grant inviter milestones for distinct qualified friends.
* **Inviter bonuses are additional.** Inviter percentage bonuses are additional points; they do not reduce the friend's award.
* **Corrections can reduce referral earnings.** Unpaid corrections are offset against future referral payouts.

### Point balances follow the ledger ID [#point-balances-follow-the-ledger-id]

Point balances are keyed by ledger ID. Renaming a ledger in **Rewards → Point ledgers** does not change a member's progress, total, or transaction history.

Two ledgers with the same display name remain separate balances. Use their descriptions and IDs to distinguish them.

### Show a quest's rewards [#show-a-quests-rewards]

Your app can list what each quest pays, such as "10 XP" or "Tote bag", next to the quest.

> [!DEV]
> Each quest in `relay.quests.experience()` lists its rewards. Every reward has display text in `label` and a `kind`:
>
> | Kind          | Fields                                                   |
> | ------------- | -------------------------------------------------------- |
> | `points`      | `amount` and `ledger: { id, key, name }`                 |
> | `tier`        | `reward: { id, key, title }` and the `tier` it grants    |
> | `integration` | A Discord role, labelled "Discord role"                  |
>
> Show `label`, or build your own text from the fields. Add up points from `amount` rather than parsing labels. To tell ledgers or rewards apart, compare `ledger.key` or `reward.key` with the keys your code declared. Other reads return only IDs; see [Match ledgers in your app](/docs/concepts/points#match-ledgers-in-your-app).

## Keep progress through changes [#keep-progress-through-changes]

* **Accepted attempts keep their release.** Accepted attempts retain their original release. Publishing new rules does not silently rewrite an attempt already in progress.
* **Pending submissions are resubmitted.** If rules change before a submission is accepted, the app asks the member to review them and submit again.
* **Apps preserve input.** Developers should preserve compatible input and use the SDK's recovery controls after a lost network response.

For implementation details, read [Participant API](/docs/reference/participant-api).

## Explain why a quest is unavailable [#explain-why-a-quest-is-unavailable]

Your app can tell a member exactly why they can't take a quest yet: it's locked until they finish another quest, reach a point balance or join a group, or it's blocked because it's outside its dates, needs them to enroll first, or has run out of budget. It can also show when a daily or weekly budget resets.

> [!DEV]
> Render the quest view's `availability` instead of inferring eligibility from its last attempt. A locked view lists typed completion, balance, and group prerequisites. A blocked view names a stable reason such as `outside-window`, `enrollment-required`, or `budget-exhausted`. An `outside-window` block can include `window: "quest"`; tracker outcomes use `"tracker"` when the tracker's own collection window refused the record. Older responses omit this additive field. `questStatus(view)` keeps the detail on its result, and `questStatusText(view)` supplies English copy.
>
> The same view includes its campaign and enrollment time, effective window, and current budget capacity. A calendar budget reports when it resets. A lifetime budget uses `resetsAt: null`. Refresh the experience after enrollment, point changes, group changes, completions, or a calendar boundary because these are live eligibility observations.

## Event admission [#event-admission]

Scanning an event pass requires a verification integration for the campaign. A campaign can run its own verification on its server and use Relay to record pass ownership and submit the entry activity.

> [!IMPORTANT] A matching code is not a valid ticket
> Relay does not accept a ticket merely because its code matches an event format. If pass verification is not configured, ask the campaign operator for the supported check-in process.

## Consecutive-day streaks [#consecutive-day-streaks]

A streak counts consecutive days with verified activity in the [project calendar](/docs/concepts/calendar)'s timezone, such as checking in every day for a week. See [calendar-day streaks](/docs/concepts/quests#calendar-day-streaks) for the model. Your developer configures a milestone with the [streak authoring guide](/docs/reference/quest-rules#calendar-day-streaks).

The member's app shows:

* The current run and the target
* The best run
* Whether today counts
* When today ends (the next day boundary)

### How runs and days are counted [#how-runs-and-days-are-counted]

| Situation                               | What happens                                                                                                                                                                                                        |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Several actions on one day              | They count as one qualifying day.                                                                                                                                                                                   |
| No activity yet today                   | The run remains alive while today is still open.                                                                                                                                                                    |
| A whole day is missed                   | Only then does the run reset. There are no implicit grace days.                                                                                                                                                     |
| Delayed verified activity arrives       | It can repair a missing day.                                                                                                                                                                                        |
| A submission is reviewed on a later day | It counts on the day it was submitted, not the day it was accepted.                                                                                                                                                 |
| Discord events are missing              | They cannot be reconstructed automatically.                                                                                                                                                                         |
| A day ends                              | Days end at midnight in the project timezone, including daylight-saving changes.                                                                                                                                    |
| The project timezone is changed         | The change takes effect at the next midnight in the current zone and never rewrites recorded days. The run continues, and the first day in the new zone ends at its next midnight, so it can be shorter than usual. |

All streak quests in a project share its calendar, and so do its budgets and calendar leaderboards.

### How milestone rewards are paid [#how-milestone-rewards-are-paid]

* **Once per member.** Each streak quest awards its milestone once per member. Separate quests can reward different milestones.
* **No repeat payouts.** A later broken or repaired run does not repeat an earned reward.
* **Exhausted budgets are not retried.** If a milestone's reward budget is exhausted, progress stays recorded and the app reports that the milestone was reached without a reward. Budget changes do not automatically retry that payout.

## Show evaluator progress [#show-evaluator-progress]

Some checks wait and look again later, such as a post that needs five likes. While they wait, your app can show how far the member is, such as "3 of 5 likes", and when Domino checks next. Showing progress never completes the quest, and the member can't retry while the check waits.

> [!DEV]
> Read `QuestView.progress` from `relay.quests.experience()` or the corresponding React observation. When present, it contains `{ current, target, reason }`. Display the counts and reason without treating them as completion. The latest attempt owns this value; a newer attempt supersedes it. The value stays through a scheduled rerun and disappears when the attempt resolves, enters review, or fails.
>
> Participant progress and quest-view readers ignore added fields on trigger and availability objects, so a newer server can extend these responses. Known fields remain validated. Publication inputs still reject undeclared fields.
>
> For a deferred attempt, display `attempt.reason` and format `attempt.nextEvaluationAt` as the next check time. `questStatus(view)` returns a waiting tone with `evaluator-deferred` or `evaluator-progress`. These are open string codes, so keep a fallback. `availability.kind` remains `checking`; there is no member retry action while the timer waits.

An operator with review permission can stop a waiting attempt by rejecting it. Stopping a deferred attempt grants and revokes nothing. If it has already completed, use the existing reward adjustment path instead.

> [!DEV]
> Reject with `management.attempts.review(id, { generation, decision: "reject", reason })`. Use the generation from the current attempt. A stale generation is refused, so reload before retrying.

## Show earned rewards in an app [#show-earned-rewards-in-an-app]

Your app can show members what they've earned: reward tiers, whether they've collected them, pickup details, point awards and reversals, and the state of a Discord role. When a collected reward needs to be recovered, keep it visible with a follow-up message from your team. Members see a public explanation; the reason your team gave for revoking stays in Console and audit history. A delivery marked revoked or removed does not undo the history of its earlier delivery.

> [!DEV]
> Use `useEntitlements()` to render earned tiers, collection state and assigned pickup details. Use `useDeliveries()` to show point awards and reversals, tier entitlements and integration delivery states together. Both hooks return `{ status, data, error, refresh }`; render `data.items` when `status` is `ready`, and a sign-in prompt for `signed-out`. They read again when the member's activity feed reports a points award, a tier or a delivery change, and every 30 seconds otherwise. See the [participant API reference](/docs/reference/participant-api) for fields and unknown-state handling.

## Notify someone about an asynchronous outcome [#notify-someone-about-an-asynchronous-outcome]

Your app can pop up a message when something happens later, such as "Your photo was accepted" after a review, or a reward arriving. Keep the completion message separate from the reward: a reward can arrive after the quest is accepted.

> [!DEV]
> Use `useActivity({ onEvent })` once near the app's root to show a notification when an attempt is accepted, rejected or awaiting review, or when a reward changes. Read `event.kind` rather than comparing completion counts. The hook pauses while the tab is hidden and clears the previous member's events when the session changes. You do not need it to keep data current: `<RelayProvider>` follows the same feed and reads again only the hooks each event changes.
>
> Use the event's ID to prevent repeating an app-side effect after a retry. The feed contains changes created after the activity-feed migration; use the quest and reward snapshots to render current state on first load. A delivery event can follow acceptance later, so keep the completion message separate from fulfillment status.

## Distinct-action counters [#distinct-action-counters]

A counter quest completes after several separate actions, such as three different posts in a week. Your app can show the count so far and the goal. An action below the goal counts as progress, with no completion or reward yet.

In Console, open **Members**, select the member, and find the counter section. **Review actions** shows distinct keys, occurrence times, reasons and achievement outcomes. **Open attempt** opens its evaluation history. Operators with `review` permission enter a reason and select **Exclude action** or **Restore action** for unsettled qualifying evidence. Restoration does not immediately grant rewards: the next accepted action checks the goal. For a settled awarded achievement, review and revoke its completion; the counter keeps its evidence and marks the achievement revoked.

> [!DEV]
> Read a quest's `counter` alongside its attempt in member progress or a quest view. It contains the goal, current count, window bounds, settled achievement count and latest achievement. Within the current window, the latest achievement is the last settled batch even when older evidence arrived late. When the current window has none, the latest achievement can belong to an earlier calendar window; `questStatusText` reports its budget-denied or revoked result only while its window is current. A counter action below its goal has attempt status `progressed`.
>
> Server integrations use `management.members.counters.history(member, quest, { cursor, limit })` and `management.members.counters.moderate(member, quest, contribution, { actionId, excluded, reason })`. History requires `read`; correction requires `review` in both test and live environments. Preserve the action ID when retrying the same correction. Members receive their counter summary, not the management evidence history.
>
> The CLI uses the same routes, shown below. Set `correction.json` to a UUID `actionId`, an `excluded` boolean and a nonempty `reason`. Page history with `cursor` and `limit` (1–100, default 50).

```sh
domino api GET /members/MEMBER/counters/QUEST
domino api POST /members/MEMBER/counters/QUEST/CONTRIBUTION/moderate correction.json
```

Budget-denied achievements remain final; see [counter window and settlement rules](/docs/concepts/quests#distinct-action-counters).
