Skip to content

feat: unread badge for automations#2656

Open
janburzinski wants to merge 4 commits into
mainfrom
emdash/automation-unread-badge
Open

feat: unread badge for automations#2656
janburzinski wants to merge 4 commits into
mainfrom
emdash/automation-unread-badge

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • adds unread badge for finished automation runs in sidebar
  • adds sidebar context menu on right click to mark all as read

Screenshot/Recording (if applicable)

https://streamable.com/y1btel

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an unread badge to the Automations sidebar item that counts finished automation runs since the user last viewed automations, and a right-click context menu to mark all as read. The automationsLastReadAt timestamp is persisted in interface settings and initialized to the latest existing run's finishedAt so first-time users don't see a stale count.

  • Introduces TERMINAL_AUTOMATION_RUN_STATUSES and AUTOMATION_SKIP_CODES_EXCLUDED_FROM_NOTIFICATIONS in the shared module; the new DB query (countUnreadFinishedRuns) filters out user-lifecycle skip reasons (disabled, automation_deleted) but is missing trigger_changed, causing the badge to increment when a user changes an automation's trigger config.
  • Wires emitRunChanged to stopRun and to the skipQueuedCronRuns call-sites so terminal-state transitions reliably invalidate the renderer-side unread count query.
  • The new AutomationsSidebarItem component handles the context menu, error toasting, and badge rendering; the useAutomationUnreadCount hook drives the count via React Query with event-based invalidation.

Confidence Score: 4/5

Safe to merge after adding trigger_changed to the skip-code exclusion list; all other wiring is correct.

The unread badge logic correctly excludes disabled and automation_deleted skips, but the same emitRunChanges path is taken when a user changes an automation's trigger configuration (trigger_changed), and that skip code is not in the exclusion list. A user who edits a trigger will see the badge increment for runs they themselves cancelled.

apps/emdash-desktop/src/shared/core/automations/automation-run.ts — the AUTOMATION_SKIP_CODES_EXCLUDED_FROM_NOTIFICATIONS constant needs trigger_changed added.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/shared/core/automations/automation-run.ts Adds terminal status set, type guard, and skip-code exclusion list; trigger_changed is missing from the exclusion list causing spurious badge increments.
apps/emdash-desktop/src/main/core/automations/automations-service.ts Adds countUnreadFinishedRuns, getNotificationsBaselineTimestamp, and properly wires terminal-state emission; isExcludedFromUnreadNotifications name is inverted and uses sql.raw with manual quoting.
apps/emdash-desktop/src/renderer/features/automations/use-automation-unread-count.ts New hook that wires unread count query with baseline initialization and terminal-state invalidation; logic is sound.
apps/emdash-desktop/src/renderer/features/sidebar/automations-sidebar-item.tsx New sidebar item with unread badge, context menu, and error-handling for mark-as-read; implementation looks correct.
apps/emdash-desktop/src/main/core/settings/schema.ts Adds automationsLastReadAt: z.number() field to interface settings schema; clean addition.
apps/emdash-desktop/src/main/core/settings/settings-registry.ts Adds default value of 0 for the new automationsLastReadAt field; correct.
apps/emdash-desktop/src/main/core/automations/controller.ts Exposes the two new RPC methods; straightforward delegation.
apps/emdash-desktop/src/renderer/features/sidebar/left-sidebar.tsx Replaces inline automations button with the new AutomationsSidebarItem component; clean refactor.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Scheduler as AutomationScheduler
    participant Service as AutomationsService
    participant Events as IPC Events
    participant Hook as useAutomationUnreadCount
    participant DB as SQLite (automationRuns)
    participant Settings as AppSettings

    Note over Service,DB: New user first mount
    Hook->>Service: getNotificationsBaselineTimestamp()
    Service->>DB: SELECT MAX(finishedAt) WHERE terminal
    DB-->>Service: baseline ts
    Service-->>Hook: baseline ts
    Hook->>Settings: updateAsync automationsLastReadAt baseline

    Note over Scheduler,Events: Run reaches terminal state
    Scheduler->>Service: onRunStep(run)
    Service->>Events: emit automationRunChangedChannel run
    Events-->>Hook: run
    Hook->>Hook: isTerminalStatus invalidateQueries
    Hook->>Service: countUnreadFinishedRuns(lastReadAt)
    Service->>DB: "SELECT count WHERE terminal AND not-excluded AND finishedAt > lastReadAt"
    DB-->>Service: n
    Service-->>Hook: n
    Hook-->>AutomationsSidebarItem: unreadCount n

    Note over Hook,Settings: User clicks Mark all as read
    AutomationsSidebarItem->>Hook: markAsRead()
    Hook->>Settings: updateAsync automationsLastReadAt Date.now
    Hook->>Hook: invalidateQueries refetch count 0
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Scheduler as AutomationScheduler
    participant Service as AutomationsService
    participant Events as IPC Events
    participant Hook as useAutomationUnreadCount
    participant DB as SQLite (automationRuns)
    participant Settings as AppSettings

    Note over Service,DB: New user first mount
    Hook->>Service: getNotificationsBaselineTimestamp()
    Service->>DB: SELECT MAX(finishedAt) WHERE terminal
    DB-->>Service: baseline ts
    Service-->>Hook: baseline ts
    Hook->>Settings: updateAsync automationsLastReadAt baseline

    Note over Scheduler,Events: Run reaches terminal state
    Scheduler->>Service: onRunStep(run)
    Service->>Events: emit automationRunChangedChannel run
    Events-->>Hook: run
    Hook->>Hook: isTerminalStatus invalidateQueries
    Hook->>Service: countUnreadFinishedRuns(lastReadAt)
    Service->>DB: "SELECT count WHERE terminal AND not-excluded AND finishedAt > lastReadAt"
    DB-->>Service: n
    Service-->>Hook: n
    Hook-->>AutomationsSidebarItem: unreadCount n

    Note over Hook,Settings: User clicks Mark all as read
    AutomationsSidebarItem->>Hook: markAsRead()
    Hook->>Settings: updateAsync automationsLastReadAt Date.now
    Hook->>Hook: invalidateQueries refetch count 0
Loading

Reviews (2): Last reviewed commit: "fix(automations): refine unread run badg..." | Re-trigger Greptile

@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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