Skip to content

feat: web UX parity — hasUnread pipeline + Auto Run tab enhancements#739

Merged
pedramamini merged 4 commits intorcfrom
feat/feat-web-ux-parity
Apr 7, 2026
Merged

feat: web UX parity — hasUnread pipeline + Auto Run tab enhancements#739
pedramamini merged 4 commits intorcfrom
feat/feat-web-ux-parity

Conversation

@chr1syy
Copy link
Copy Markdown
Contributor

@chr1syy chr1syy commented Apr 6, 2026

Summary

  • Fix hasUnread data pipeline: Add hasUnread to AITabData type, serialize it in web-server factory, and include it in the tabs broadcast hash so web clients receive unread notification state changes (red dots)
  • Enhance Auto Run tab: Replace placeholder inline Auto Run tab with full document listing, progress status (task + document indices), stop controls, and refresh button — reuses shared DocumentCard component extracted from AutoRunPanel
  • Cleanup: Remove dead default export, extract repeated isStopped expression

Changed files

File Change
src/main/web-server/types.ts Add hasUnread to AITabData
src/main/web-server/web-server-factory.ts Map hasUnread in aiTabs serialization
src/renderer/hooks/remote/useRemoteIntegration.ts Include hasUnread in tabs hash + broadcast
src/web/mobile/AutoRunDocumentCard.tsx New shared DocumentCard component
src/web/mobile/AutoRunPanel.tsx Import shared DocumentCard instead of inline
src/web/mobile/RightDrawer.tsx Full AutoRunTabContent with docs, progress, stop
src/web/mobile/RightPanel.tsx Thread new props to AutoRunTabContent

Test plan

  • Verify unread red dots appear on web/mobile tabs when agent has unread output
  • Verify Auto Run tab shows document list with progress bars
  • Verify stop button stops a running Auto Run batch
  • Verify refresh button reloads document list
  • Verify "Configure & Launch" button opens setup (disabled when running)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Auto Run panel now shows a tappable document list with per-document task progress and visual progress bars.
    • You can stop active Auto Run runs and refresh the document list from the panel.
  • Refactor

    • Auto Run UI reorganized for clearer toolbar, loading/empty states, and document-driven view.
    • Tab state now carries unread indicators so clients can display unread status.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Added optional hasUnread to AI tab data and propagated it through server session mapping and renderer broadcasts. Extracted DocumentCard into its own component and enhanced the Auto Run UI (document listing, per-document progress, stop/refresh flows), with parent components forwarding session and websocket send APIs.

Changes

Cohort / File(s) Summary
Unread State Implementation
src/main/web-server/types.ts, src/main/web-server/web-server-factory.ts, src/renderer/global.d.ts, src/renderer/hooks/remote/useRemoteIntegration.ts
Added optional hasUnread?: boolean to tab types and included hasUnread in session-to-web payloads and tab-broadcast hashing/serialization (normalizes via tab.hasUnread ?? false).
DocumentCard Extraction
src/web/mobile/AutoRunDocumentCard.tsx, src/web/mobile/AutoRunPanel.tsx
Created DocumentCard component file (exports DocumentCardProps and DocumentCard) and replaced the inline DocumentCard in AutoRunPanel with the imported component; preserves progress calc, haptics, and tap handling.
Auto Run UI & Wiring
src/web/mobile/RightDrawer.tsx, src/web/mobile/RightPanel.tsx
Expanded AutoRunTabContent to fetch/display Auto Run documents, show per-document progress, manage stop/refresh UX with async stop + rollback on failure, and forward sessionId, sendRequest, send, and onOpenDocument through RightDrawer/RightPanel.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant UI as AutoRunTabContent
  participant Hook as useAutoRun
  participant WS as WebSocket (send/sendRequest)
  participant Server as Server

  User->>UI: Open AutoRun tab / Tap refresh
  UI->>Hook: fetchDocuments(sessionId)
  Hook->>WS: sendRequest(fetchDocuments)
  WS->>Server: request documents
  Server-->>WS: documents list
  WS-->>Hook: documents payload
  Hook-->>UI: documents
  UI->>User: render DocumentCard list

  User->>UI: Tap DocumentCard(filename)
  UI->>WS: send(openDocument, filename)
  WS->>Server: open document request
  Server-->>WS: ack
  WS-->>UI: ack -> UI invokes onOpenDocument(filename)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A flag for the tabs, a soft little cheer,
Documents lined up, progress bars clear.
I hop, I tap, the websocket sings,
AutoRun dances with tiny springs—
Carrots and code, off we steer! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main feature additions: hasUnread pipeline implementation and Auto Run tab enhancements.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
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 feat/feat-web-ux-parity

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 6, 2026

Greptile Summary

This PR wires hasUnread end-to-end through the web-server pipeline (AITabData type → factory serialization → broadcast hash) so web/mobile clients receive unread-indicator state for tabs. It also replaces the placeholder Auto Run drawer tab with a full implementation (document listing, progress status, stop/refresh controls) by extracting a shared DocumentCard component from AutoRunPanel.

Confidence Score: 5/5

Safe to merge; both remaining findings are P2 style issues with no runtime impact

The hasUnread pipeline is correctly wired end-to-end (type → factory → hash → broadcast), and the new AutoRunTabContent is functional and well-structured. All findings are minor naming/copy issues.

AutoRunPanel.tsx (stale directory path in empty state) and AutoRunDocumentCard.tsx (prop naming)

Important Files Changed

Filename Overview
src/main/web-server/types.ts Adds hasUnread?: boolean to AITabData; clean backward-compatible addition
src/main/web-server/web-server-factory.ts Serializes hasUnread in aiTabs mapping with ?? false normalization; correct and complete
src/renderer/hooks/remote/useRemoteIntegration.ts Adds hasUnread to the tabsHash comparison string and to the broadcast payload, correctly wiring unread-dot state to web clients
src/web/mobile/AutoRunDocumentCard.tsx New shared DocumentCard component; destructured prop named document shadows the global DOM document object
src/web/mobile/AutoRunPanel.tsx Refactored to import shared DocumentCard; empty-state still references stale .maestro/auto-run/ path instead of .maestro/playbooks/
src/web/mobile/RightDrawer.tsx Adds full AutoRunTabContent with document listing, progress status, stop/refresh controls; correctly uses .maestro/playbooks/ path
src/web/mobile/RightPanel.tsx Threads new props through to AutoRunTabContent; no issues

Sequence Diagram

sequenceDiagram
    participant Desktop as Renderer (useRemoteIntegration)
    participant Factory as web-server-factory
    participant WS as WebSocket Broadcast
    participant Web as Web/Mobile Client

    Note over Desktop: interval tick (500ms)
    Desktop->>Desktop: build tabsHash (id:name:starred:state:hasUnread)
    Desktop->>WS: broadcastTabsChange(sessionId, tabs[], activeTabId)
    WS->>Web: tabs_changed { aiTabs[{id, state, hasUnread, ...}], activeTabId }
    Web->>Web: render red dot if any tab.hasUnread === true

    Note over Factory,Web: On initial connect / getSessions
    Factory->>Factory: serialize aiTabs (hasUnread ?? false)
    Factory->>Web: sessions payload with hasUnread in each AITabData
Loading

Comments Outside Diff (1)

  1. src/web/mobile/AutoRunPanel.tsx, line 414 (link)

    P2 Stale empty-state directory path

    The canonical location for Auto Run documents is .maestro/playbooks/ (per PLAYBOOKS_DIR in src/shared/maestro-paths.ts), but this empty-state message still shows .maestro/auto-run/. The new AutoRunTabContent in RightDrawer.tsx correctly shows .maestro/playbooks/, so the two panels give users conflicting guidance.

Reviews (1): Last reviewed commit: "fix: remove dead default export and extr..." | Re-trigger Greptile

onTap: (filename: string) => void;
}

export function DocumentCard({ document, onTap }: DocumentCardProps) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Prop name document shadows the global DOM object

Destructuring a prop called document shadows window.document inside the function body. The component doesn't currently need the DOM global, but it's a footgun for future edits. A clearer name like doc avoids the collision.

Suggested change
export function DocumentCard({ document, onTap }: DocumentCardProps) {
export function DocumentCard({ document: doc, onTap }: DocumentCardProps) {

(and rename all uses of document to doc inside the function body)

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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/hooks/remote/useRemoteIntegration.ts (1)

860-871: ⚠️ Potential issue | 🟡 Minor

Update global.d.ts to include hasUnread field in broadcastTabsChange declaration.

The broadcastTabsChange type in src/renderer/global.d.ts (lines 542–556) does not include the hasUnread field that is being passed at line 870 of useRemoteIntegration.ts. While TypeScript allows extra properties, this type mismatch creates inconsistency with the actual implementation types (src/main/web-server/types.ts and src/web/hooks/useWebSocket.ts), which do include hasUnread?: boolean;.

Add hasUnread?: boolean; to the aiTabs array type in the broadcastTabsChange declaration to keep types in sync.

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

In `@src/renderer/hooks/remote/useRemoteIntegration.ts` around lines 860 - 871,
Add the missing optional hasUnread property to the broadcastTabsChange
declaration in src/renderer/global.d.ts: update the aiTabs array item type
within the broadcastTabsChange type to include hasUnread?: boolean so it matches
the actual payload constructed in useRemoteIntegration.ts (the tabsForBroadcast
object) and the implementations in src/main/web-server/types.ts and
src/web/hooks/useWebSocket.ts; ensure the property is optional to match existing
usages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web/mobile/AutoRunDocumentCard.tsx`:
- Around line 28-49: The button in AutoRunDocumentCard (the element using
handleTap and aria-label with document.filename) currently removes the native
focus ring via outline: 'none'; restore a visible focus indicator by removing
that outline: 'none' and instead add an accessible focus style (e.g., a clear
outline or focus-visible box-shadow/border) so keyboard users can see focus when
tabbing the card; implement this as a focus/focus-visible style on the same
button element (or via a CSS class applied to the component) so the card remains
fully keyboard operable.

In `@src/web/mobile/RightDrawer.tsx`:
- Around line 922-937: Replace the hardcoded Auto Run directory strings with a
single exported constant (e.g., AUTO_RUN_DIR) and use it from both components:
add the constant to a shared module (e.g., export const AUTO_RUN_DIR =
'.maestro/playbooks/' or the canonical path) and import it into RightDrawer
(where the hint currently renders ".maestro/playbooks/") and AutoRunPanel (where
it currently renders ".maestro/auto-run/"); update the JSX in both
RightDrawer.tsx and AutoRunPanel.tsx to render the imported AUTO_RUN_DIR inside
the <code> element so both screens use one source of truth.
- Around line 716-723: The stop flow is silently swallowing unexpected
errors—update the hook and the handler so recoverable errors are surfaced to the
UI while unexpected errors propagate to Sentry: change stopAutoRun in
useAutoRun.ts to return/throw a discriminated result (e.g., { ok: boolean,
error?: 'NETWORK_ERROR' | ... } or throw for unexpected exceptions) instead of
catching all exceptions and returning false, and update handleStop in
RightDrawer.tsx to inspect that result and set UI state / show a user-facing
error for expected recoverable cases (e.g., NETWORK_ERROR) while NOT
catching/re-suppressing unexpected exceptions so they bubble up. Reference
symbols: stopAutoRun (useAutoRun.ts), handleStop (RightDrawer.tsx), and
setIsStopping/triggerHaptic to maintain existing UX.

---

Outside diff comments:
In `@src/renderer/hooks/remote/useRemoteIntegration.ts`:
- Around line 860-871: Add the missing optional hasUnread property to the
broadcastTabsChange declaration in src/renderer/global.d.ts: update the aiTabs
array item type within the broadcastTabsChange type to include hasUnread?:
boolean so it matches the actual payload constructed in useRemoteIntegration.ts
(the tabsForBroadcast object) and the implementations in
src/main/web-server/types.ts and src/web/hooks/useWebSocket.ts; ensure the
property is optional to match existing usages.
🪄 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: 5e1a96cf-1032-4601-876f-0e27028bfb1a

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4f1cb and acca6f9.

📒 Files selected for processing (7)
  • src/main/web-server/types.ts
  • src/main/web-server/web-server-factory.ts
  • src/renderer/hooks/remote/useRemoteIntegration.ts
  • src/web/mobile/AutoRunDocumentCard.tsx
  • src/web/mobile/AutoRunPanel.tsx
  • src/web/mobile/RightDrawer.tsx
  • src/web/mobile/RightPanel.tsx

Comment on lines +716 to +723
const handleStop = useCallback(async () => {
triggerHaptic(HAPTIC_PATTERNS.interrupt);
setIsStopping(true);
const success = await stopAutoRun(sessionId);
if (!success) {
setIsStopping(false);
}
}, [sessionId, stopAutoRun]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't let stop failures disappear silently.

Line 719 only gets a boolean back, so a network/backend error just flips the button from "Stopping..." back to "Stop". src/web/hooks/useAutoRun.ts:136-146 currently catches every exception and returns false, which means this new control neither tells the user what happened nor preserves unexpected failures for Sentry. Please surface a recoverable error here, and let the unexpected path bubble out of the hook.

As per coding guidelines, "Do not silently swallow errors. Let unhandled exceptions bubble up to Sentry for error tracking in production. Handle expected/recoverable errors explicitly (e.g., NETWORK_ERROR). For unexpected errors, re-throw them to allow Sentry to capture them."

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

In `@src/web/mobile/RightDrawer.tsx` around lines 716 - 723, The stop flow is
silently swallowing unexpected errors—update the hook and the handler so
recoverable errors are surfaced to the UI while unexpected errors propagate to
Sentry: change stopAutoRun in useAutoRun.ts to return/throw a discriminated
result (e.g., { ok: boolean, error?: 'NETWORK_ERROR' | ... } or throw for
unexpected exceptions) instead of catching all exceptions and returning false,
and update handleStop in RightDrawer.tsx to inspect that result and set UI state
/ show a user-facing error for expected recoverable cases (e.g., NETWORK_ERROR)
while NOT catching/re-suppressing unexpected exceptions so they bubble up.
Reference symbols: stopAutoRun (useAutoRun.ts), handleStop (RightDrawer.tsx),
and setIsStopping/triggerHaptic to maintain existing UX.

Comment on lines +922 to +937
<p style={{ fontSize: '13px', color: colors.textDim, margin: '0 0 6px 0' }}>
No Auto Run documents found
</p>
<p style={{ fontSize: '12px', color: colors.textDim, margin: 0, opacity: 0.7 }}>
Add documents to{' '}
<code
style={{
fontSize: '11px',
backgroundColor: `${colors.textDim}15`,
padding: '1px 4px',
borderRadius: '3px',
}}
>
.maestro/playbooks/
</code>{' '}
directory
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use one source of truth for the Auto Run directory hint.

These lines now tell users to add files under .maestro/playbooks/, but src/web/mobile/AutoRunPanel.tsx:401-416 still points at .maestro/auto-run/. Because both screens render the same Auto Run document list, one of these instructions is wrong and will send users to the wrong folder. Please drive this copy from a shared constant/message.

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

In `@src/web/mobile/RightDrawer.tsx` around lines 922 - 937, Replace the hardcoded
Auto Run directory strings with a single exported constant (e.g., AUTO_RUN_DIR)
and use it from both components: add the constant to a shared module (e.g.,
export const AUTO_RUN_DIR = '.maestro/playbooks/' or the canonical path) and
import it into RightDrawer (where the hint currently renders
".maestro/playbooks/") and AutoRunPanel (where it currently renders
".maestro/auto-run/"); update the JSX in both RightDrawer.tsx and
AutoRunPanel.tsx to render the imported AUTO_RUN_DIR inside the <code> element
so both screens use one source of truth.

@pedramamini
Copy link
Copy Markdown
Collaborator

Thanks for the contribution, @chr1syy! The hasUnread pipeline and Auto Run tab enhancements look solid overall — nice work extracting the shared DocumentCard component and wiring the unread state end-to-end.

A few things to address before we can merge:

CI failures

Both lint-and-format and test checks are failing. Please fix those so we get a clean CI run.

Stale directory path in AutoRunPanel.tsx

The empty-state message in AutoRunPanel.tsx (line ~414) still references .maestro/auto-run/, but the canonical path is .maestro/playbooks/ (which you correctly use in the new RightDrawer.tsx code). This gives users conflicting guidance between the two views. Please update the AutoRunPanel to match.

Missing hasUnread in global.d.ts

The broadcastTabsChange type declaration in src/renderer/global.d.ts doesn't include the hasUnread field you're now passing. Worth adding hasUnread?: boolean there to keep types in sync.

Once CI is green and those are addressed, this should be good to go!

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.

🧹 Nitpick comments (1)
src/web/mobile/AutoRunPanel.tsx (1)

107-114: Consider extracting the repeated stopping-state check.

The expression isStopping || autoRunState?.isStopping is repeated 7 times throughout the JSX. Extracting it to a single variable alongside the other derived state would improve clarity and reduce duplication.

♻️ Suggested extraction
 	const isRunning = autoRunState?.isRunning ?? false;
+	const isStopPending = isStopping || autoRunState?.isStopping;
 	const totalTasks = autoRunState?.totalTasks;

Then replace all occurrences of isStopping || autoRunState?.isStopping with isStopPending.

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

In `@src/web/mobile/AutoRunPanel.tsx` around lines 107 - 114, Extract the repeated
stopping-state check into a single derived variable (e.g., isStopPending) near
the other computed state values (alongside isRunning, totalTasks,
completedTasks, currentTaskIndex, progress, totalDocs, currentDocIndex) by
computing isStopPending = isStopping || autoRunState?.isStopping, and then
replace every occurrence of isStopping || autoRunState?.isStopping in the
AutoRunPanel JSX with isStopPending to remove duplication and improve clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/web/mobile/AutoRunPanel.tsx`:
- Around line 107-114: Extract the repeated stopping-state check into a single
derived variable (e.g., isStopPending) near the other computed state values
(alongside isRunning, totalTasks, completedTasks, currentTaskIndex, progress,
totalDocs, currentDocIndex) by computing isStopPending = isStopping ||
autoRunState?.isStopping, and then replace every occurrence of isStopping ||
autoRunState?.isStopping in the AutoRunPanel JSX with isStopPending to remove
duplication and improve clarity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fac3a4af-b9a4-424f-b6af-1e94fd356f61

📥 Commits

Reviewing files that changed from the base of the PR and between acca6f9 and fbda513.

📒 Files selected for processing (3)
  • src/renderer/global.d.ts
  • src/web/mobile/AutoRunDocumentCard.tsx
  • src/web/mobile/AutoRunPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/web/mobile/AutoRunDocumentCard.tsx

chr1syy and others added 4 commits April 6, 2026 16:01
Add hasUnread field to AITabData type, web-server aiTabs mapping, and
useRemoteIntegration broadcast hash + payload so unread indicators
propagate to web clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…op controls

Extracted DocumentCard into shared AutoRunDocumentCard.tsx and rewrote
AutoRunTabContent to show a document list with progress cards, running
status with task/doc counters and progress bar, stop button, and proper
loading/empty states. Threaded sessionId, sendRequest, send, and
onOpenDocument props through RightDrawer and RightPanel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, rename document prop

- Add hasUnread?: boolean to broadcastTabsChange type in global.d.ts
- Fix stale .maestro/auto-run/ path to .maestro/playbooks/ in AutoRunPanel
- Rename document prop to doc to avoid shadowing window.document
- Remove outline: 'none' from DocumentCard button for keyboard accessibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chr1syy chr1syy force-pushed the feat/feat-web-ux-parity branch from fbda513 to 70b0aa8 Compare April 6, 2026 14:02
@scriptease
Copy link
Copy Markdown

Yay loving me some auto run improvements 👍

@pedramamini pedramamini merged commit b07b378 into rc Apr 7, 2026
4 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.

3 participants