Skip to content

Show earning in the Money tab — net flow, same channel - #97

Merged
ALLiDoizCode merged 2 commits into
mainfrom
sandcastle/issue-86
Aug 3, 2026
Merged

Show earning in the Money tab — net flow, same channel#97
ALLiDoizCode merged 2 commits into
mainfrom
sandcastle/issue-86

Conversation

@toon-backlog-bot

Copy link
Copy Markdown

Adds net-flow/runway domain logic to the desktop Money tab so earning is derived from same-channel net flow rather than raw incoming payments.

Part of #86

This PR was produced by the sandcastle agent:implement runner and is awaiting human review.

🤖 Generated with Claude Code

claude added 2 commits August 3, 2026 22:29
…ing (buzz#86)

Task: #86, part of the agent-fleet-money epic
(toon-meta#262 decision 9 / toon-meta#261 decision 4). Ships the pure
net-flow domain logic the ticket's four acceptance criteria all reduce
to — income beside spend, runway that accounts for income, the earning
predicate for AgentIdentityCard, and low-funds-alert suppression.

Key decisions:
- New `desktop/src/features/profile/lib/agentNetworkFlow.ts`: models
  `spendable = deposit − owed + credited` (the connector's stated
  netting formula, one channel, never a second "earnings" pot) plus a
  runway derivation that discounts untrusted income. Sustained income
  (>= 3 samples in the trailing window) that covers burn yields a
  `self-funding` state with no depletion date; anything less degrades
  to burn-rate-alone runway — a single paid job must not flip the UI to
  "indefinite" and strand someone when the job stream dries up.
  `isEarning` (AgentIdentityCard badge) and `shouldSuppressLowFundsAlert`
  reuse the same trusted-income bar so the fleet glance and the alert
  never disagree about whether an agent pays for itself.
- Followed the `paymentsOverview.ts` / `huddleFeeQuote.ts` idiom: pure,
  fully unit-tested (RGR — 8 new cases), no DOM/network, ready for a
  caller to wire once real data exists.
- Not wired into any component. The two things this logic needs to
  render for real do not exist yet in this repo: the Network spend
  block itself (#80 — still a "Coming soon" placeholder, no balance/
  burn-rate read of any kind) and a per-agent claim-state read
  (toon-client#494's `getClaimState()`, not present in this repo's
  pinned `@toon-protocol/client@0.25.1`). Per the issue's own Gotchas,
  income must come from the connector's claim state, never inferred by
  scanning NIP-90 job events client-side — so there is no honest
  workaround available in this repo's current state. Updated the Money
  tab's Network spend placeholder copy to state the net-flow model
  (income nets into the same balance) without fabricating a live read.

Files changed: desktop/src/features/profile/lib/agentNetworkFlow.ts
(new, +test), desktop/src/features/profile/ui/UserProfilePanelMoneyTab.tsx.

Full local gate green: fmt-check, desktop-tauri-fmt-check, clippy,
test-unit (864 tests), desktop-check, desktop-test (4209 passed),
desktop-build, web-check, web-build.

Blockers for next iteration: #80 (Network spend block: balance,
burn-rate read via `onPaidWrite`, refill) must land first, and the
per-agent claim-state read this ticket's income data depends on needs
either a newer `@toon-protocol/client` with `getClaimState()` or the
buzz-cli/relay-side plumbing to expose it to the desktop for an agent
that is not the local wallet identity. Once either lands, `deriveNetworkRunway`/
`isEarning`/`shouldSuppressLowFundsAlert` are ready to consume it directly.

Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
…sertion

The "single payment does not flip runway to indefinite" test built its
finite-runway state from a read with incomeSampleCount: 1, but then
asserted isEarning(NO_INCOME) — the untouched base fixture with a zero
income rate, which trivially returns false regardless of the sample-count
logic under test. Assert against the same read used to derive the state.

Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
@ALLiDoizCode
ALLiDoizCode merged commit bf0246e into main Aug 3, 2026
22 checks passed
@ALLiDoizCode
ALLiDoizCode deleted the sandcastle/issue-86 branch August 3, 2026 22:56
@ALLiDoizCode

Copy link
Copy Markdown

Heads-up on a cross-epic collision, not a blocker for this PR's own correctness.

This PR (buzz#86, earning epic toon-protocol/toon-meta#262) edits the Money tab, and so does buzz#80 (Network spend block, agent-fleet-money epic toon-protocol/toon-meta#261). #80 establishes the tab's two-block structure and the never-summed rule that earning then slots into, so the agreed sequencing is #80 lands first, then this PR rebases onto it.

#80 is currently blocked on buzz#79. Flagging now so the layout decisions here are made knowing the Network spend block is arriving beside them — two independent takes on the same file's structure is the one thing a rebase cannot reconcile.

ALLiDoizCode pushed a commit that referenced this pull request Aug 4, 2026
* RALPH: earning badge on AgentIdentityCard, close out net-flow Money tab (buzz#86)

Task: #86, part of the agent-fleet-money epic
(toon-meta#262 decision 9). Per the issue's own re-dispatch comment,
AC1/AC2 (income beside spend, income-aware runway) landed in PR #97
(agentNetworkFlow.ts) and were wired to real data in #108/#106 (PR #113,
#106). AC4 (suppress the low-funds alert for a self-funding agent) was
already implemented and tested by buzz#76/PR #111 —
`deriveAgentFleetRunwayBadge` returns no badge once `deriveNetworkRunway`
concludes an agent is self-funding, which flows straight through
`countLowFundsAgents`/`shouldShowSidebarLowFundsCard` — confirmed via the
existing "self-funding agents show no badge" test, no code change needed.
This lands the one true remaining gap: AC3, an earning indicator on
`AgentIdentityCard` in the Agents grid.

Key decisions:
- New `isAgentFleetEarning(state: NetworkSpendState): boolean`
  (agentFleetRunway.ts) wraps `agentNetworkFlow.ts`'s `isEarning` behind
  the same `state.kind !== "quoted"` gate `deriveAgentFleetRunwayBadge`
  already uses, so the earning badge and the low-funds suppression share
  one trusted-income bar and can never disagree about whether an agent
  is self-funding.
- New `useAgentFleetEarningBadges` hook mirrors
  `useAgentFleetRunwayBadges`'s `isSelf`-only gate exactly (same
  documented buzz#79 ADR 0006 gap — no per-agent channel read exists for
  any identity but the one this desktop process itself pays as). Kept as
  its own hook rather than folding into the existing runway hook: the
  runway hook's `Map<pubkey, AgentFleetRunwayBadge>` return type is
  consumed as-is by two call sites (`UnifiedAgentsSection.tsx`,
  `SidebarLowFundsCard.tsx`), and `SidebarLowFundsCard.tsx` has no need
  for earning state, so widening its shape would be churn for one
  consumer's benefit.
- `AgentStatusBadge` (the card's single-warning slot, `UnifiedAgentsSection.tsx`)
  gets a third, lowest-priority branch: operational issues, then a
  low-funds runway warning, then "Earning" (success-variant badge,
  `TrendingUp` icon) — same slot, no layout change. The three are
  mutually exclusive in practice (self-funding agents never carry a
  runway badge), but priority order is defensive.

Files changed: desktop/src/features/agents/lib/{agentFleetRunway.ts (+3
tests),useAgentFleetEarningBadges.ts (new)},
desktop/src/features/agents/ui/UnifiedAgentsSection.tsx.

Verified: fmt-check, desktop-tauri-fmt-check, clippy (workspace,
-D warnings), test-unit (864 tests), desktop-check, desktop-test (4334
passed), desktop-build, web-check, web-build — all green.

Blockers/notes for next iteration: the earning badge is wired but inert
against real data today — `networkSpendState.ts`'s `deriveNetworkSpendState`
hardcodes `incomeRateBaseUnitsPerSec: 0`/`incomeSampleCount: 0` (no live
inbound-payment event feed exists yet, only `networkSpendLiveStore.ts`'s
outbound `onPaidWrite` — documented in agentNetworkFlow.ts's module doc
since PR #113), so `isEarning`/`isAgentFleetEarning` cannot return true
from a real read until that income-rate feed lands. This is the same
documented, pre-existing gap AC1/AC2's runway already ships ahead of, not
new scope this ticket introduces.

Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: toon-backlog-bot[bot] <294337193+toon-backlog-bot[bot]@users.noreply.github.com>

* Dedupe fleet-status hooks: one useNetworkSpend read for badges + earning

UnifiedAgentsSection called useAgentFleetRunwayBadges and the new
useAgentFleetEarningBadges together, each independently calling
useNetworkSpend(true)/useIdentityQuery and duplicating the same
isSelf-matching loop — doubling the connector fetch on every mount.
Fold both into useAgentFleetStatus, which computes runway badges and
the earning set from a single self-spend read; useAgentFleetRunwayBadges
becomes a thin wrapper so SidebarLowFundsCard is unaffected.

Signed-off-by: toon-backlog-bot[bot] <294337193+toon-backlog-bot[bot]@users.noreply.github.com>

---------

Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: toon-backlog-bot[bot] <294337193+toon-backlog-bot[bot]@users.noreply.github.com>
Co-authored-by: toon-backlog-bot[bot] <294337193+toon-backlog-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants