> For the complete documentation index, see [llms.txt](https://docs.velthoryn.site/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.velthoryn.site/architecture-decisions/adr-fe-003-campaign-lifecycle.md).

# ADR-FE-003: Campaign Lifecycle FSM

**Status:** Accepted

## Context

Before Week 8, the frontend determined campaign display state using a single `cancelledAt != null` check. This caused two user-visible bugs:

1. Instantly-refunded campaigns still showed a "Grace Period Active -- Needs Action" banner.
2. Campaigns where all beneficiaries had claimed during the grace period also showed the false banner.

`cancelledAt` alone cannot distinguish four distinct post-cancel states.

## Decision

Export a `CampaignLifecycle` type from `apps/web/src/lib/vesting/list.ts` with 8 states:

```
active | paused | claimable | claimed |
cancelled_grace | cancelled_expired | instant_refunded | settled
```

Add `isGracePeriodVisible()` helper: returns `true` only when all three conditions hold -- `cancelledAt != null`, `instantRefunded === false`, and `streamSettled === false`. The beneficiary API at `/api/beneficiary/[address]/vesting-progress` was updated to return `instantRefunded` and `streamSettled` booleans (non-breaking addition).

## Consequences

**Positive:**

* No false "Needs Action" banners for settled or instantly-refunded campaigns.
* Claim button remains visible and active when `claimable > 0` after creator cancel -- correct grace-period behaviour.
* All 8 states have corresponding CSS badge variants in `CampaignStatusBadge.tsx`.

**Negative:**

* API consumers must handle two new boolean fields (`streamSettled`, `instantRefunded`) -- additive, non-breaking change.

## Alternatives Considered

* **Keep single boolean check:** Simpler but produces incorrect UI states for 4 out of 8 lifecycle states. The false "Needs Action" banner was a user-reported bug.
* **Derive state entirely client-side from raw fields:** Possible but duplicates logic across multiple components. Centralizing in a single enum + helper is more maintainable.
* **Server-computed lifecycle state:** Would add a `lifecycle` field to the API response. Viable but requires backend changes; the client-side computation was faster to ship and keeps the API schema stable.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.velthoryn.site/architecture-decisions/adr-fe-003-campaign-lifecycle.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
