Skip to content

feat(pianola): add a "Watched by Pianola" section to the Dashboard - #1271

Merged
chr1syy merged 4 commits into
RunMaestro:rcfrom
chr1syy:followup/pianola-watch-control
Jul 22, 2026
Merged

feat(pianola): add a "Watched by Pianola" section to the Dashboard#1271
chr1syy merged 4 commits into
RunMaestro:rcfrom
chr1syy:followup/pianola-watch-control

Conversation

@chr1syy

@chr1syy chr1syy commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem

Adding an agent to Pianola's watch list was CLI-only (maestro-cli pianola supervise watch <tabId> --agent <id>). The supervisor IPC and preload bridge (window.maestro.pianola.supervisor.{list,add,setEnabled,remove}) already existed and are typed in global.d.ts, but nothing in the renderer called them, so there was no in-app way to see or manage what Pianola watches.

Change

A pinned "Watched by Pianola" section at the top of the Pianola Dashboard (answering "where do we put the button?" - it lives right where you manage the workspace's agents):

  • Lists current watch targets with the agent name, a live health dot (running / backing-off / failed / disabled from the daemon snapshot), the daemon lastError when present, an enable/disable toggle, and an unwatch button. Rows click-to-jump to the agent.
  • A + Watch an agent picker (rem-sized, whitespace-nowrap, click-away) lists top-level, non-Pianola agents that have an AI tab and aren't already watched; selecting one calls supervisor.add({ kind: 'watch', agentId, tabId, enabled: true }). It targets the agent's active AI tab (falling back to the first) - agents with no AI tab are excluded since a watch needs a tabId.
  • The Dashboard's top Refresh now refreshes the watch snapshot too (the hook is lifted to PianolaDashboard).

All through the same supervisor path the CLI uses - deterministic, reuses the existing gated IPC, no new backend.

Files

  • usePianolaSupervisor.ts (new) - polled supervisor.list() hook + add/remove/toggle mutators; pure deriveWatchState(sessions, targets, health) returning { watched, watchable }.
  • PianolaDashboard.tsx - Section gains an optional headerAction; new WatchedSection; hook lifted to the top component so Refresh covers both.
  • deriveWatchState.test.ts (new) - name/health/enabled enrichment, short-id fallback, lastError, orchestrate-target exclusion, watchable filtering (Pianola / children / already-watched / no-tab), and active-vs-first tab selection.

Test

deriveWatchState.test.ts 6/6 green; full npm run lint typecheck clean; ESLint clean. Like the sibling Pianola PRs, its shard 2/2 inherits the pre-existing broken SessionList test on rc until #1269 merges and I rebase.

Summary by CodeRabbit

  • New Features
    • Added a “Watched by Pianola” section to the dashboard with live status indicators and health/error details.
    • Added controls to enable/disable monitoring, stop watching, and manage watched agents via a “Watch an agent” picker.
    • Updated refresh to reload both dashboard data and watched-agent state.
  • Bug Fixes
    • Prevented UI flicker during transient supervisor/list failures and avoided stale polling overwriting recent changes.
  • Tests
    • Added Vitest coverage for watch-state derivation and the supervisor hook, including success and failure scenarios (toasts/error reporting).

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5787ba21-a05d-413e-9512-9f3bc0017544

📥 Commits

Reviewing files that changed from the base of the PR and between 4a41e77 and 7b4144f.

📒 Files selected for processing (2)
  • src/__tests__/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts
  • src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts
  • src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts

📝 Walkthrough

Walkthrough

Adds Pianola supervisor watch-state derivation and polling, exposes watch controls in the dashboard, and tests filtering, health enrichment, fallback naming, and AI-tab selection.

Changes

Pianola supervisor watch

Layer / File(s) Summary
Watch-state derivation and validation
src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts, src/__tests__/renderer/components/PianolaDashboard/deriveWatchState.test.ts
Defines watched and watchable state, derives it from sessions, targets, and health, and tests filtering, enrichment, fallback naming, errors, and tab selection.
Supervisor polling and mutations
src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts, src/__tests__/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts
Polls supervisor snapshots, retains prior state on fetch errors, handles disabled-state clearing and mutation ordering, and exposes watch, unwatch, enable, and refresh actions.
Dashboard watch controls
src/renderer/components/PianolaDashboard/PianolaDashboard.tsx
Adds the watched section, agent picker, health indicators, enable/disable controls, stop-watching actions, and supervisor refresh wiring.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PianolaDashboard
  participant usePianolaSupervisor
  participant SupervisorAPI
  PianolaDashboard->>usePianolaSupervisor: refresh or change watch state
  usePianolaSupervisor->>SupervisorAPI: list, watch, unwatch, or setEnabled
  SupervisorAPI-->>usePianolaSupervisor: targets and health snapshots
  usePianolaSupervisor-->>PianolaDashboard: watched and watchable state
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a "Watched by Pianola" section to the Dashboard.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Pianola watch management to the dashboard. The main changes are:

  • A polled hook for supervisor targets and live health.
  • A watched-agent section with add, remove, enable, and disable controls.
  • Watchable-agent filtering and active AI-tab selection.
  • Refresh integration and derivation tests.

Confidence Score: 4/5

Supervisor snapshot reconciliation can display state that no longer matches the daemon.

  • Disabling Pianola leaves phantom watched and running rows.
  • An older poll response can overwrite a newer mutation snapshot.
  • The pure derivation and UI action wiring otherwise match the supervisor data shape.

src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts

Important Files Changed

Filename Overview
src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts Adds supervisor derivation, polling, and mutations, but disabled-state handling and response ordering can leave the UI stale.
src/renderer/components/PianolaDashboard/PianolaDashboard.tsx Adds the watched-agent picker, health display, navigation, and supervisor controls.
src/tests/renderer/components/PianolaDashboard/deriveWatchState.test.ts Covers pure watch-state enrichment, filtering, and AI-tab selection.

Reviews (1): Last reviewed commit: "feat(pianola): add a "Watched by Pianola..." | Re-trigger Greptile

Comment thread src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts Outdated
Comment thread src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts Outdated
@chr1syy
chr1syy force-pushed the followup/pianola-watch-control branch from 08d3c37 to 3bfc220 Compare July 22, 2026 06:43

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts (1)

115-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared polling lifecycle

usePianolaSupervisor repeats the same nonce, cancel flag, and interval cleanup pattern used in usePianolaDashboardData. Pull that lifecycle into a shared hook so only the IPC fetch logic stays local.

🤖 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/renderer/components/PianolaDashboard/usePianolaSupervisor.ts` around
lines 115 - 147, The polling lifecycle in usePianolaSupervisor, including nonce
state, cancellation, interval setup, and cleanup, should be extracted into a
shared hook reused by usePianolaDashboardData. Update usePianolaSupervisor so
only its supervisor.list IPC fetch and snapshot state updates remain local,
while preserving refresh-triggered polling and cancellation behavior.

Source: Path instructions

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

Inline comments:
In `@src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts`:
- Around line 123-170: Update the polling effect and the watch, unwatch, and
setEnabled callbacks to handle failures explicitly. Treat the known
PianolaDisabled case as recoverable, but rethrow unexpected polling errors for
Sentry capture; report mutator failures through the canonical renderer
notification utility or hook so users receive feedback instead of unhandled
rejections. Preserve successful snapshot updates and apply the same handling
consistently to all three mutators.

---

Nitpick comments:
In `@src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts`:
- Around line 115-147: The polling lifecycle in usePianolaSupervisor, including
nonce state, cancellation, interval setup, and cleanup, should be extracted into
a shared hook reused by usePianolaDashboardData. Update usePianolaSupervisor so
only its supervisor.list IPC fetch and snapshot state updates remain local,
while preserving refresh-triggered polling and cancellation behavior.
🪄 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: 2e51bc01-6a1e-4bff-add5-7599304605ae

📥 Commits

Reviewing files that changed from the base of the PR and between cb7fcf8 and 08d3c37.

📒 Files selected for processing (3)
  • src/__tests__/renderer/components/PianolaDashboard/deriveWatchState.test.ts
  • src/renderer/components/PianolaDashboard/PianolaDashboard.tsx
  • src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts

Comment thread src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts Outdated
@pedramamini

Copy link
Copy Markdown
Collaborator

Thanks for the contribution, @chr1syy - this is a clean, well-scoped addition that reuses the existing supervisor IPC path rather than adding new backend surface.

I reviewed the PR alongside the Greptile and CodeRabbit feedback, and you've already handled the substantive findings in your follow-up commits:

  • Disabled-Pianola phantom state (Greptile P1): the poll catch now clears watched rows on PianolaDisabled and only retains the last snapshot on transient errors. Nicely distinguished.
  • Out-of-order poll clobbering a newer mutation (Greptile P1): the mutationEpochRef epoch guard cleanly prevents a stale poll from overwriting an authoritative mutation snapshot.
  • Mutator error handling (CodeRabbit major): the mutate wrapper now surfaces a red toast via notifyToast and reports through captureException, matching our canonical renderer + Sentry conventions.

The added usePianolaSupervisor.test.ts covers exactly these reconciliation cases (disable clears, transient keeps, slow poll can't clobber, failure surfaces toast + Sentry), which is great. The remaining CodeRabbit item (extracting a shared polling lifecycle hook) is a trivial nitpick and fine to leave for a later cleanup.

CI is green on both the ubuntu and windows matrix legs, and there are no merge conflicts. Approving. 🎉

chr1syy added 3 commits July 22, 2026 11:07
Watching an agent was CLI-only (`maestro-cli pianola supervise watch ...`);
the supervisor IPC + preload (`window.maestro.pianola.supervisor.*`) already
existed but had no UI. Add a pinned "Watched by Pianola" section at the top of
the Pianola Dashboard: it lists current watch targets with live daemon health
(status dot + lastError), an enable/disable toggle, and an unwatch button, plus
a "+ Watch an agent" picker that adds a watch through the same supervisor path
the CLI uses. The picker lists top-level, non-Pianola agents that have an AI
tab and aren't already watched; it targets the agent's active AI tab (else the
first). The Dashboard's Refresh now refreshes the watch snapshot too.
Address two review P1s in usePianolaSupervisor:

- A disabled Pianola (supervisor.list rejects with 'PianolaDisabled') left the
  last watched/running rows on screen. The poll catch now clears targets+health
  on that specific rejection; a transient IPC error still keeps the last
  snapshot so the list doesn't flicker.
- A slow poll issued before a mutation could resolve afterward and clobber the
  mutation's fresher snapshot with stale data. Mutations are now authoritative:
  each bumps a mutation epoch before and after its IPC call and always applies
  its returned snapshot, while a poll applies only if the epoch is unchanged on
  resolve. Adds usePianolaSupervisor.test.ts covering both paths.
Watch/unwatch/enable calls were fire-and-forget (void), so an IPC failure became
a silent unhandled rejection with no user feedback. The hook's mutate now catches,
shows a red toast naming the action, and reports the error to Sentry.
@chr1syy
chr1syy force-pushed the followup/pianola-watch-control branch from f17ef0c to 4a41e77 Compare July 22, 2026 09:08

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

Actionable comments posted: 1

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

Inline comments:
In
`@src/__tests__/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts`:
- Around line 110-118: Update the test for usePianolaSupervisor().watch so the
unexpected “disk full” mutation error is asserted to reject after verifying the
red toast and captureException calls. Preserve the existing reporting
assertions, and only expect resolution if the implementation explicitly
classifies this failure as recoverable.
🪄 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: f93e2cdd-5eaa-4bba-befb-10f2618711f5

📥 Commits

Reviewing files that changed from the base of the PR and between 08d3c37 and 4a41e77.

📒 Files selected for processing (4)
  • src/__tests__/renderer/components/PianolaDashboard/deriveWatchState.test.ts
  • src/__tests__/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts
  • src/renderer/components/PianolaDashboard/PianolaDashboard.tsx
  • src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/renderer/components/PianolaDashboard/usePianolaSupervisor.ts
  • src/renderer/components/PianolaDashboard/PianolaDashboard.tsx

Comment thread src/__tests__/renderer/components/PianolaDashboard/usePianolaSupervisor.test.ts Outdated
Match PianolaModal's convention for recoverable IPC mutations: a failed
watch/unwatch/enable now toasts and reports to Sentry only when the error is
unexpected (isPianolaDisabled gate), then resolves rather than rejecting so a
fire-and-forget click never becomes an unhandled rejection. Adds a test that an
expected PianolaDisabled mutation failure is toasted but not reported, and makes
the recoverable (resolves) classification explicit in the existing test.
@chr1syy
chr1syy merged commit 8462282 into RunMaestro:rc Jul 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants