Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ pnpm monorepo with three packages:
- `indexer-envio/` — Envio HyperIndex indexer for Celo v3 FPMM pools
- `ui-dashboard/` — Next.js 16 + Plotly.js monitoring dashboard

## Operating Rule (read this before opening PRs)

> **Any PR that adds or changes stateful data flow across layers must ship with explicit invariants, degraded-mode behavior, and interaction tests before opening.**

This repo has already paid the tax for learning this the hard way.

If your change touches any combination of:

- Envio schema/entities
- event handlers / entity writers
- generated types / GraphQL queries / dashboard types
- paginated or sortable UI state
- partial failure behavior (missing counts, stale RPC, missing txHash, etc.)

then you are expected to run the dedicated PR checklist before opening or updating the PR:

- **Checklist:** `docs/pr-checklists/stateful-data-ui.md`

Do not rely on PR review to finish the design. Reviews should catch misses, not define the invariants for the first time.

## Quick Commands

```bash
Expand Down Expand Up @@ -165,6 +185,10 @@ pnpm indexer:codegen # Validates Envio can parse handler entry point + module
pnpm --filter @mento-protocol/ui-dashboard test:coverage
```

Before pushing any cross-layer or stateful UI change, also read and apply:

- **`docs/pr-checklists/stateful-data-ui.md`**

**Common traps:**

- `codespell` flags short variable names that match common abbreviations (e.g. a two-letter loop var that looks like a misspelling). Use descriptive names like `netData` to avoid this.
Expand Down
205 changes: 205 additions & 0 deletions docs/pr-checklists/stateful-data-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Stateful Data + UI PR Checklist

Use this checklist for any PR that changes stateful data flow across layers.

## Operating rule

> **Any PR that adds or changes stateful data flow across layers must ship with explicit invariants, degraded-mode behavior, and interaction tests before opening.**

If the change touches any combination of:

- Envio schema/entities
- event handlers / entity writers
- generated types / GraphQL queries / dashboard types
- paginated, sortable, filterable, or searchable UI state
- partial failure behavior (count query failure, stale RPC, missing metadata, old rows after schema rollout)

then this checklist is mandatory.

---

## 1. Define invariants first

Write down the rules the system must obey before coding.

Examples:

- Every event/snapshot entity must persist `txHash`
- Charts must not depend on paginated table slices
- Paginated tables must have deterministic ordering
- Aggregate-query failure must degrade visibly, not silently
- Client-side search over large datasets must be bounded and disclosed

If you cannot state the invariant in one sentence, the design is not ready.

---

## 2. Cross-layer audit

For every new field / changed field / changed behavior, walk the full path:

### Schema / source of truth

- [ ] `schema.graphql` updated if entity shape changed
- [ ] field names/types/nullability are intentional
- [ ] backward-compatibility / rollout behavior considered for old rows

### Writers

- [ ] every entity constructor / writer is updated consistently
- [ ] all event handlers that produce the entity were checked, not just the obvious one
- [ ] generated/codegen artifacts refreshed where applicable

### Readers

- [ ] GraphQL queries updated
- [ ] dashboard/runtime types updated
- [ ] derived formatting / rendering logic updated
- [ ] search/sort/filter fields updated intentionally

### Tests

- [ ] producer/indexer tests updated
- [ ] consumer/UI tests updated
- [ ] fixtures reflect the new schema reality

If one layer is missing, stop and fix it before opening the PR.

---

## 3. Stateful table rubric

If the PR touches a table with pagination, sort, filter, search, or linked charts, answer all of these explicitly.

### Sorting

- [ ] Is sorting server-side, client-side, or hybrid?
- [ ] Are page boundaries deterministic for non-unique sort fields?
- [ ] Is there a unique tiebreaker (`id`, tx hash, composite key, etc.)?
- [ ] Do headers expose sort state accessibly (`aria-sort`)?

### Pagination

- [ ] What determines total row count?
- [ ] What happens when count/aggregate fails?
- [ ] Does pagination remain usable after transient failure?
- [ ] Are controls actual buttons with `type="button"`?

### Search / filtering

- [ ] Does search operate on current page, fetched window, or full dataset?
- [ ] Is that behavior documented in code comments and PR notes?
- [ ] If bounded, is the cap explicit and user-visible?
- [ ] If unbounded, can the backend/query path actually support it?

### Coupled visualizations

- [ ] Do charts use dedicated queries instead of inheriting paginated/sorted table state?
- [ ] If not, is that coupling intentional and documented?

### URL / local state

- [ ] Is table state URL-backed or intentionally local?
- [ ] If local-only, is that explicitly called out as an intentional scope decision?

---

## 4. Degraded-mode checklist

For each non-happy path, decide the behavior explicitly.

- [ ] count query fails
- [ ] chart query fails
- [ ] some rows predate a new schema field
- [ ] RPC-derived metadata is missing
- [ ] total dataset is much larger than the current happy-path sample
- [ ] search term matches data outside the currently fetched window
- [ ] empty state vs loading state vs partial-data state are distinct

The key question:

> What will the user see, and will they understand that the data is partial or degraded?

Silent degradation is not acceptable.

---

## 5. Required test matrix

For nontrivial stateful data/UI changes, tests must cover all 3 buckets:

### Happy path

- [ ] normal render / query wiring
- [ ] new field is displayed/used correctly

### State transition

- [ ] sort toggle changes query/order state
- [ ] page transition changes offset/page state
- [ ] search input resets/updates the right state
- [ ] links/actions resolve to the expected target

### Failure / degraded mode

- [ ] count error fallback
- [ ] capped search behavior
- [ ] missing field / legacy row behavior
- [ ] user-visible warning or fallback state

If the risky behavior is interactive, a static markup assertion is not enough.

---

## 6. PR description requirements

Before opening the PR, include these sections:

### What this PR changes

Short factual summary.

### Invariants

List the system rules this PR relies on or introduces.

### Degraded behavior

What happens on count/query/RPC failure, old rows, large datasets, etc.

### Intentional non-goals

Examples:

- URL-backed sort/page state deferred
- full server-side search deferred
- abstraction cleanup out of scope

This prevents reviews from repeatedly rediscovering scope boundaries.

---

## 7. Repo-specific lessons already paid for

These are not theoretical.

- New UI fields must not assume schema support without verifying all writers.
- Shared presentational components should forward DOM props unless intentionally constrained.
- Count fallback must preserve prior total, not collapse to current page length.
- Search behavior must be bounded and disclosed when not truly global.
- Charts and tables should usually be decoupled.
- Cross-layer features need both indexer and UI regression coverage.

---

## 8. Final pre-PR questions

If you answer “no” to any of these, do not open yet.

- [ ] Could another engineer explain the invariants from the PR description alone?
- [ ] Would a transient backend failure produce a sensible UI instead of a misleading one?
- [ ] Are the largest-cardinality paths still bounded?
- [ ] Do tests prove behavior, not just markup?
- [ ] Did review stop being the place where design gets finished?

If not, one more local pass is cheaper than three more review rounds.
8 changes: 8 additions & 0 deletions indexer-envio/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on Celo + Monad (multichain).

## Before Opening PRs

If your indexer change propagates into Hasura/UI behavior — schema changes, entity additions, new fields on existing entities, degraded RPC/error handling, or any stateful dashboard behavior fed by indexer data — read and apply:

- `../docs/pr-checklists/stateful-data-ui.md`

This is mandatory for cross-layer/stateful data work. Do not assume the UI/query layer will “just catch up” later.

## Key Files

- `config.multichain.mainnet.yaml` — **Default** mainnet config (Celo + Monad)
Expand Down
1 change: 1 addition & 0 deletions indexer-envio/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type OracleSnapshot @index(fields: ["poolId", "timestamp"]) {
rebalanceThreshold: Int!
source: String!
blockNumber: BigInt!
txHash: String!
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txHash is now required (String!), but this introduces a schema-breaking rollout path unless you guarantee full backfill/reindex before any consumer reads this schema. Please either:

  1. make this field nullable for a safe rollout window, then enforce non-null after reindex, or
  2. document/enforce an atomic deploy + reindex sequence so older rows/environments never serve a schema that lacks populated txHash.

As written, staggered environments can fail hard on oracle reads.

}

type PoolSnapshot @index(fields: ["poolId", "timestamp"]) {
Expand Down
2 changes: 2 additions & 0 deletions indexer-envio/src/handlers/fpmm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ FPMM.UpdateReserves.handler(async ({ event, context }) => {
rebalanceThreshold: pool.rebalanceThreshold,
source: "update_reserves",
blockNumber,
txHash: event.transaction.hash,
};
context.OracleSnapshot.set(snapshot);
}
Expand Down Expand Up @@ -623,6 +624,7 @@ FPMM.Rebalanced.handler(async ({ event, context }) => {
rebalanceThreshold: pool.rebalanceThreshold,
source: "rebalanced",
blockNumber,
txHash: event.transaction.hash,
};
context.OracleSnapshot.set(snapshot);
}
Expand Down
2 changes: 2 additions & 0 deletions indexer-envio/src/handlers/sortedOracles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ SortedOracles.OracleReported.handler(async ({ event, context }) => {
rebalanceThreshold: existing.rebalanceThreshold,
source: "oracle_reported",
blockNumber,
txHash: event.transaction.hash,
};
context.OracleSnapshot.set(snapshot);
}
Expand Down Expand Up @@ -140,6 +141,7 @@ SortedOracles.MedianUpdated.handler(async ({ event, context }) => {
rebalanceThreshold: existing.rebalanceThreshold,
source: "oracle_median_updated",
blockNumber,
txHash: event.transaction.hash,
};
context.OracleSnapshot.set(snapshot);
}
Expand Down
Loading
Loading