Skip to content

ios(widgets+live activity): polish notifs, lock screen, and live activity surfaces#207

Merged
arul28 merged 2 commits into
mainfrom
ade/mobile-notifs-and-widgets-fixes-15875e46
Apr 26, 2026
Merged

ios(widgets+live activity): polish notifs, lock screen, and live activity surfaces#207
arul28 merged 2 commits into
mainfrom
ade/mobile-notifs-and-widgets-fixes-15875e46

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented Apr 26, 2026

Summary

  • Lock screen, control, and workspace widgets refreshed with new layouts and accent treatments
  • Live Activity views and previews reworked for richer agent/PR status surfaces
  • Shared models/container updated to support new awaiting-input semantics + sync hooks

Test plan

  • Build iOS targets (App + Widgets) in Xcode
  • Verify lock screen rectangular/circular/inline complications render correctly
  • Trigger a Live Activity and verify Dynamic Island + lock screen variants
  • Confirm widget previews match expected snapshots

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Style

    • Streamlined widget and control labels for clarity (e.g., "Open" instead of "Open ADE")
    • Updated mute toggle icon and label formatting for consistency
  • New Features

    • Enhanced session status indicators across widgets and lock screen, now distinguishing running, awaiting input, and idle states
    • Improved Dynamic Island and lock screen display layouts with clearer visual status markers
    • Better session count tracking and display throughout the app

Greptile Summary

This PR refactors the iOS widget and Live Activity surfaces to support a three-state session model (running / awaiting-input / idle), replacing per-session awaiting-input roster rows with aggregate count chips, and polishing the visual language across Dynamic Island, Lock Screen, and home widget views.

  • activeSessions semantic change in SyncService: the field now stores all live chat sessions (running + awaiting-input + idle), but the newly added doc-comment still reads "Roster only — sessions whose runtime is actively producing output", which directly contradicts its actual contents and could silently break existing consumers that relied on the old "running/awaiting only" contract.

Confidence Score: 4/5

Safe to merge with one doc-comment fix; the semantic change to activeSessions is the main risk.

One P1 finding: the activeSessions doc-comment contradicts its new contents and could mislead future consumers. The rest of the changes are well-structured visual/layout work with correct backward-compatible decoders and no security concerns.

apps/ios/ADE/Services/SyncService.swift — verify all existing consumers of activeSessions handle the expanded set (running + awaiting + idle) correctly after this change.

Important Files Changed

Filename Overview
apps/ios/ADE/Services/SyncService.swift Refactors session categorisation into running/awaiting/idle buckets; activeSessions semantics changed to include all live sessions (not just running) with a misleading "Roster only" doc-comment that could break existing consumers.
apps/ios/ADE/Shared/ADESharedModels.swift Adds awaitingInputCount/idleCount fields with backward-compatible custom decoder and a runningAgents computed filter; filter is a negative exclusion list that doesn't explicitly guard against status == "awaiting_input" when awaitingInput == false.
apps/ios/ADE/Services/LiveActivityCoordinator.swift Adds dismissal-cooldown suppression with user-dismissal observer (activityStateTask) and passes the new awaitingInputCount/idleCount through reconcile; correctly guards attention signals from the cooldown window.
apps/ios/ADEWidgets/ADELiveActivityViews.swift Significant visual refactor: replaces brand-dot roster with uniform green ActiveDot, adds CountsStrip for chat/PR counts, and unifies lock-screen + expanded glance surfaces; ExpandedGlanceStrip now re-uses CountsStrip which can produce redundant attention+count duplication.
apps/ios/ADEWidgets/ADELockScreenWidget.swift Rectangular view refactored with conditional progress bar and secondary line; circular gauge split into running/waiting/idle branches; awaiting-input deep link removed in favour of PR fallback.
apps/ios/ADEWidgets/ADEWidgetPreviewData.swift Replaces real-DB fixtures with explicitly synthetic UUIDs and labels; adds REAL_CURRENT/RICH/PRS_ONLY ContentState constants covering new count fields.
apps/ios/ADEWidgets/ADELiveActivityPreviews.swift New activity-level canvas previews for all DI/lock-screen regions; force-unwrapped ATTN_STATES subscripts will crash the canvas if any attention key is missing.
apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift Home widget views updated to use runningAgents and new count fields; consistent use of helper methods and cleaner visual treatment throughout.
apps/ios/ADE/Shared/ADESharedContainer.swift Inline summary updated to show running/waiting/idle counts separately; logic is straightforward and correct.
apps/ios/ADEWidgets/ADEControlWidget.swift Label copy trimmed and mute icon updated to bell.slash.fill; cosmetic only.

Sequence Diagram

sequenceDiagram
    participant SS as SyncService
    participant LAC as LiveActivityCoordinator
    participant CS as ContentState
    participant LA as Live Activity (iOS)

    SS->>SS: refreshActiveSessionsAndSnapshot()
    Note over SS: Partition sessions into<br/>runningAgents / awaitingInputCount / idleCount
    SS->>SS: activeSessions = allAgents
    SS->>LAC: reconcile(with: runningAgents, awaitingInputCount:, idleCount:)
    LAC->>LAC: shouldStartFreshActivity(for:)?
    Note over LAC: Cooldown suppressed if<br/>attention != nil
    LAC->>CS: makeContentState(...)
    CS->>CS: selectAttention() → .awaitingInput
    LAC->>LA: update / start activity
    LA-->>LAC: activityStateUpdates (.dismissed)
    LAC->>LAC: lastUserDismissalAt = Date()
Loading

Comments Outside Diff (4)

  1. apps/ios/ADEWidgets/ADEWidgetPreviewData.swift, line 1879-1923 (link)

    P2 Real database UUIDs and local filesystem path committed to source

    The preview fixtures embed real session and PR UUIDs sampled from the developer's actual on-disk database (/Users/arul/ADE/.ade/ade.db), and the file path itself is preserved in a comment. Although this is #if DEBUG-only, it still commits live internal IDs and a machine-specific absolute path to the repository history. Consider replacing the UUIDs with clearly synthetic stand-ins (e.g. "real-session-1") and removing the path from the comment.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ios/ADEWidgets/ADEWidgetPreviewData.swift
    Line: 1879-1923
    
    Comment:
    **Real database UUIDs and local filesystem path committed to source**
    
    The preview fixtures embed real session and PR UUIDs sampled from the developer's actual on-disk database (`/Users/arul/ADE/.ade/ade.db`), and the file path itself is preserved in a comment. Although this is `#if DEBUG`-only, it still commits live internal IDs and a machine-specific absolute path to the repository history. Consider replacing the UUIDs with clearly synthetic stand-ins (e.g. `"real-session-1"`) and removing the path from the comment.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. apps/ios/ADEWidgets/ADELiveActivityPreviews.swift, line 678-683 (link)

    P2 Force-unwrapped dictionary subscripts crash the canvas if a key is missing

    Every ATTN_STATES[.someKey]! call will crash the Xcode canvas if ATTN_STATES ever omits that key. Since this is #if DEBUG it won't ship, but a missing attention-kind entry will silently break all canvas previews without a helpful error. Consider using guard let or providing a fallback state.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ios/ADEWidgets/ADELiveActivityPreviews.swift
    Line: 678-683
    
    Comment:
    **Force-unwrapped dictionary subscripts crash the canvas if a key is missing**
    
    Every `ATTN_STATES[.someKey]!` call will crash the Xcode canvas if `ATTN_STATES` ever omits that key. Since this is `#if DEBUG` it won't ship, but a missing attention-kind entry will silently break all canvas previews without a helpful error. Consider using `guard let` or providing a fallback state.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  3. apps/ios/ADEWidgets/ADELockScreenWidget.swift, line 1797-1803 (link)

    P2 Circular gauge ring is always 100% full when active > 0

    Gauge(value: Double(active), in: 0...Double(max(active, 1))) sets the upper bound to active itself, so the ring is permanently at full fill regardless of total chat count. The current range gives the ring no contextual meaning beyond "something is running." If the intent is a purely boolean indicator, a simple Circle stroke might read more clearly than a gauge stuck at 100%.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ios/ADEWidgets/ADELockScreenWidget.swift
    Line: 1797-1803
    
    Comment:
    **Circular gauge ring is always 100% full when `active > 0`**
    
    `Gauge(value: Double(active), in: 0...Double(max(active, 1)))` sets the upper bound to `active` itself, so the ring is permanently at full fill regardless of total chat count. The current range gives the ring no contextual meaning beyond "something is running." If the intent is a purely boolean indicator, a simple `Circle` stroke might read more clearly than a gauge stuck at 100%.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  4. apps/ios/ADE/Services/SyncService.swift, line 281-283 (link)

    P1 activeSessions doc-comment contradicts new semantics

    The newly added line says "Roster only — sessions whose runtime is actively producing output", but the assignment activeSessions = allAgents stores every live chat — including idle and awaiting-input sessions. Any existing observer of activeSessions that relied on the old "running/awaiting only" contract will now silently receive a larger set.

    The mismatch between the doc-comment and the actual contents makes the property dangerous to consume: a caller reading the comment would filter assuming the list is already trimmed, and either under-count or expose stale-idle rows that were specifically excluded before this change.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ios/ADE/Services/SyncService.swift
    Line: 281-283
    
    Comment:
    **`activeSessions` doc-comment contradicts new semantics**
    
    The newly added line says *"Roster only — sessions whose runtime is actively producing output"*, but the assignment `activeSessions = allAgents` stores every live chat — including idle and awaiting-input sessions. Any existing observer of `activeSessions` that relied on the old "running/awaiting only" contract will now silently receive a larger set.
    
    The mismatch between the doc-comment and the actual contents makes the property dangerous to consume: a caller reading the comment would filter assuming the list is already trimmed, and either under-count or expose stale-idle rows that were specifically excluded before this change.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/ios/ADE/Services/SyncService.swift
Line: 281-283

Comment:
**`activeSessions` doc-comment contradicts new semantics**

The newly added line says *"Roster only — sessions whose runtime is actively producing output"*, but the assignment `activeSessions = allAgents` stores every live chat — including idle and awaiting-input sessions. Any existing observer of `activeSessions` that relied on the old "running/awaiting only" contract will now silently receive a larger set.

The mismatch between the doc-comment and the actual contents makes the property dangerous to consume: a caller reading the comment would filter assuming the list is already trimmed, and either under-count or expose stale-idle rows that were specifically excluded before this change.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/ios/ADE/Shared/ADESharedModels.swift
Line: 526-533

Comment:
**`runningAgents` excludes `awaiting_input` status string only implicitly**

The filter relies entirely on `!agent.awaitingInput` to exclude waiting sessions, but does not check `agent.status.lowercased() != "awaiting_input"`. A session decoded from a snapshot where `awaitingInput` was accidentally stored as `false` but whose `status` is `"awaiting_input"` would pass the filter and appear in the roster as a running session.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/ios/ADEWidgets/ADELiveActivityViews.swift
Line: 782-784

Comment:
**`ExpandedGlanceStrip` now surfaces chat counts that duplicate the attention card**

`ExpandedGlanceStrip` has been replaced with `CountsStrip`, which renders `awaitingInputCount` and `idleCount` chips alongside the PR chips. When `awaitingInputCount > 0`, the coordinator also sets `state.attention = .awaitingInput`, so the expanded Dynamic Island shows "N chats waiting for input" in the attention region *and* a "N waiting" chip in the bottom counts strip simultaneously.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "ship: iter 1 — address PR #207 review (c..." | Re-trigger Greptile

…vity surfaces

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented Apr 26, 2026

@copilot review but do not make fixes

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Apr 26, 2026 10:13pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

Session state management is refactored across the iOS ADE system to segregate chat sessions into running, awaiting-input, and idle states. SyncService now derives and publishes separate counts for awaiting-input and idle sessions, and WorkspaceSnapshot carries these aggregated counts alongside a new runningAgents computed property. Widget and Live Activity UI is updated to reflect these distinct states and render counts accordingly.

Changes

Cohort / File(s) Summary
Service & Data Layer
apps/ios/ADE/Services/SyncService.swift, apps/ios/ADE/Shared/ADESharedModels.swift, apps/ios/ADE/Shared/ADESharedContainer.swift
SyncService now separates chat sessions by state and publishes awaitingInputSessionsCount and idleSessionsCount. WorkspaceSnapshot gains awaitingInputCount, idleCount stored properties and a runningAgents computed property with custom decoding for backward compatibility. ADESharedContainer updates inline summary logic to use aggregated counts instead of filtering agents.
Widget Control UI
apps/ios/ADEWidgets/ADEControlWidget.swift
Button labels shortened ("Open ADE" → "Open", "Mute pushes" → "Mute") and mute-state icon changed from moon.fill to bell.slash.fill.
Workspace Widget Views
apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift, apps/ios/ADEWidgets/ADELockScreenWidget.swift
Small/medium/large widget UI reworked to render states (running/waiting/idle) derived from snapshot counts and running agents; roster displays only running/failed agents; deep-linking and progress rendering updated to account for running agents subset; circular widget behavior restructured with conditional RUN/WAIT/idle gauges.
Live Activity
apps/ios/ADEWidgets/ADELiveActivity.swift, apps/ios/ADEWidgets/ADELiveActivityViews.swift
ADESessionAttributes.ContentState adds awaitingInputCount and idleCount with custom decoding and defaults. Dynamic Island layout refactored: removed multi-agent dots and timer rendering; replaced with pulsing success indicator, new dot primitives (ActiveDot, ActiveDotMini), and count-based display logic; Lock Screen redesigned with edge-to-edge padding, CountsStrip/CountChip system, and truncated roster based on attention.
Live Activity Previews
apps/ios/ADEWidgets/ADELiveActivityPreviews.swift
Expanded preview suite with whole-activity, Dynamic Island (compact/expanded/minimal), and attention-oriented content state combinations; added shared attributes constant and real-data variant previews.
Preview & Test Data
apps/ios/ADEWidgets/ADEWidgetPreviewData.swift
Introduced real database-sourced agent/PR roster snapshots and three workspace snapshot variants (current, rich with attention counts, PRs-only); added Live Activity ContentState fixtures with explicit awaiting/idle count fields; mock roster updated to remove awaiting-input session entry; new Preview macros added for widget/lock-screen variants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

ios

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: iOS widget and Live Activity UI refinements with focus on notifications, lock screen, and Live Activity surfaces.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/mobile-notifs-and-widgets-fixes-15875e46

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.

Comment thread apps/ios/ADE/Services/LiveActivityCoordinator.swift
Copy link
Copy Markdown

@capy-ai capy-ai Bot left a comment

Choose a reason for hiding this comment

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

Added 1 comment

/// user-dismiss gesture is treated uniformly — within the cooldown window,
/// no flavor of LA (ambient running roster, attention banner, count
/// summary) is allowed to come back. Silence is silence.
private func shouldStartFreshActivity(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[🟡 Medium] [🔵 Bug]

The dismissedCooldown property doc (line 59-62) says "Attention signals (awaiting-input / failed / CI failing / etc.) override the cooldown because the user actually needs to see those." However, shouldStartFreshActivity accepts state as a parameter but never checks state.attention — it only checks elapsed time. The function's own doc at line 203-206 explicitly contradicts this: "no flavor of LA... is allowed to come back. Silence is silence."

If the Configuration doc is correct (attention should override), urgent signals like failed CI, awaiting user input, or review requests will be silently suppressed for 10 minutes after a user dismisses the Live Activity. If the function doc is correct, the Configuration property doc is misleading and should be updated.

// Configuration doc says attention overrides, but this never checks state.attention:
private func shouldStartFreshActivity(
    for state: ADESessionAttributes.ContentState
) -> Bool {
    guard let dismissedAt = lastUserDismissalAt else { return true }
    if Date().timeIntervalSince(dismissedAt) >= configuration.dismissedCooldown {
        lastUserDismissalAt = nil
        return true
    }
    return false  // attention signals are silently suppressed
}

Either add if state.attention != nil { return true } before the cooldown check, or update the Configuration.dismissedCooldown doc to remove the override promise.

Copy link
Copy Markdown

@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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ios/ADEWidgets/ADEWidgetPreviewData.swift (1)

63-68: ⚠️ Potential issue | 🟡 Minor

populatedSnapshot no longer covers the waiting-state surfaces.

sampleAgents still contains an awaiting-input session, but this snapshot leaves the new aggregate counts at their default 0 values. Most widget previews that use populatedSnapshot will therefore miss the waiting/standby UI path entirely.

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

In `@apps/ios/ADEWidgets/ADEWidgetPreviewData.swift` around lines 63 - 68,
populatedSnapshot currently uses sampleAgents (which includes an awaiting-input
session) but leaves WorkspaceSnapshot aggregate counts at their defaults, so
waiting/standby UI paths are not exercised; update the populatedSnapshot
construction to set the snapshot's aggregate/waiting counts to reflect
sampleAgents' awaiting-input session (either by passing the correct aggregate
parameters to the WorkspaceSnapshot initializer or by computing and assigning
the counts from sampleAgents before creating populatedSnapshot) so previews
using populatedSnapshot will render the waiting/standby surfaces.
apps/ios/ADEWidgets/ADELiveActivityViews.swift (1)

307-336: ⚠️ Potential issue | 🟡 Minor

Honor accessibilityReduceMotion for the expanded-leading pulse.

WorkspaceCompactLeading, LockRosterRow, ExpandedRosterStrip, and FocusedCardBottom all read @Environment(\.accessibilityReduceMotion) and disable the pulse when reduce-motion is on. WorkspaceExpandedLeading is the odd one out — line 322 hardcodes pulse: true, so users with Reduce Motion enabled still see the phased halo animation here.

♿ Proposed fix
 struct WorkspaceExpandedLeading: View {
     let state: ADESessionAttributes.ContentState
+    `@Environment`(\.accessibilityReduceMotion) private var reduceMotion

     var body: some View {
         Group {
             if let attention = state.attention {
                 …
             } else if !state.sessions.isEmpty {
-                ActiveDotMini(color: ADESharedTheme.statusSuccess, pulse: true)
+                ActiveDotMini(color: ADESharedTheme.statusSuccess, pulse: !reduceMotion)
                     .scaleEffect(1.4)
                     .frame(width: 28, height: 28)

As per coding guidelines, this falls under "proper SwiftUI patterns" for apps/ios/**/*.swift.

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

In `@apps/ios/ADEWidgets/ADELiveActivityViews.swift` around lines 307 - 336,
WorkspaceExpandedLeading currently hardcodes the pulsing halo by passing pulse:
true to ActiveDotMini, which ignores the user's Reduce Motion setting; add an
`@Environment`(\.accessibilityReduceMotion) private var accessibilityReduceMotion
to the WorkspaceExpandedLeading view and change the ActiveDotMini initializer to
pulse: !accessibilityReduceMotion (i.e., disable the pulse when
accessibilityReduceMotion is true) so behavior matches other components like
WorkspaceCompactLeading and LockRosterRow.
🧹 Nitpick comments (1)
apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift (1)

332-334: WorkspaceIdleState carries dead branches under its only call site.

WorkspaceIdleState is rendered only inside the gate at lines 332-334, which already requires awaitingInputCount == 0 && idleCount == 0 and an empty openPrs. Inside the view:

  • idleTitle (lines 446-452): the awaitingInputCount > 0 || idleCount > 0 branch is unreachable, so it always returns "ADE · idle".
  • idleSummary (lines 454-468): every part is gated on counts that are guaranteed 0 plus openPrs > 0 which is also false, so it always returns nil and the if let summary = idleSummary { … } block at lines 433-440 never renders.
  • var snapshot: WorkspaceSnapshot? = nil (line 418): the default is never used since the call site always passes a snapshot.

Either drop the dead "standby"/summary code (and the optional snapshot), or loosen the parent gate to runningAgents.isEmpty && openPrs.isEmpty so this view actually gets to render its richer states.

♻️ Option A — trim to current behavior
 private struct WorkspaceIdleState: View {
     let accented: Bool
-    var snapshot: WorkspaceSnapshot? = nil

     var body: some View {
-        VStack(alignment: .leading, spacing: 6) {
+        VStack(alignment: .leading, spacing: 6) {
             Spacer(minLength: 0)
             HStack(spacing: 8) {
                 Circle()
                     .fill(accented ? Color.primary.opacity(0.5) : WorkspaceWidgetPalette.textQuaternary)
                     .frame(width: 8, height: 8)
-                Text(idleTitle)
+                Text("ADE · idle")
                     .font(.system(size: 13.5, weight: .semibold))
                     .kerning(-0.1)
                     .lineLimit(1)
                     .foregroundStyle(accented ? Color.primary : WorkspaceWidgetPalette.textPrimary)
             }
-            if let summary = idleSummary { … }
             Spacer(minLength: 0)
         }
         .frame(maxWidth: .infinity, alignment: .leading)
     }
-
-    private var idleTitle: String { … }
-    private var idleSummary: String? { … }
 }
♻️ Option B — keep the rich view, expand the gate
-            if snapshot.runningAgents.isEmpty && openPrs.isEmpty
-                && snapshot.awaitingInputCount == 0 && snapshot.idleCount == 0 {
+            if snapshot.runningAgents.isEmpty && openPrs.isEmpty {
                 WorkspaceIdleState(accented: accented, snapshot: snapshot)
             } else {

Also applies to: 416-468

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

In `@apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift` around lines 332 - 334,
WorkspaceIdleState contains unreachable branches because its only call site
already guarantees awaitingInputCount == 0, idleCount == 0 and openPrs.isEmpty;
to fix, trim the dead standby/summary code and the unnecessary optional
snapshot: update WorkspaceIdleState to accept a non-optional snapshot (change
var snapshot: WorkspaceSnapshot? = nil to snapshot: WorkspaceSnapshot), simplify
idleTitle to the single `"ADE · idle"` branch (remove the
awaitingInputCount/idleCount conditional), remove idleSummary logic (or make it
always nil) and delete the if-let summary rendering block, and update any
initializers/uses accordingly since callers already pass a snapshot;
alternatively, if you prefer to keep the richer UI instead, loosen the parent
gate to only require runningAgents.isEmpty && openPrs.isEmpty so the existing
branches can become reachable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ios/ADE/Services/LiveActivityCoordinator.swift`:
- Around line 196-216: The cooldown gate in shouldStartFreshActivity currently
only checks lastUserDismissalAt and dismissedCooldown, ignoring urgent attention
signals; update shouldStartFreshActivity(for:) to return true immediately if the
incoming ADESessionAttributes.ContentState contains a non-nil/priority attention
(e.g., check state.attention or the same predicate used by selectAttention), so
attention overrides the cooldown, and clear lastUserDismissalAt when bypassing;
also revise the surrounding comment to reflect that attention-level events can
resurrect the Live Activity despite a recent user dismissal.

In `@apps/ios/ADE/Shared/ADESharedModels.swift`:
- Around line 130-138: The decoder currently defaults awaitingInputCount and
idleCount to 0 when keys are missing; change init(from decoder: Decoder) so that
if decodeIfPresent returns nil you derive those counts from the decoded agents
array instead of zeroing them — e.g. compute awaitingInputCount by counting
AgentSnapshot entries in agents that indicate an awaiting-input state and
idleCount by counting entries indicating idle (use the AgentSnapshot
status/flags your model exposes), then assign those derived values; keep
existing behavior when the keys are present. Ensure this preserves existing
fields generatedAt, agents, prs, connection and interacts correctly with
runningAgents logic.

In `@apps/ios/ADEWidgets/ADEWidgetPreviewData.swift`:
- Around line 72-76: The checked-in preview fixtures in ADEWidgetPreviewData
(the real ADE data block and the larger preview dataset around the MARK comment
and lines 78–216) contain developer-specific paths, PR titles, branch names and
real UUIDs; replace that hard-coded real workspace data with sanitized/generated
sample data: remove the local filesystem path and any real strings, replace
UUIDs with freshly generated placeholders (e.g., UUID()), replace
PR/branch/title strings with generic examples, or construct the preview data via
a small factory method (e.g., makeSampleADEPreviewData()) used by the previews
so fixtures are deterministic and non-sensitive while preserving shape and types
used by the widget preview code in ADEWidgetPreviewData.

In `@apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift`:
- Line 117: The UI currently checks agent.status == "failed" (e.g.,
SmallStatusDot(failed: focus?.status == "failed"), smallStatusLabel, rowDotColor
and the inline "failed" Text) but WorkspaceSnapshot.runningAgents filters out
failed agents, making those branches dead; choose one of two fixes: (A) keep
roster-level failed rendering — modify WorkspaceSnapshot.runningAgents to stop
filtering out status.lowercased() == "failed" (only filter out
idle/ended/completed) so focusAgent(), WorkspaceMediumView and
WorkspaceLargeView can surface recently-failed agents, or (B) remove the dead UI
— delete/stop-setting the failed flag in SmallStatusDot, remove the failed
branch in smallStatusLabel, the inline failed Text, and the failed branch in
rowDotColor so the roster matches the current runningAgents behavior; update
corresponding uses in focusAgent(), SmallStatusDot, smallStatusLabel,
WorkspaceMediumView and WorkspaceLargeView accordingly.

---

Outside diff comments:
In `@apps/ios/ADEWidgets/ADELiveActivityViews.swift`:
- Around line 307-336: WorkspaceExpandedLeading currently hardcodes the pulsing
halo by passing pulse: true to ActiveDotMini, which ignores the user's Reduce
Motion setting; add an `@Environment`(\.accessibilityReduceMotion) private var
accessibilityReduceMotion to the WorkspaceExpandedLeading view and change the
ActiveDotMini initializer to pulse: !accessibilityReduceMotion (i.e., disable
the pulse when accessibilityReduceMotion is true) so behavior matches other
components like WorkspaceCompactLeading and LockRosterRow.

In `@apps/ios/ADEWidgets/ADEWidgetPreviewData.swift`:
- Around line 63-68: populatedSnapshot currently uses sampleAgents (which
includes an awaiting-input session) but leaves WorkspaceSnapshot aggregate
counts at their defaults, so waiting/standby UI paths are not exercised; update
the populatedSnapshot construction to set the snapshot's aggregate/waiting
counts to reflect sampleAgents' awaiting-input session (either by passing the
correct aggregate parameters to the WorkspaceSnapshot initializer or by
computing and assigning the counts from sampleAgents before creating
populatedSnapshot) so previews using populatedSnapshot will render the
waiting/standby surfaces.

---

Nitpick comments:
In `@apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift`:
- Around line 332-334: WorkspaceIdleState contains unreachable branches because
its only call site already guarantees awaitingInputCount == 0, idleCount == 0
and openPrs.isEmpty; to fix, trim the dead standby/summary code and the
unnecessary optional snapshot: update WorkspaceIdleState to accept a
non-optional snapshot (change var snapshot: WorkspaceSnapshot? = nil to
snapshot: WorkspaceSnapshot), simplify idleTitle to the single `"ADE · idle"`
branch (remove the awaitingInputCount/idleCount conditional), remove idleSummary
logic (or make it always nil) and delete the if-let summary rendering block, and
update any initializers/uses accordingly since callers already pass a snapshot;
alternatively, if you prefer to keep the richer UI instead, loosen the parent
gate to only require runningAgents.isEmpty && openPrs.isEmpty so the existing
branches can become reachable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00b77a1b-7340-4754-a15e-dc40902294a0

📥 Commits

Reviewing files that changed from the base of the PR and between 60c6b58 and 1d92510.

📒 Files selected for processing (11)
  • apps/ios/ADE/Services/LiveActivityCoordinator.swift
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Shared/ADESharedContainer.swift
  • apps/ios/ADE/Shared/ADESharedModels.swift
  • apps/ios/ADEWidgets/ADEControlWidget.swift
  • apps/ios/ADEWidgets/ADELiveActivity.swift
  • apps/ios/ADEWidgets/ADELiveActivityPreviews.swift
  • apps/ios/ADEWidgets/ADELiveActivityViews.swift
  • apps/ios/ADEWidgets/ADELockScreenWidget.swift
  • apps/ios/ADEWidgets/ADEWidgetPreviewData.swift
  • apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift

Comment thread apps/ios/ADE/Services/LiveActivityCoordinator.swift
Comment thread apps/ios/ADE/Shared/ADESharedModels.swift Outdated
Comment thread apps/ios/ADEWidgets/ADEWidgetPreviewData.swift Outdated
Comment thread apps/ios/ADEWidgets/ADEWorkspaceWidgetViews.swift Outdated
…shot decode, scrub fixtures, drop dead failed UI)

- LiveActivityCoordinator: attention signals override the dismissal cooldown
- ADESharedModels: derive awaiting/idle counts from agents when keys absent
- ADEWidgetPreviewData: scrub real workspace data from preview fixtures
- ADEWorkspaceWidgetViews: remove unreachable failed-agent UI branches

Addresses: 3144225338, 3144229701, 3144229739, 3144229740, 3144229741, 3144229742

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented Apr 26, 2026

@copilot review but do not make fixes

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.

1 participant