Skip to content

Money tab: the Network spend block and the refill action - #106

Merged
ALLiDoizCode merged 1 commit into
mainfrom
sandcastle/issue-80
Aug 4, 2026
Merged

Money tab: the Network spend block and the refill action#106
ALLiDoizCode merged 1 commit into
mainfrom
sandcastle/issue-80

Conversation

@toon-backlog-bot

Copy link
Copy Markdown

Adds the Network spend block and the refill action to the desktop app's Money tab.

Part of #80

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

🤖 Generated with Claude Code

Task: buzz#80, part of the agent-fleet-money epic (toon-meta#261 decision
9 / toon-meta#262). #75 shipped the Money tab with Model usage; this adds
the sibling Network spend block (balance, allowance, runway, burn rate)
and the app's first working refill/top-up action.

Key decisions:
- Budget state is a discriminated union matching the huddle fee quote's
  shape exactly (`relay | pending | unavailable | quoted`,
  networkSpendState.ts), with `canRefillNetworkSpend(state)` as the
  separate "can I act" answer per the issue's explicit AC — never a
  `state.kind === "quoted"` check scattered at call sites.
- `ToonPaidWriter.getNetworkFlowStatus()` (toonPaidWriter.ts) prefers the
  connector's claim-state endpoint and falls back to the client's own
  locally-tracked deposit/cumulative-claimed read when the connector is
  unreachable or the client build predates `getClaimState` — the block
  never goes blank, per decision 5's "always-available free floor".
- `getClaimState` is wired against a hand-rolled `ClaimStateReadResult`
  type, not imported from `@toon-protocol/client`: the pin is still
  `^0.25.1` (see Blockers below), so no real client build supplies this
  method yet — `tryClaimState` treats its absence as "no verified
  answer" and falls back cleanly, same as an unreachable connector.
- Burn rate is sourced from a new module-level store
  (networkSpendLiveStore.ts) fed by `ToonEventTransport.onPaidWrite`,
  per the epic's established module-level-store +
  `useSyncExternalStore` idiom — a 5-minute trailing window, reset on
  community switch (wired into `resetCommunityState()`, AGENTS.md
  updated). Reusing `agentNetworkFlow.ts`'s `deriveNetworkRunway`
  directly on a zero (unmeasured) rate would read as "self-funding" —
  a lie — so `networkSpendState.ts` gates that call on a real sample
  having landed first ("burn rate hasn't been measured yet this
  session" otherwise).
- Real, per-agent claim-state reads are scoped to `isSelf` (the identity
  this desktop process itself pays as, account index 0) — not to every
  managed agent. This is a verified architectural gap, not a scoping
  shortcut: no toon-clientd spawn/lifecycle exists for managed agents
  (buzz#79's ADR 0006 says so explicitly), no account-index lookup is
  exposed to the frontend, and `ToonClient.getClaimState`/
  `getChannel*` only ever answer for channels THIS client instance
  tracks locally — there is no channel id to ask about for another
  agent's identity. Viewing another agent's Money tab honestly reads
  `unavailable`, never a fabricated figure.
- Top-up reuses `ToonPaidWriter.depositToChannel` (buzz#77's real,
  already-wired deposit path) and `paymentsOverview.ts`'s
  `parseUsdcAmount` — the same real on-chain deposit Settings ->
  Payments already executes, now reachable from the Money tab too.

Files changed: desktop/src/features/profile/lib/{networkSpendState,
networkSpendLiveStore,useNetworkSpend}.ts (+tests),
desktop/src/features/profile/ui/{UserProfilePanelNetworkSpend.tsx (new),
UserProfilePanelMoneyTab.tsx,UserProfilePanelSections.tsx},
desktop/src/shared/api/{toonPaidWriter.ts (+tests),transportSelection.ts},
desktop/src/features/communities/useCommunityInit.ts, AGENTS.md.

Verified: fmt-check, desktop-tauri-fmt-check, clippy (workspace,
-D warnings), test-unit (864 tests), desktop-check (incl. file-size
ratchet — UserProfilePanelSections.tsx was already at the grandfathered
1000-line ceiling; offset the one-line `isSelf` prop wire with a
content-preserving one-line collapse of PROFILE_HERO_SPACING),
desktop-test (4278 passed), desktop-build, web-check, web-build — all
green.

Blockers/notes for next iteration: bumping `@toon-protocol/client` to
0.26.0 (needed for the real `getClaimState()`, per the issue's own
comment) is blocked in THIS sandbox by pnpm's minimumReleaseAge
supply-chain gate — 0.26.0 was published too recently and the lockfile
verification fails even with a `minimumReleaseAgeExclude` entry present
(reproduced directly: `pnpm install` fails
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION regardless). The `getClaimState`
seam is fully wired and tested against a hand-rolled type so the bump
is the only remaining step once it clears that gate (or enough real
time passes). Per-managed-agent live reads (not just `isSelf`) need
toon-clientd spawn/lifecycle (still absent per buzz#79) plus an
account-index Tauri command exposed to the frontend — both real,
separate follow-ups, not this ticket's scope. buzz#68 (mid-huddle
top-up unwired) is addressed at its root cause — a working top-up
action now exists and is reachable — but its huddle-specific
low-collateral warning is not implemented here (deferred v1.1 per
epic decision 8); left a comment on #68 rather than closing it.

Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
@ALLiDoizCode
ALLiDoizCode merged commit e0d4da6 into main Aug 4, 2026
22 checks passed
@ALLiDoizCode
ALLiDoizCode deleted the sandcastle/issue-80 branch August 4, 2026 03:34
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