sync: project quota, provider status, todo triage, and full diffs to the phone

Four Mac surfaces the iOS remote couldn't render now ride the wire,
forward-compatibly (decodeIfPresent defaults; unknown HostMsg tags
already decode to .unknown):

- DashboardSnapshot gains `usage` (WireSubscriptionUsage — the Mac's
  5-hour/weekly quota gauges) and `statusFeeds` (WireStatusFeed —
  active provider incidents); the host re-pushes the dashboard when a
  poll changes either.
- WireTodo gains `triage` (raw TriageLevel name); the phone renders
  the Mac's TriageBadge honoring the encouraging/classic label
  setting.
- ClientMsg.fetchDiff / HostMsg.sessionDiff deliver the full worktree
  patch on demand (capped at 512 KB with a truncated flag), gated on
  the new WireCapabilities.canFetchDiff so a new phone never sends it
  to an old host. iOS renders a file list + colored unified patch,
  falling back to the diffstat summary against older hosts.

Round-trip and legacy-decode tests cover the new fields; the full
suite passes apart from the pre-existing fake-backend fixture gaps
and the flaky nvrsion lock-domain test (same failure rate on the
base commit).

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-02 16:06:32 -07:00
co-authored by Claude Fable 5
parent a33ab80313
commit 480b29207f
6 changed files with 438 additions and 8 deletions
@@ -49,6 +49,10 @@ final class RemoteStore: ObservableObject {
@Published private(set) var openSessionID: SessionID?
@Published private(set) var openEvents: [AgentEvent] = []
@Published private(set) var openApprovals: [ApprovalRequest] = []
/// The open session's full diff (the Mac Diff tab's patch), fetched on demand when the
/// user opens the Diff tab and the host advertises `canFetchDiff`. Nil until it arrives.
@Published private(set) var openDiff: WireSessionDiff?
@Published private(set) var diffLoading = false
/// A transient host-reported error (the mobile echo of the Mac's last-error bubble):
/// shown as a red bubble at the bottom of the screen, auto-dismissed after a few seconds.
@@ -123,7 +127,8 @@ final class RemoteStore: ObservableObject {
connectivity = .connected
hostName = "Andrew's Mac"
grantedScope = .control
capabilities = WireCapabilities(canModifyToolInput: true, allowAlwaysScopes: [.session, .toolName])
capabilities = WireCapabilities(
canModifyToolInput: true, allowAlwaysScopes: [.session, .toolName], canFetchDiff: true)
modelCatalog = WireModelCatalog(
groups: [
[WireModelCatalog.Model(sku: "claude-opus-4-8[1m]", displayName: "Opus 4.8", backend: .claudeCode,
@@ -179,14 +184,52 @@ final class RemoteStore: ObservableObject {
],
todos: [
WireTodo(id: TodoID(rawValue: "t1"), text: "Add dark mode to settings", summary: "Dark mode in settings",
projectID: p2, projectName: "website", status: .open, dispatchedSessionID: nil, updatedAt: Date()),
projectID: p2, projectName: "website", status: .open, dispatchedSessionID: nil,
triage: "high", updatedAt: Date()),
WireTodo(id: TodoID(rawValue: "t2"), text: "Investigate the memory leak in the sync server", summary: "Sync server memory leak",
projectID: p1, projectName: "nucleic", status: .open, dispatchedSessionID: nil, updatedAt: Date()),
projectID: p1, projectName: "nucleic", status: .open, dispatchedSessionID: nil,
triage: "critical", updatedAt: Date()),
WireTodo(id: TodoID(rawValue: "t3"), text: "Write release notes", summary: nil,
projectID: nil, projectName: nil, status: .open, dispatchedSessionID: nil, updatedAt: Date()),
projectID: nil, projectName: nil, status: .open, dispatchedSessionID: nil,
triage: "low", updatedAt: Date()),
],
usage: WireSubscriptionUsage(
fiveHour: WireUsageWindow(utilization: 42, resetsAt: Date().addingTimeInterval(3 * 3600)),
sevenDay: WireUsageWindow(utilization: 78, resetsAt: Date().addingTimeInterval(2.4 * 86_400))),
statusFeeds: [
WireStatusFeed(provider: "claude", providerName: "Claude", incidents: []),
WireStatusFeed(provider: "openai", providerName: "OpenAI", incidents: [
WireStatusIncident(
id: "i1", title: "Elevated errors on Codex", url: URL(string: "https://status.openai.com"),
updatedAt: Date(), state: "Monitoring", isResolved: false, components: ["Codex"]),
]),
WireStatusFeed(provider: "xai", providerName: "xAI", incidents: []),
])
}
/// Offline diff fixture (NUCLEIC_DEMO) so the full-patch Diff tab renders without a host.
private func demoDiff(_ sessionID: SessionID) -> WireSessionDiff {
WireSessionDiff(
sessionID: sessionID,
stat: DiffStat(filesChanged: 2, added: 312, removed: 40),
files: [
WireFileDiff(path: "auth/middleware.ts", oldPath: nil, status: "modified", added: 290, removed: 38),
WireFileDiff(path: "auth/session.ts", oldPath: nil, status: "added", added: 22, removed: 2),
],
patch: """
diff --git a/auth/middleware.ts b/auth/middleware.ts
--- a/auth/middleware.ts
+++ b/auth/middleware.ts
@@ -10,7 +10,9 @@ export function requireSession(req: Request) {
- const token = req.headers.get("x-auth")
+ const header = req.headers.get("authorization") ?? ""
+ const token = header.replace(/^Bearer /, "")
+ if (!token) throw new AuthError("missing bearer token")
return verify(token)
}
""")
}
/// Pair from a scanned QR (SYNC §4.2): connect (LAN hint first, else Bonjour), run XXpsk0,
/// and on success pin the host key for future IK reconnects.
func pair(with payload: PairingPayload) {
@@ -240,12 +283,23 @@ final class RemoteStore: ObservableObject {
openSessionID = sessionID
openEvents = []
openApprovals = []
openDiff = nil
diffLoading = false
seenSeq.removeAll()
markOpened(sessionID)
if demoMode { seedDemoTranscript(sessionID); return }
send(.subscribe(Subscribe(sessionID: sessionID, sinceSeq: nil, verbosity: .full)))
}
/// Ask the host for the open session's full patch (Diff tab). No-op when the host
/// doesn't advertise the capability — the view falls back to the diffstat summary.
func fetchDiff(_ sessionID: SessionID) {
if demoMode { openDiff = demoDiff(sessionID); return }
guard capabilities.canFetchDiff else { return }
diffLoading = openDiff == nil
send(.fetchDiff(sessionID))
}
/// Record that the user looked at this session now (clears its unseen-completion wash).
func markOpened(_ sessionID: SessionID) {
lastOpenedAt[sessionID] = Date()
@@ -287,6 +341,8 @@ final class RemoteStore: ObservableObject {
openSessionID = nil
openEvents = []
openApprovals = []
openDiff = nil
diffLoading = false
}
func respond(_ approval: ApprovalRequest, _ decision: Decision) {
@@ -418,6 +474,10 @@ final class RemoteStore: ObservableObject {
}
case .approvalResolved(let resolved):
openApprovals.removeAll { $0.id == resolved.id }
case .sessionDiff(let diff):
guard diff.sessionID == openSessionID else { break }
openDiff = diff
diffLoading = false
case .wireError(let error):
// Not fatal — surface as a transient bubble (the Mac's last-error overlay).
// Losing an approval race isn't an error worth interrupting for; the card