[DISCUSS] The “Never 0.00” Challenge — design a resilient price pipeline (client → edge) together
New group, first thread. Let’s start with a problem every finance app faces: your quotes flicker to 0.00
right when users need truth. How do we design a pipeline that never panics, stays fast, and makes the UI explainable?
I’m 22s from Pocket Portfolio (OSS dashboard: CSV → live P/L → Mock-Trade Lab). We’ve been hardening our price path and want to compare notes with you — architects, frontenders, edge-folk, crypto quants, and a11y pros.

The story (2 mins)
You’re rendering a positions grid. Provider “A” hiccups for 900 ms, “B” is slow in one region, and “C” returns a weird NaN
. Your UI shows 0.00 or a spinner graveyard. Users lose trust.
We flipped the rule: it’s better to show a clearly tagged stale-but-true quote than a shiny 0.00. The UX stays calm, and the system buys enough time to recover.
The pattern (client → edge)
Client
└── fetch quote(sym) with 1.4s budget
└── Edge function (region-local)
├─ Try P1 (timeout 1.4s) -> OK? return
├─ Try P2 (1.4s) -> OK? return
├─ Try P3 (1.6s) -> OK? return
└─ else: return last-known-good (stale:true) + tiny error summary
Cache
└── KV: sym -> { price, ts, src } // updated on successful hits
Key properties:
- Short, brutal timeouts per provider (fail fast).
- Rotation and backoff to avoid “thundering herd”.
- Last-known-good as final fallback (with a stale flag).
- Zero logs spam — count errors, sample detail, keep it cheap.
Tiny TypeScript example (edge)
// /edge/quote.ts (simplified)
const providers = [
{ name: 'p1', url: env.P1_URL, timeoutMs: 1400 },
{ name: 'p2', url: env.P2_URL, timeoutMs: 1400 },
{ name: 'p3', url: env.P3_URL, timeoutMs: 1600 },
];
export async function getQuote(sym: string) {
for (const p of providers) {
try {
const ctl = new AbortController();
const t = setTimeout(() => ctl.abort(), p.timeoutMs);
const r = await fetch(`${p.url}/q?symbol=${sym}`, { signal: ctl.signal, cf:{ cacheTtl: 10 }});
clearTimeout(t);
if (!r.ok) continue;
const { price } = await r.json();
if (Number.isFinite(price)) {
await KV.put(`q:${sym}`, JSON.stringify({ price, ts: Date.now(), src: p.name }), { expirationTtl: 120 });
return { price, src: p.name, stale: false };
}
} catch {}
}
const last = await KV.get(`q:${sym}`);
const cached = last ? JSON.parse(last) : null;
return { price: cached?.price ?? null, src: cached?.src ?? 'none', stale: true };
}

UX that earns trust (copy/paste checklist)
- ✅ Never show 0.00 unless a symbol truly trades at zero (rare).
- ✅ If stale, show a subtle chip:
Last update 28s • p2
with tooltip “Provider fallback active”.
- ✅ Keyboard-first: when a value changes, announce to SRs (
aria-live="polite"
) and use diff-highlights that fade in <1s.
- ✅ Explain the why: on hover, show “Switched p1→p2 (timeout)”.
- ✅ Error CSV for failed imports; same spirit: explain, don’t mystify.
Mini-spec template (fill in and post as a reply)
policy:
ttl_cache_seconds: 10
providers:
- name: p1
timeout_ms: 1400
- name: p2
timeout_ms: 1400
- name: p3
timeout_ms: 1600
final_fallback: last_known_good
stale_badge_after_ms: 15000
count_telemetry: true # no PII, just counters
ui:
show_stale_chip: true
announce_changes: polite
hover_explain: provider_switch
observability:
per_region_counters: true
sample_errors: 0.05
Post your filled YAML + any trade-offs (e.g., mobile vs desktop, crypto vs equities). We’ll review and suggest tweaks.
One-hour build (join in)
If you’ve got an hour and want something concrete:
- Add the stale chip to your quote component (text + tooltip).
- Implement the second provider with timeouts (even a mock server).
- Store last-known-good in a tiny KV/local cache.
- Paste a 10s GIF here. We’ll give quick notes.
What you get back
- Peer review on your policy & code snippet
- A reusable fallback policy you can ship this week
- A shout-out in our next Pocket Portfolio dev post (and we’ll link your repo)
Why this group exists (and where we’re going)
We care about open finance, DeFi, cryptography, and accessible design. We’ll run Spec Clinics, Paper Club, and Ship Fridays. Next threads will cover:
- Broker CSV normalization (share one redacted row)
- Keyboard-first dashboards (a11y = speed)
- Wallet UX & security (MPC/AA, seedless recovery)
Introduce yourself below (stack, timezone, what you’re building). Then drop your fallback policy reply. Let’s make “never 0.00” the default. ️✨



— 22s (Pocket Portfolio)
App: pocketportfolio.app/app • Repo: github.com/PocketPortfolio/Financialprofilenetwork