Skip to content

Fix TUI control-plane convergence regressions - #217

Merged
TraderSamwise merged 8 commits into
masterfrom
chore/next-20260621-control-plane
Jun 22, 2026
Merged

Fix TUI control-plane convergence regressions#217
TraderSamwise merged 8 commits into
masterfrom
chore/next-20260621-control-plane

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • keep dashboard open/focus routes fast by moving resume out of notification-target focus and treating request timeouts separately from dead services
  • reconcile dashboard, graveyard, and coordination state after async mutation settle/error paths
  • refresh runtime guard immediately after forced dashboard service refresh failures

Verification

  • yarn typecheck
  • yarn lint --quiet
  • yarn vitest --run
  • yarn build

Summary by CodeRabbit

  • New Features
    • Added a dedicated HTTP timeout error type and timeout detection to enable more precise retry/recovery behavior.
  • Improvements
    • Focus and notification-target actions now enforce stronger validation, use the live client tty when sessions are stale, and handle offline/exited states more consistently (including early offline responses).
    • Dashboard activation and notification opening now return clearer activation outcomes (“opened/missing/error/blocked”), retry timeouts more reliably, and refresh views more consistently after changes.
    • Coordination reachability now treats “exited” as offline.
  • Bug Fixes
    • Notification and coordination mutations now fail more predictably with appropriate UI feedback and recovery refreshes.

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 22, 2026 2:54am

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b302e9e-3615-4877-8462-d17652d5394a

📥 Commits

Reviewing files that changed from the base of the PR and between 62f4524 and ecf2ce3.

📒 Files selected for processing (2)
  • src/multiplexer/dashboard-control.test.ts
  • src/multiplexer/dashboard-control.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/multiplexer/dashboard-control.ts

📝 Walkthrough

Walkthrough

Introduces HttpTimeoutError with ETIMEDOUT code, refactors metadata-server focus-session resolution and offline/exited target handling to return 409 immediately, removes endpoint pre-verification from the dashboard control retry loop in favor of error-type classification, adds DashboardActivationResult return type to dashboard activation methods, schedules dashboard model reconciliation after pending-action settlement, gates notification opening on activation success, treats exited session status as offline in coordination model, and refreshes dashboard model after graveyard mutations.

Changes

Offline/stale state handling across dashboard, notifications, and control

Layer / File(s) Summary
HttpTimeoutError class and predicate
src/http-client.ts
Exports HttpTimeoutError with code = "ETIMEDOUT" and timeoutMs; exports isHttpTimeoutError predicate; requestJson timeout handler destroys with HttpTimeoutError instead of generic Error.
Metadata server focus session resolution and offline 409
src/metadata-server.ts, src/metadata-server.test.ts
Adds resolveControlFocusClientSession helper and tightens validateControlFocusContext to require clientTty and verify project membership. openNotificationTarget returns 409 immediately for offline/exited targets instead of resuming. openDashboard, openInbox, focusWindow, activeWindow derive focusClientSession via the new helper. Tests assert 409, no resume/tmux-resolution calls, and correct client-tty selection.
Dashboard control plane retry/recovery refactor
src/multiplexer/dashboard-control.ts, src/multiplexer/dashboard-control.test.ts
Removes verifyProjectServiceEndpoint pre-verification; requestProjectService issues requests directly and uses isHttpTimeoutError to retry GET timeouts without restart, trigger recovery on ECONNREFUSED, or surface POST timeouts. ensureDashboardControlPlane returns early for concurrent non-restart recovery. waitAndOpenLiveTmuxWindowForEntry derives timeout from entry.status. Tests cover timeout/refused/503/concurrency scenarios and offline activation with restore-sized timeouts.
DashboardActivationResult type and offline activation flow
src/multiplexer/dashboard-interaction.ts, src/multiplexer/dashboard-interaction.test.ts
Exports DashboardActivationResult union type. activateDashboardService and activateDashboardEntry return it with opened/missing/blocked branches. Offline/exited paths resume sessions, refresh dashboard model, wait for live tmux window with status-derived timeout, and set footer flash on miss.
Dashboard model reconciliation after pending actions
src/multiplexer/dashboard-model.ts, src/multiplexer/dashboard-model.test.ts, src/multiplexer/dashboard-model-service.test.ts
Adds scheduleDashboardModelReconcile that calls refreshDashboardModelFromService(true) and re-renders on apply; hooks into session/service pending settle and failure paths. Adds failDashboardServiceRefresh to guard refreshRuntimeGuard calls on force mode.
Coordination model exited target reachability
src/coordination-model.ts, src/coordination-model.test.ts
resolveReachability now treats session status "exited" as "offline" reachability. Formatting updates to buildCoordinationModel and buildCoordinationView signatures. Tests verify exited targets are classified offline and remain actionable.
Notification opening gated on activation result and failure recovery
src/multiplexer/notifications.ts, src/multiplexer/notifications.test.ts, src/multiplexer/coordination.ts
Adds activationSucceeded helper; openCoordinationNotification checks activation result before proceeding to settle. notificationTargetState classifies "exited" status as "offline". settle wraps mark-read path in try/catch with footerFlash and refreshCoordinationFromService on failure. Coordination mutation failure triggers reloadCoordination before re-render.
Graveyard mutation dashboard model refresh
src/multiplexer/archives.ts, src/multiplexer/archives.test.ts
Adds refreshDashboardAfterGraveyardMutation helper; resurrectGraveyardEntry and deleteSelectedGraveyardWorktree call it after graveyard refresh in dashboard mode; error path surfaces via showDashboardError and also refreshes model. Tests set up beforeEach hooks and assert dashboard refresh on success/failure instead of direct graveyard fetch assertions.

Sequence Diagram(s)

sequenceDiagram
    participant NotifScreen as Notifications
    participant DashActivate as DashboardInteraction
    participant DashControl as DashboardControl
    participant MetadataServer as MetadataServer
    participant HttpClient as HttpClient

    rect rgba(100, 100, 200, 0.5)
        Note over NotifScreen,HttpClient: Notification target open: offline returns 409
        NotifScreen->>MetadataServer: POST /control/open-notification-target
        MetadataServer->>MetadataServer: resolveControlFocusClientSession(clientTty)
        MetadataServer->>MetadataServer: Check service/agent running state
        alt Offline/exited
            MetadataServer-->>NotifScreen: 409 (service is offline / agent is offline)
        end
    end

    rect rgba(100, 200, 100, 0.5)
        Note over NotifScreen,DashControl: Dashboard activation: offline session flow
        NotifScreen->>DashActivate: activateDashboardEntry(offlineEntry)
        DashActivate->>DashActivate: resumeOfflineSessionWithFeedback()
        DashActivate->>DashActivate: refreshDashboardModelFromService(true)
        DashActivate->>DashControl: waitAndOpenLiveTmuxWindowForEntry(entry, 60000ms)
        DashControl->>HttpClient: requestJson with retry loop
        HttpClient-->>DashControl: HttpTimeoutError (GET) → retry without restart
        HttpClient-->>DashControl: ECONNREFUSED → removeEndpoint + restart
        HttpClient-->>DashControl: 503 → restart before retry
        DashControl-->>DashActivate: "opened" | "missing"
        DashActivate-->>NotifScreen: DashboardActivationResult
        alt activationSucceeded(result)
            NotifScreen->>NotifScreen: settle() (mark read, refresh coordination)
        else activationFailed
            NotifScreen->>NotifScreen: failOpen() (skip settle)
        end
    end

    rect rgba(200, 100, 100, 0.5)
        Note over NotifScreen,DashControl: Pending action settlement: model reconciliation
        NotifScreen->>NotifScreen: withMetadataSessionPending(settle)
        Note over NotifScreen: settle() resolves
        DashActivate->>DashActivate: scheduleDashboardModelReconcile()
        DashActivate->>DashActivate: refreshDashboardModelFromService(true)
        DashActivate->>DashActivate: renderDashboard() if applied
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TraderSamwise/aimux#187: Directly overlaps with this PR's src/metadata-server.ts changes to openNotificationTarget/focusWindow behavior and focus-aware control-flow contracts.
  • TraderSamwise/aimux#215: Both modify requestProjectService/retry handling and ensureDashboardControlPlane behavior in src/multiplexer/dashboard-control.ts.
  • TraderSamwise/aimux#174: Both modify coordination model reachability semantics and offline/missing target handling across notifications and dashboard interaction.

Poem

🐇 Hoppity hop through the offline maze,
No more resuming in a half-dead haze!
A timeout code of ETIMEDOUT now gleams,
Focus sessions resolved by clientTty dreams.
Exited targets join the offline crew,
Models reconcile when pending actions brew! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.98% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely summarizes the main change: fixing TUI control-plane convergence regressions across multiple subsystems.
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.

✏️ 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 chore/next-20260621-control-plane

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

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent review finding resolved in 9ff9c27: openProjectServiceNotificationTarget treated agent/service is offline as a hard open error during restore, so stale pre-refresh rows could fail before the 60s restore budget elapsed. Fixed by classifying offline focus responses as retryable missing inside the wait loop and adding regression coverage.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent review finding resolved in 9ff9c27: Coordination reachability treated exited sessions as live, which could leave exited notification targets on the wrong screen path. Fixed by treating exited like offline in both the service-built coordination model and TUI notification target helper, with tests.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/multiplexer/notifications.ts (1)

179-189: ⚠️ Potential issue | 🟠 Major

Avoid .catch on an optional call result.

At line 187, host.refreshCoordinationFromService?.().catch(...) can still throw when the method is absent, because .catch is accessed on undefined. Guard the method before chaining to keep failure recovery non-throwing.

Suggested fix
   const settle = async () => {
     if (!unread) return;
     try {
       await markCoordinationItemRead(host, item);
       await host.refreshCoordinationFromService?.();
     } catch {
       host.footerFlash = "Notification update failed";
       host.footerFlashTicks = 3;
-      await host.refreshCoordinationFromService?.().catch(() => {});
+      if (host.refreshCoordinationFromService) {
+        await host.refreshCoordinationFromService().catch(() => {});
+      }
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/multiplexer/notifications.ts` around lines 179 - 189, The issue is in the
settle function where optional chaining on
host.refreshCoordinationFromService?.() is followed by .catch(), which throws
when the method is absent because .catch is being called on undefined. Guard the
optional call by either applying optional chaining to .catch as well using
.catch?.() to safely handle the case when the method doesn't exist, or
conditionally call the catch handler only when the method is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/multiplexer/notifications.ts`:
- Around line 179-189: The issue is in the settle function where optional
chaining on host.refreshCoordinationFromService?.() is followed by .catch(),
which throws when the method is absent because .catch is being called on
undefined. Guard the optional call by either applying optional chaining to
.catch as well using .catch?.() to safely handle the case when the method
doesn't exist, or conditionally call the catch handler only when the method is
present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74d14a6a-0d18-46ec-a33e-95ceec13b785

📥 Commits

Reviewing files that changed from the base of the PR and between ca00bbb and 9ff9c27.

📒 Files selected for processing (6)
  • src/coordination-model.test.ts
  • src/coordination-model.ts
  • src/multiplexer/dashboard-control.test.ts
  • src/multiplexer/dashboard-control.ts
  • src/multiplexer/notifications.test.ts
  • src/multiplexer/notifications.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/multiplexer/notifications.test.ts
  • src/multiplexer/dashboard-control.ts
  • src/multiplexer/dashboard-control.test.ts

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent round 2 finding resolved in 7cc3ad1: non-dashboard activation fallback restored only offline rows, while exited rows are also restorable. Fixed by treating exited like offline in the fallback path and adding regression coverage.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Resolved the CodeRabbit summary finding in 62f4524: guarded the optional coordination refresh before attaching .catch, and added regression coverage for the missing-refresh wiring path.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

Resolved independent reviewer finding in ecf2ce3: mutating project-service requests now validate the cached endpoint pid/manifest through a capped /health check before POST, while GETs keep the no-preflight fast path. Added regression coverage for stale manifest recovery before mutation.

@TraderSamwise
TraderSamwise merged commit c582dc3 into master Jun 22, 2026
3 checks passed
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