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
63 changes: 63 additions & 0 deletions frontend/docs/dashboard-tour.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Dashboard Tour

A lightweight, accessible overlay that guides new users through the main
dashboard regions.

## Capabilities

- **Step-by-step guide** — walks the user through key dashboard regions with a
spotlight and a tooltip per step.
- **Skip and resume** — the tour can be skipped at any point and resumes at the
last viewed step the next time it is started.
- **Responsive positioning** — the spotlight and tooltip recompute on resize and
scroll, and the tooltip is clamped within the viewport.
- **Accessible navigation** — the tooltip is a focusable modal dialog with ARIA
labelling; supports keyboard navigation (`←`/`→` to move, `Esc` to skip) and
moves focus to each step.
- **Persist completion** — completion and the last-viewed step are stored in
local storage, so the tour auto-starts only once and remembers progress.

## Main files

- `src/hooks/useDashboardTour.ts` — tour state, persistence, skip/resume logic.
- `src/components/dashboard/DashboardTour.tsx` — overlay, spotlight, tooltip.
- `src/pages/Dashboard.tsx` — step definitions, region markers, trigger button.

## Flow

1. On a user's first visit the tour auto-starts at step one.
2. Each step highlights a dashboard region (`data-tour="..."`) and shows a
tooltip with the step title, description, progress dots, and controls.
3. Navigate with **Back** / **Next**, the arrow keys, or **Skip**.
4. **Skip** (button, `Esc`, or clicking the backdrop) closes the tour but keeps
the current step so it can be resumed.
5. **Finish** on the last step marks the tour completed.
6. Users can re-open the tour at any time via the **Take a tour** / **Replay
tour** button in the dashboard toolbar.

## Step definition

```ts
interface TourStep {
id: string;
target: string; // CSS selector, e.g. '[data-tour="kpis"]'
title: string;
body: string;
placement?: "top" | "bottom" | "left" | "right" | "auto";
}
```

Region markers currently used on the dashboard:

- `[data-tour="toolbar"]` — preset/refresh/export/share actions.
- `[data-tour="filters"]` — the asset/bridge filter panel.
- `[data-tour="kpis"]` — the key-metrics banner.
- `[data-tour="status-cards"]` — the inline status cards.

## Persistence

- Storage key: `bridge-watch:dashboard-tour:v1`.
- Stored shape: `{ completed: boolean, lastStep: number, seen: boolean }`.
- `seen` suppresses auto-start after the first run.
- `lastStep` enables skip-and-resume.
- `completed` is set when the user finishes the final step.
72 changes: 72 additions & 0 deletions frontend/docs/entity-summary-banner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Entity Summary Banner

A banner that surfaces the most important summary fields for the selected entity
(asset, bridge, etc.) at the top of its detail page.

## Capabilities

- **Summary fields** — shows the key metrics for an entity in a compact grid.
- **Context sensitive** — the calling page supplies the field set, so each entity
type shows the fields that matter for it (e.g. assets show health, price,
liquidity, trend).
- **Compact layout** — dense by default; values, status dots, and trend chips.
- **Compact and expanded modes** — a built-in toggle switches between a dense grid
and an expanded layout that reveals per-field hint text.
- **Loading state** — renders skeleton cards while the underlying data loads.
- **Drilldown links** — each field can link to a route (`to`) or trigger an
in-page drilldown (`onDrilldown`, e.g. switching detail tabs).

## Main files

- `src/components/entity/EntitySummaryBanner.tsx` — the banner component.
- `src/components/entity/index.ts` — public exports.
- `src/pages/AssetDetail.tsx` — integration for the asset entity.

## Behaviour

- The banner header shows an entity-type badge, the entity title, and an optional
subtitle. An `actions` slot holds page-level controls (refresh, watchlist, etc.).
- The mode toggle (top-right) flips between **Compact view** and **Expanded view**.
Expanded mode shows each field's `hint` for extra context.
- Each field renders its label, value, an optional status dot
(`healthy` / `warning` / `critical` / `neutral`), and an optional trend chip
(`up` / `down` / `neutral`).
- A field with `to` renders a router `Link`; a field with `onDrilldown` renders a
button. The affordance label defaults to "View" and can be overridden with
`drilldownLabel`.
- While `loading` is true, the banner renders skeleton cards (at least four).

## Usage

```tsx
import { EntitySummaryBanner, type EntitySummaryField } from "../components/entity";

const fields: EntitySummaryField[] = [
{
id: "health",
label: "Health score",
value: "82/100",
status: "healthy",
trend: { direction: "up", label: "Improving" },
hint: "Composite health score across all factors.",
onDrilldown: () => setTab("summary"),
drilldownLabel: "Open summary",
},
// ...more fields
];

<EntitySummaryBanner
entityType="Asset"
title="USDC"
subtitle="Detailed monitoring for USDC on the Stellar network"
fields={fields}
loading={isLoading}
defaultMode="compact"
/>;
```

## Reuse

The banner is entity-agnostic: any detail page can build its own
`EntitySummaryField[]` and render the banner. This keeps the layout, loading
state, mode toggle, and drilldown behaviour consistent across entity types.
61 changes: 61 additions & 0 deletions frontend/docs/filter-presets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Filter Presets Menu

Save commonly used dashboard filter combinations and reapply them from a single menu.

## Capabilities

- **Save preset** — name and store the current dashboard filters (assets, bridges, status, time range).
- **Load preset** — apply a saved preset to the dashboard in one click.
- **Rename preset** — rename a preset inline; duplicate names are rejected.
- **Delete preset** — remove presets that are no longer needed.
- **Shared access options** — mark a preset as `Shared` or `Private`. Shared presets expose a "Copy link" action that produces a URL re-applying the preset's filters.
- **Persistent storage** — presets are persisted in browser local storage and survive reloads.

## Main files

- `src/hooks/useDashboardFilters.ts` — filter state, preset CRUD, share-URL builder.
- `src/components/Filters/FilterPresetsMenu.tsx` — dropdown menu UI.
- `src/components/Filters/AssetFilterPanel.tsx` — inline preset controls in the filter sidebar.
- `src/pages/Dashboard.tsx` — wires the menu into the dashboard toolbar.

## Workflow

1. Apply one or more filters on the dashboard (assets, bridges, status, time range).
2. Open the **Presets** menu in the dashboard toolbar.
3. Enter a name and choose **Save**. The current filter combination is stored.
4. Reopen the menu at any time and click a preset name to reapply it.
5. Use **Rename**, **Make shared / Make private**, or **Delete** under each preset.
6. For shared presets, choose **Copy link** to share a URL that opens the dashboard with the preset's filters applied.

## Preset definition

```ts
interface DashboardFilterPreset {
id: string;
name: string;
filters: {
assets: string[];
bridges: string[];
status: "all" | "healthy" | "warning" | "critical";
timeRange: "all" | "24h" | "7d" | "30d";
};
shared: boolean;
createdAt: number;
updatedAt: number;
}
```

## Persistence

- Storage key: `bridge-watch:dashboard-filter-presets:v1`.
- Stored as a JSON array of `DashboardFilterPreset` objects.
- Presets saved before the shared/timestamp fields existed are normalized on read
(`shared` defaults to `false`, timestamps default to the current time).

## Shared links

A shared preset's link encodes the filters as dashboard URL search params, e.g.:

`/dashboard?assets=USDC,EURC&bridges=Circle&status=warning&range=7d`

Opening the link applies those filters because dashboard filter state is URL-persisted.
59 changes: 59 additions & 0 deletions frontend/docs/live-update-pills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Live Update Pills

Small pills that show how recently a view's data updated, whether it is stale,
and whether it is polling live.

## Capabilities

- **Last updated time** — shows a short relative time, e.g. "Updated 5s ago".
- **Stale indicator** — switches to an amber "stale" state once the data is older
than a configurable threshold (default 60s).
- **Live polling state** — shows a pulsing green dot and a "live" suffix while the
view is actively fetching/polling.
- **Compact size** — a single rounded pill with a status dot and short text.
- **Accessible text** — exposes a full sentence via `role="status"`,
`aria-live="polite"`, and an `aria-label`; the decorative dot is hidden from
assistive tech.

## Main files

- `src/hooks/useRelativeTime.ts` — relative-time formatting, age, and staleness;
re-renders on a cadence matched to the value's age.
- `src/components/LiveUpdatePill/LiveUpdatePill.tsx` — the pill component.
- `src/pages/Dashboard.tsx`, `src/pages/AssetDetail.tsx` — reuse sites.

## Update semantics

- **Timestamp source** — pass `updatedAt` from the data layer. The reuse sites use
React Query's `dataUpdatedAt` (the last successful fetch time). `null` /
invalid values render as "never".
- **Relative buckets** — `< 5s` → "just now", `< 60s` → "Ns ago",
`< 60m` → "Nm ago", `< 24h` → "Nh ago", else "Nd ago".
- **Re-render cadence** — every 1s under a minute old, every 30s under an hour,
every 5m beyond that. This keeps the text fresh without excessive renders.
- **Staleness** — `isStale` becomes true once `age > staleAfterMs`. A stale pill
takes priority over the live state.
- **Polling** — when `polling` is true and the data is not stale, the pill shows
the live (pulsing) state. While polling but stale, the stale state still wins.

## States

| State | Dot | Text example |
| ------- | -------------- | -------------------- |
| live | pulsing green | `Updated 3s ago · live` |
| fresh | green | `Updated 12s ago` |
| stale | amber | `Updated 5m ago · stale` |
| unknown | grey | `Updated: never` |

## Usage

```tsx
import { LiveUpdatePill } from "../components/LiveUpdatePill";

<LiveUpdatePill
updatedAt={query.dataUpdatedAt > 0 ? query.dataUpdatedAt : null}
polling={query.isFetching}
staleAfterMs={60_000}
label="Updated"
/>;
```
Loading