Skip to content

feat: implement Dashboard views#107

Open
2witstudios wants to merge 3 commits intomainfrom
ppg/issue-83-dashboard
Open

feat: implement Dashboard views#107
2witstudios wants to merge 3 commits intomainfrom
ppg/issue-83-dashboard

Conversation

@2witstudios
Copy link
Owner

@2witstudios 2witstudios commented Feb 27, 2026

Summary

  • DashboardViewNavigationStack with Active/Completed worktree sections, pull-to-refresh, connection status indicator in toolbar, empty state and disconnected state
  • WorktreeCard — Status card showing name, branch, agent count, status badge with color-coded capsule
  • WorktreeDetailView — Inspector with agent list, diff stats section, merge/kill/PR action buttons with confirmation dialogs
  • AgentRow — Agent status row with kill (active agents) and restart (failed/killed) action buttons

All views use @Observable DashboardStore pattern and domain models (Worktree, Agent, WorktreeStatus, AgentStatus) aligned with the macOS app's model layer. Includes SwiftUI #Preview blocks for all states.

Closes #83

Test plan

  • Verify views compile in an iOS 17+ target
  • Preview DashboardView in connected, empty, and disconnected states
  • Preview WorktreeCard with running and merged worktrees
  • Preview WorktreeDetailView with mixed agent statuses
  • Preview AgentRow with running, completed, failed, and killed agents
  • Verify pull-to-refresh triggers store.refresh()
  • Verify navigation from worktree list to detail view
  • Verify kill/restart buttons appear only for appropriate agent states
  • Verify merge/kill confirmation dialogs in WorktreeDetailView

Summary by CodeRabbit

  • New Features
    • Added dashboard view with connection status indicators and project overview
    • Introduced agent management controls (kill and restart actions)
    • Added worktree status display with merge and kill operations
    • Implemented visual status indicators and agent error messaging
    • Created detailed views for agent and worktree information

Add SwiftUI views for the main dashboard experience:
- DashboardView: NavigationStack with Active/Completed worktree sections,
  pull-to-refresh, connection status indicator, empty and disconnected states
- WorktreeCard: status card with name, branch, agent count, status badge
- WorktreeDetailView: inspector with agent list, diff stats, merge/kill/PR actions
- AgentRow: agent status row with kill/restart action buttons

Includes @observable DashboardStore protocol and domain models (Worktree,
Agent, WorktreeStatus, AgentStatus) aligned with the macOS app's model layer.

Closes #83
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9843c5b and 8001339.

📒 Files selected for processing (6)
  • ios/PPGMobile/PPGMobile/Models/DashboardModels.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift
  • src/commands/spawn.test.ts
📝 Walkthrough

Walkthrough

Four new SwiftUI view files introduce a Dashboard UI module for iOS, comprising a main dashboard view with connection state management, domain models for worktrees and agents, a DashboardStore class for state coordination, and reusable view components for displaying worktree and agent details.

Changes

Cohort / File(s) Summary
Dashboard Core
ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift
Main dashboard view with navigation, refresh/retry actions, and connection state UI. Defines domain models (ConnectionState, Worktree, WorktreeStatus, Agent, AgentStatus) and @Observable DashboardStore with async methods for managing agents and worktrees. Includes preview helpers for connected, empty, and disconnected states.
Dashboard Components
ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift, ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift, ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift
Reusable UI components: WorktreeCard displays worktree summary with agent counts and status badge; WorktreeDetailView provides detailed inspector with agent list and merge/kill actions; AgentRow renders individual agent entries with conditional kill/restart buttons. All include preview implementations.

Sequence Diagram

sequenceDiagram
    actor User
    participant View as Dashboard View
    participant Store as DashboardStore
    participant Backend as Backend/API

    User->>View: Tap Action (Kill/Restart Agent)
    View->>Store: killAgent(_:in:) or restartAgent(_:in:)
    activate Store
    Store->>Backend: Perform Async Operation
    activate Backend
    Backend-->>Store: Response
    deactivate Backend
    Store->>View: Update connectionState/worktrees
    deactivate Store
    View->>View: Re-render with New State
    View-->>User: Display Updated UI
    
    User->>View: Pull to Refresh
    View->>Store: refresh()
    activate Store
    Store->>Backend: Fetch Manifest
    activate Backend
    Backend-->>Store: Updated Worktree Data
    deactivate Backend
    Store->>View: Update worktrees Array
    deactivate Store
    View-->>User: Display Refreshed Data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A dashboard bright with views so neat,
Worktrees dance in columns sweet,
Agents row by row displayed,
Async flows well-orchestrated!
Now dashboards bloom where data played! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides detailed summaries of all four new views, lists domain models, mentions the @Observable pattern, and includes a test plan, but does not follow the template structure. Restructure the description to match the template: add 'What', 'Why', 'How' sections, and replace the manual checklist with template requirements (npm test, npm run typecheck, tests for new behavior).
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: implement Dashboard views' directly and clearly summarizes the main change—adding new SwiftUI Dashboard UI components for iOS.
Linked Issues check ✅ Passed All acceptance criteria from issue #83 are addressed: DashboardView with NavigationStack, worktree sections, pull-to-refresh, connection indicator, empty/disconnected states; WorktreeCard displaying name, branch, agent count, status badge; WorktreeDetailView with agent list, diff stats, and actions; and AgentRow with kill/restart buttons.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #83 requirements: four new view files (DashboardView, WorktreeCard, WorktreeDetailView, AgentRow) with supporting domain models and previews, with no extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ppg/issue-83-dashboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (8)
ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift (1)

53-66: Minor inconsistency in status badge opacity.

The status badge here uses opacity(0.15) (line 63), while AgentRow.statusLabel uses opacity(0.12) (line 63 in AgentRow.swift). Consider using a consistent opacity value across the module for visual uniformity.

🎨 Proposed fix for consistency

Either change this to 0.12 to match AgentRow, or define a shared constant:

-.background(worktree.status.color.opacity(0.15))
+.background(worktree.status.color.opacity(0.12))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift` around lines 53 -
66, status badge opacity is inconsistent: WorktreeCard.statusBadge uses
opacity(0.15) while AgentRow.statusLabel uses opacity(0.12); update
WorktreeCard.statusBadge to use the same opacity (0.12) or extract a shared
constant (e.g., STATUS_BADGE_OPACITY) and use it in both
WorktreeCard.statusBadge and AgentRow.statusLabel so the module shares a single
source of truth for the opacity value.
ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift (5)

151-160: Consider adding Equatable and Hashable conformance to Worktree.

Worktree is used in ForEach and compared via first(where:). Adding Equatable enables SwiftUI to diff changes more efficiently. Since all stored properties are value types (or arrays of Identifiable items), synthesis is straightforward.

♻️ Proposed refinement
-struct Worktree: Identifiable {
+struct Worktree: Identifiable, Equatable, Hashable {
     let id: String
     // ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift` around lines 151
- 160, The Worktree struct should conform to Equatable and Hashable so SwiftUI
can diff it in ForEach and comparisons like first(where:); update the
declaration of Worktree (the struct named Worktree) to adopt Equatable and
Hashable (e.g. "struct Worktree: Identifiable, Equatable, Hashable") and ensure
its stored types (WorktreeStatus, Agent) also support these protocols (or add
conformance there) so the compiler can synthesize equality and hashing.

145-149: Consider making ConnectionState Equatable and Hashable.

The enum is compared using != on line 33. While Swift enums without associated values are implicitly Equatable, explicitly declaring conformance improves clarity and enables future use in collections or as dictionary keys.

💡 Proposed refinement
-enum ConnectionState {
+enum ConnectionState: Equatable, Hashable {
     case disconnected
     case connecting
     case connected
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift` around lines 145
- 149, The enum ConnectionState should explicitly conform to Equatable and
Hashable to make comparisons and future collection use explicit; update the enum
declaration for ConnectionState to adopt Equatable and Hashable (e.g., add :
Equatable, Hashable to the enum), rebuild to ensure no other code relies on
implicit conformance, and adjust any usages if needed (the != comparison that
compares ConnectionState values will continue to work).

196-206: Consider adding Equatable and Hashable conformance to Agent.

Similar to Worktree, Agent is used in ForEach loops and would benefit from Equatable for efficient diffing and Hashable for potential use in sets or dictionaries.

♻️ Proposed refinement
-struct Agent: Identifiable {
+struct Agent: Identifiable, Equatable, Hashable {
     let id: String
     // ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift` around lines 196
- 206, The Agent struct lacks Equatable and Hashable conformance, which hurts
efficient diffing in SwiftUI ForEach and prevents using Agent in
sets/dictionaries; update the Agent declaration (struct Agent: Identifiable) to
also conform to Equatable and Hashable (i.e., struct Agent: Identifiable,
Equatable, Hashable) and ensure any stored properties (id, name, agentType,
status, prompt, startedAt, completedAt, exitCode, error) either already conform
or provide custom implementations of == and hash(into:) on Agent if needed
(e.g., if AgentStatus is not Hashable/Equatable), mirroring the approach used
for Worktree.

247-261: DashboardStore methods are empty stubs — ensure implementation follows.

The store methods (refresh(), connect(), killAgent(), etc.) have empty bodies. The comment says "Store Protocol" but DashboardStore is a concrete class, not a protocol. If this is intentional placeholder code for the PR, consider adding TODO comments or fatalError("Not implemented") to make the incomplete state explicit during development.

Would you like me to suggest a protocol-based design if the intent is to enable dependency injection for testing?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift` around lines 247
- 261, The DashboardStore currently contains empty stub methods (refresh,
connect, killAgent, restartAgent, mergeWorktree, killWorktree) and is declared
as a concrete class though the comment says "Store Protocol"; fix by creating a
protocol (e.g., DashboardStoreProtocol) that declares these async methods and
properties (projectName, worktrees, connectionState), then implement a concrete
type (e.g., DefaultDashboardStore or have DashboardStore conform) with real
implementations or, if not yet implemented, replace empty bodies with explicit
placeholders such as a TODO comment and fatalError("Not implemented in
DashboardStore") to make the incomplete state explicit and enable DI for
testing; ensure you reference the protocol/implementation names and the exact
method signatures (refresh(), connect(), killAgent(_:in:), restartAgent(_:in:),
mergeWorktree(_:), killWorktree(_:)) when updating code so callers can compile.

67-71: Navigation destination may show empty view for stale worktree IDs.

If store.worktrees is refreshed and a worktree is removed while the user is viewing the list, tapping a stale NavigationLink will navigate to an empty view since the if let guard silently fails. Consider providing a fallback UI or handling the missing worktree case.

💡 Proposed enhancement
 .navigationDestination(for: String.self) { worktreeId in
     if let worktree = store.worktrees.first(where: { $0.id == worktreeId }) {
         WorktreeDetailView(worktree: worktree, store: store)
+    } else {
+        ContentUnavailableView("Worktree Not Found", systemImage: "questionmark.folder")
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift` around lines 67
- 71, The navigationDestination(for: String.self) handler can return an empty
view when a stale worktreeId no longer exists in store.worktrees; update the
handler in DashboardView to handle the missing-worktree case explicitly by
returning a fallback UI (e.g., a Text("Worktree not found") or a Retry/Back
view) or presenting an alert instead of silently doing nothing. Locate the
navigationDestination block that uses worktreeId and WorktreeDetailView, check
for let worktree = store.worktrees.first(where: { $0.id == worktreeId }), and
add an else branch that constructs a clear fallback UI (or triggers navigation
pop/alert) so users see a meaningful message when the worktree is removed.
ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift (1)

124-129: PR creation button has no action — add a TODO comment or disable if not ready.

The "Create Pull Request" button has an empty action closure. Consider adding a TODO comment to make this explicit, or wire it to a store method for consistency with other actions.

💡 Proposed clarification
 Button {
-    // PR creation — will be wired to store action
+    // TODO: Wire to store.createPullRequest(worktree.id)
 } label: {
     Label("Create Pull Request", systemImage: "arrow.triangle.pull")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift` around
lines 124 - 129, The Button in WorktreeDetailView currently has an empty action
closure; either add a clear TODO comment inside the closure (e.g., "// TODO:
wire Create Pull Request to store.createPullRequest(...)") so intent is
explicit, or wire it to the appropriate store action (call
store.createPullRequest(worktree) or similar) and handle result/async, and if
the feature isn't ready disable the button unconditionally (e.g., set
.disabled(true) or expand the existing .disabled condition). Update the Button
action in WorktreeDetailView and keep the existing .disabled(worktree.status !=
.running && worktree.status != .merged) logic consistent with the chosen
approach.
ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift (1)

70-95: Consider adding accessibility labels to action buttons.

The kill and restart buttons use only icons without text labels. For VoiceOver users, adding .accessibilityLabel() would improve accessibility.

♿ Proposed enhancement
 Button {
     onKill?()
 } label: {
     Image(systemName: "stop.fill")
         .font(.caption)
         .foregroundStyle(.red)
 }
 .buttonStyle(.borderless)
+.accessibilityLabel("Kill agent")

 // ...

 Button {
     onRestart?()
 } label: {
     Image(systemName: "arrow.counterclockwise")
         .font(.caption)
         .foregroundStyle(.blue)
 }
 .buttonStyle(.borderless)
+.accessibilityLabel("Restart agent")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift` around lines 70 - 95,
Update the icon-only buttons in the actionButtons ViewBuilder to provide
VoiceOver text by adding accessibility labels (and optional hints) to the Button
or Image; specifically, in AgentRow.swift inside the actionButtons computed
property, add .accessibilityLabel("Kill agent") (or localized string) to the
stop.fill button that invokes onKill and .accessibilityLabel("Restart agent") to
the arrow.counterclockwise button that invokes onRestart (you may also add
.accessibilityHint(...) and use localized strings for accessibility).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift`:
- Around line 70-95: Update the icon-only buttons in the actionButtons
ViewBuilder to provide VoiceOver text by adding accessibility labels (and
optional hints) to the Button or Image; specifically, in AgentRow.swift inside
the actionButtons computed property, add .accessibilityLabel("Kill agent") (or
localized string) to the stop.fill button that invokes onKill and
.accessibilityLabel("Restart agent") to the arrow.counterclockwise button that
invokes onRestart (you may also add .accessibilityHint(...) and use localized
strings for accessibility).

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift`:
- Around line 151-160: The Worktree struct should conform to Equatable and
Hashable so SwiftUI can diff it in ForEach and comparisons like first(where:);
update the declaration of Worktree (the struct named Worktree) to adopt
Equatable and Hashable (e.g. "struct Worktree: Identifiable, Equatable,
Hashable") and ensure its stored types (WorktreeStatus, Agent) also support
these protocols (or add conformance there) so the compiler can synthesize
equality and hashing.
- Around line 145-149: The enum ConnectionState should explicitly conform to
Equatable and Hashable to make comparisons and future collection use explicit;
update the enum declaration for ConnectionState to adopt Equatable and Hashable
(e.g., add : Equatable, Hashable to the enum), rebuild to ensure no other code
relies on implicit conformance, and adjust any usages if needed (the !=
comparison that compares ConnectionState values will continue to work).
- Around line 196-206: The Agent struct lacks Equatable and Hashable
conformance, which hurts efficient diffing in SwiftUI ForEach and prevents using
Agent in sets/dictionaries; update the Agent declaration (struct Agent:
Identifiable) to also conform to Equatable and Hashable (i.e., struct Agent:
Identifiable, Equatable, Hashable) and ensure any stored properties (id, name,
agentType, status, prompt, startedAt, completedAt, exitCode, error) either
already conform or provide custom implementations of == and hash(into:) on Agent
if needed (e.g., if AgentStatus is not Hashable/Equatable), mirroring the
approach used for Worktree.
- Around line 247-261: The DashboardStore currently contains empty stub methods
(refresh, connect, killAgent, restartAgent, mergeWorktree, killWorktree) and is
declared as a concrete class though the comment says "Store Protocol"; fix by
creating a protocol (e.g., DashboardStoreProtocol) that declares these async
methods and properties (projectName, worktrees, connectionState), then implement
a concrete type (e.g., DefaultDashboardStore or have DashboardStore conform)
with real implementations or, if not yet implemented, replace empty bodies with
explicit placeholders such as a TODO comment and fatalError("Not implemented in
DashboardStore") to make the incomplete state explicit and enable DI for
testing; ensure you reference the protocol/implementation names and the exact
method signatures (refresh(), connect(), killAgent(_:in:), restartAgent(_:in:),
mergeWorktree(_:), killWorktree(_:)) when updating code so callers can compile.
- Around line 67-71: The navigationDestination(for: String.self) handler can
return an empty view when a stale worktreeId no longer exists in
store.worktrees; update the handler in DashboardView to handle the
missing-worktree case explicitly by returning a fallback UI (e.g., a
Text("Worktree not found") or a Retry/Back view) or presenting an alert instead
of silently doing nothing. Locate the navigationDestination block that uses
worktreeId and WorktreeDetailView, check for let worktree =
store.worktrees.first(where: { $0.id == worktreeId }), and add an else branch
that constructs a clear fallback UI (or triggers navigation pop/alert) so users
see a meaningful message when the worktree is removed.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift`:
- Around line 53-66: status badge opacity is inconsistent:
WorktreeCard.statusBadge uses opacity(0.15) while AgentRow.statusLabel uses
opacity(0.12); update WorktreeCard.statusBadge to use the same opacity (0.12) or
extract a shared constant (e.g., STATUS_BADGE_OPACITY) and use it in both
WorktreeCard.statusBadge and AgentRow.statusLabel so the module shares a single
source of truth for the opacity value.

In `@ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift`:
- Around line 124-129: The Button in WorktreeDetailView currently has an empty
action closure; either add a clear TODO comment inside the closure (e.g., "//
TODO: wire Create Pull Request to store.createPullRequest(...)") so intent is
explicit, or wire it to the appropriate store action (call
store.createPullRequest(worktree) or similar) and handle result/async, and if
the feature isn't ready disable the button unconditionally (e.g., set
.disabled(true) or expand the existing .disabled condition). Update the Button
action in WorktreeDetailView and keep the existing .disabled(worktree.status !=
.running && worktree.status != .merged) logic consistent with the chosen
approach.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34deb69 and 9843c5b.

📒 Files selected for processing (4)
  • ios/PPGMobile/PPGMobile/Views/Dashboard/AgentRow.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/DashboardView.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeCard.swift
  • ios/PPGMobile/PPGMobile/Views/Dashboard/WorktreeDetailView.swift

- Extract domain models to Models/DashboardModels.swift (was embedded
  in DashboardView.swift)
- Add DiffStats model and Changes section to WorktreeDetailView
- Fix stale worktree data: WorktreeDetailView now takes worktreeId and
  derives worktree from store, so refreshes propagate to detail view
- Add else branch to navigationDestination for missing worktree IDs
- Add confirmation dialog to individual agent kill in AgentRow
- Add store.createPullRequest(for:) and wire PR button with TODO
- Gate all #Preview blocks and preview helpers with #if DEBUG
- Add worktree(by:) lookup helper to DashboardStore
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.

Implement Dashboard views

1 participant