diff --git a/frontend/src/pages/app/StreamDetail.jsx b/frontend/src/pages/app/StreamDetail.jsx index 1c0eacc..b57b334 100644 --- a/frontend/src/pages/app/StreamDetail.jsx +++ b/frontend/src/pages/app/StreamDetail.jsx @@ -362,8 +362,6 @@ export default function StreamDetail() { const isActive = streamValidUntil > 0n && now < streamValidUntil; const isPending = !isActive && (totalDeposited ?? 0n) > 0n && (streamValidUntil === 0n || streamValidUntil <= startTime); const duration = streamValidUntil > startTime ? streamValidUntil - startTime : 0n; - const elapsed = isActive ? now - startTime : duration; - const progressPct = isPending ? 0 : duration > 0n ? Math.min(Number((elapsed * 100n) / duration), 100) : 100; const tokenLabel = TOKEN_LABELS[token] ?? short(token); const ratePerDay = parseFloat(formatUnits(ratePerSecond ?? 0n, 6)) * 86400; @@ -398,6 +396,18 @@ export default function StreamDetail() { : 0n; const hasUnearned = unearned > 0n; + // Amount actually streamed to the contractor so far (claimable + already withdrawn). + const streamedAmount = (resolvedBalance ?? 0n) + (totalWithdrawn ?? 0n); + // Progress = share of the total budget earned, NOT time within the current funded + // window. In the micro-extension model the window resets on every verified PR, so + // budget-streamed is the only meaningful measure of overall engagement. + const progressPct = (totalDeposited ?? 0n) > 0n + ? Math.min(Number((streamedAmount * 10000n) / totalDeposited) / 100, 100) + : 0; + // Frozen but revivable: window lapsed, budget remains, not yet reclaimed. A new + // verified deliverable re-extends it — so it's "Paused", not "Ended". + const isPaused = !isActive && !isPending && hasUnearned; + /** Parse a wagmi/viem error into a human-readable one-liner. */ function txErrorMessage(err) { if (!err) return null; @@ -478,6 +488,8 @@ export default function StreamDetail() { ? Active : isPending ? Pending verification + : isPaused + ? Paused : Ended }