Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Packages/CrowCore/Sources/CrowCore/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ public final class AppState {
/// Set by `IssueTracker` when the token lacks a required scope; cleared on next success.
public var githubScopeWarning: String?

/// Last observed GitHub GraphQL rate-limit snapshot. `nil` before the first
/// successful query. Populated from the `rateLimit` block on each refresh.
public var githubRateLimit: GitHubRateLimit?

/// Non-fatal rate-limit warning surfaced in Settings. `nil` when not throttled.
/// Set by `IssueTracker` when polling is suspended; cleared on next success.
public var rateLimitWarning: String?

/// Terminal readiness state per terminal ID.
public var terminalReadiness: [UUID: TerminalReadiness] = [:]

Expand Down Expand Up @@ -303,6 +311,26 @@ public final class AppState {
public init() {}
}

// MARK: - GitHub Rate Limit

/// Snapshot of the GitHub GraphQL rate-limit state observed from the `rateLimit`
/// block on the last successful query.
public struct GitHubRateLimit: Equatable, Sendable {
public let remaining: Int
public let limit: Int
public let resetAt: Date
public let cost: Int
public let observedAt: Date

public init(remaining: Int, limit: Int, resetAt: Date, cost: Int, observedAt: Date) {
self.remaining = remaining
self.limit = limit
self.resetAt = resetAt
self.cost = cost
self.observedAt = observedAt
}
}

// MARK: - Per-Session Hook State

/// Observable wrapper for per-session hook/Claude state.
Expand Down
20 changes: 20 additions & 0 deletions Packages/CrowUI/Sources/CrowUI/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,31 @@ public struct SettingsView: View {
}
}

@ViewBuilder
private var rateLimitWarningBanner: some View {
if let warning = appState.rateLimitWarning {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "clock.badge.exclamationmark.fill")
.foregroundStyle(.orange)
Text(warning)
.font(.caption)
.textSelection(.enabled)
Spacer()
}
.padding(10)
.background(Color.orange.opacity(0.12))
.cornerRadius(6)
}
}

private var generalTab: some View {
Form {
if appState.githubScopeWarning != nil {
Section { githubScopeWarningBanner }
}
if appState.rateLimitWarning != nil {
Section { rateLimitWarningBanner }
}
Section("Development Root") {
HStack {
TextField("Path", text: $devRoot)
Expand Down
Loading
Loading