Skip to content

Improve auto-loop event emission and add ntfy notifications#821

Merged
gsxdsm merged 9 commits intoAutoMaker-Org:v1.0.0rcfrom
gsxdsm:feature/improve-events
Mar 1, 2026
Merged

Improve auto-loop event emission and add ntfy notifications#821
gsxdsm merged 9 commits intoAutoMaker-Org:v1.0.0rcfrom
gsxdsm:feature/improve-events

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 28, 2026

Summary

  • Refactored auto-loop sleep intervals into named constants for better maintainability
  • Fixed premature auto_mode_idle event emission by checking for in-progress features before firing the event
  • Added ntfy.sh notification support to event hooks with deep-linking for the board
  • Consolidated event-related notification logic into FeatureStateManager with notification constants

Changes

AutoLoopCoordinator Service

  • Extracted hardcoded sleep intervals into named constants (SLEEP_INTERVAL_CAPACITY_MS, SLEEP_INTERVAL_IDLE_MS, SLEEP_INTERVAL_NORMAL_MS, SLEEP_INTERVAL_ERROR_MS) for improved readability and maintenance
  • Added hasInProgressFeaturesForWorktree() method to check if features are transitioning states (e.g., from in_progress to completed)
  • Fixed auto_mode_idle event firing logic to verify no in-progress features exist before emitting, preventing premature idle events during feature status transitions
  • Added featureBelongsToWorktree() helper to properly filter features by branch context (handles main vs feature worktrees)
  • Updated auto_mode_idle event payload to include projectPath and branchName fields

EventHookService

  • Added support for 'ntfy' action type in event hooks via new executeNtfyHook() method
  • Integrated ntfy.sh notification service for sending notifications to configured endpoints
  • Added deep-link support to auto-open the board with feature context when notifications are clicked
  • Moved HookContext type definition to shared @automaker/types as EventHookContext for reusability
  • Implements automatic defaultClickUrl resolution with deep-link path construction for better UX

FeatureStateManager

  • Introduced notification type constants for event categorization (NOTIFICATION_TYPE_WAITING_APPROVAL, NOTIFICATION_TYPE_VERIFIED, etc.)
  • Added notification title constants for consistency across events
  • Implemented subscription to auto-mode events to handle error event notifications
  • Added destroy() method for proper cleanup of event subscriptions
  • Refactored task finalization logic for improved clarity (separates setting vs clearing justFinishedAt)

Configuration

  • Updated .gitignore to exclude entire test/fixtures/ directory and removed outdated submodule reference

Summary by CodeRabbit

  • New Features

    • Push notification support via ntfy.sh for event hooks; manage endpoints in Settings
    • Deep-linking to open feature output modals via /board?featureId=
    • New error notification types with dedicated icons
    • Show model & provider info for running agents
    • Improved auto-mode idle detection respecting worktree scope
  • Improvements

    • Expand/collapse PR description in PR dialog
    • Global newest-first board sorting option
    • Fixed session-creation race so new session selection is stable
  • UI/UX Polish

    • Refined notification indicators, dev-server status, and settings UI interactions

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

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
📝 Walkthrough

Walkthrough

Adds ntfy.sh notification support (server + UI), worktree-aware auto-mode idle detection, feature-error notifications and handling, deep-linking for board feature modals, numerous UI/store/type changes, extensive tests, and version bumps to 1.0.0.

Changes

Cohort / File(s) Summary
Ntfy backend & hooks
apps/server/src/services/ntfy-service.ts, apps/server/src/services/event-hook-service.ts, libs/types/src/settings.ts, libs/types/src/index.ts
New NtfyService, ntfy endpoint types and EventHookNtfyAction; EventHookService routes and executes ntfy hooks with endpoint resolution, auth, variable substitution, and click-URL handling.
Auto-mode & feature state
apps/server/src/services/auto-loop-coordinator.ts, apps/server/src/services/feature-state-manager.ts
Worktree-scoped idle detection and new AutoModeConfig fields; timed sleep constants; helpers to determine feature/worktree relevance; feature-state manager emits auto-mode/feature error notifications and finalizes in_progress tasks.
Settings & persistence
apps/server/src/services/settings-service.ts, apps/ui/src/hooks/use-settings-sync.ts, apps/ui/src/hooks/use-settings-migration.ts, apps/ui/src/store/*, libs/types/src/settings.ts
Add ntfyEndpoints to GlobalSettings and UI store, protect against accidental clears, migrate/sync ntfy endpoints and new settings (defaultSortNewestCardOnTop, knownDynamicModelIds, featureTemplates).
UI: Event hooks & endpoints management
apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx, .../event-hooks-section.tsx
Add ntfy action UI, endpoint CRUD (dialog/cards), validation, priority/tags/emoji/clickUrl fields and integration into hook creation/editing flows.
UI: Notifications & deep-linking
apps/ui/src/components/layout/.../notification-bell.tsx, apps/ui/src/components/views/notifications-view.tsx, apps/ui/src/routes/board.lazy.tsx, apps/ui/src/routes/board.tsx, apps/ui/src/components/views/board-view.tsx
Deep-link support for /board?featureId → BoardView initialFeatureId prop and modal auto-open; notification UI shows new error icons and navigates to board with featureId.
UI: Board & card behaviors
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx, .../kanban-card/agent-info-panel.tsx, .../create-pr-dialog.tsx, .../create-worktree-dialog.tsx
Sorting override (new sortNewestCardOnTop prop), clickable summary header and nullable task summaries, PR description expand/collapse, remote branch qualification adjustments.
UI: Worktree & running agents
apps/ui/src/components/views/board-view/worktree-panel/..., apps/ui/src/components/views/running-agents-view.tsx, apps/ui/src/lib/electron.ts
Spinner variant change and prop rename isStartingAnyDevServer; running agents now include optional model/provider fields and model badge display.
Utilities & types
apps/ui/src/lib/utils.ts, apps/ui/src/lib/log-parser.ts, apps/ui/src/hooks/use-agent-output-websocket.ts, apps/ui/src/hooks/queries/use-worktrees.ts, apps/ui/src/types/electron.d.ts
Added formatRelativeTime util, robust log parsing, tightened types and guards, and new dev-server:starting event variant.
Tests
apps/server/tests/..., apps/ui/tests/..., apps/ui/tests/features/...
Extensive unit tests for ntfy-service, event-hook-service, auto-loop-coordinator, feature-state-manager, settings-service; new E2E tests for deep-link and event-hooks UI; updates to running-agents and other test fixtures.
Misc & packaging
.gitignore, test/fixtures/projectA, package.json, apps/server/package.json, apps/ui/package.json, apps/ui/src/main.ts
Ignore broadened for test/fixtures/, removed submodule line, version bumps to 1.0.0, added desktopName to apps/ui package, removed Linux desktopName binding in main.ts.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Client (Browser)
    participant Router as Board Router
    participant BoardView as BoardView
    participant Store as AppStore
    participant Server as Backend API
    participant FeatureDB as Feature Loader

    Browser->>Router: Navigate /board?featureId=F
    Router->>BoardView: render with initialFeatureId=F
    BoardView->>Server: load feature metadata (by id)
    Server->>FeatureDB: fetch feature
    FeatureDB-->>Server: feature { id, branchName }
    Server-->>BoardView: feature metadata
    alt feature.branchName exists
        BoardView->>Store: switch worktree to feature.branchName
    else
        BoardView->>Store: switch to main worktree
    end
    Store-->>BoardView: worktree switched
    BoardView->>BoardView: open output modal for F
    BoardView-->>Browser: modal displayed
Loading
sequenceDiagram
    participant HookSvc as EventHookService
    participant Settings as SettingsService
    participant NtfySvc as NtfyService
    participant NtfyServer as ntfy.sh
    participant Logger as Logger

    HookSvc->>Settings: getGlobalSettings()
    Settings-->>HookSvc: returns hooks + ntfyEndpoints
    HookSvc->>HookSvc: select ntfy action & resolve endpointId
    HookSvc->>Settings: get endpoint by id
    Settings-->>HookSvc: NtfyEndpointConfig
    HookSvc->>NtfySvc: sendNotification(endpoint, options, context)
    NtfySvc->>NtfySvc: validate endpoint, substitute variables, build headers/body
    NtfySvc->>NtfyServer: POST /<topic> with payload
    alt 2xx
        NtfyServer-->>NtfySvc: success
        NtfySvc->>Logger: log success
        NtfySvc-->>HookSvc: { success: true }
    else error
        NtfyServer-->>NtfySvc: error/timeout
        NtfySvc->>Logger: log error
        NtfySvc-->>HookSvc: { success: false, error }
    end
    HookSvc->>Logger: record hook result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #626: Adds/adjusts FeatureLoader integration into EventHookService — closely related to ntfy hook endpoint resolution and execution.
  • PR #161: Worktree-aware auto-mode and branchName handling overlap with auto-loop-coordinator and feature scoping changes.
  • PR #805: Prior changes to EventHookService affecting hook handling logic; likely overlaps with ntfy integration.

Suggested labels

Enhancement, type: feature, Refactor, Testers-Requested

Poem

🐰 I hopped through code, made hooks sing ntfy tunes,

Deep-links point features to bright afternoon moons,
Worktrees now whisper when idle and still,
Notifications leap, with emojis and thrill,
From tiny tests to v1.0 — a joyous hop and trill! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 changes: improving auto-loop event emission and adding ntfy notifications support across multiple services.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the event notification system by integrating ntfy.sh for customizable push notifications, improving the accuracy of auto-loop idle event emissions, and enabling deep-linking to specific features within the board view. These changes aim to provide users with more timely and relevant information about their automated processes and improve overall navigation and maintainability.

Highlights

  • Ntfy.sh Notifications Integration: Introduced comprehensive support for ntfy.sh push notifications, allowing users to configure custom endpoints with authentication, tags, emojis, and deep-linking capabilities for event hooks.
  • Improved Auto-Loop Event Emission: Refactored auto-loop sleep intervals into named constants for better maintainability and fixed a bug that caused premature auto_mode_idle event emission by ensuring no in-progress features exist before signaling idle state.
  • Feature Deep-Linking to Board View: Implemented functionality to deep-link directly to the board view and automatically open the output modal for a specific feature using a URL query parameter (/board?featureId=xxx).
  • Enhanced Notification Management: Consolidated event-related notification logic within FeatureStateManager, added specific notification types and titles for various events (e.g., feature_error, auto_mode_error), and improved display name handling for notifications.
  • UI/UX Improvements: Added an expand/collapse option for the PR description textarea, improved worktree base branch selection logic for remote branches, and updated running agents display to include model and provider information.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .gitignore
    • Updated to exclude the entire test/fixtures/ directory.
  • apps/server/src/services/auto-loop-coordinator.ts
    • Refactored hardcoded sleep intervals into named constants for clarity.
    • Implemented a check for in-progress features to prevent premature auto_mode_idle event emission.
    • Added hasInProgressFeaturesForWorktree() and featureBelongsToWorktree() helper methods for accurate worktree-specific feature status checks.
  • apps/server/src/services/event-hook-service.ts
    • Added support for a new 'ntfy' action type in event hooks.
    • Integrated ntfy.sh notification service for sending notifications.
    • Implemented deep-link support for ntfy notifications to open the board with feature context.
    • Moved HookContext type definition to shared @automaker/types as EventHookContext.
  • apps/server/src/services/feature-state-manager.ts
    • Introduced notification type and title constants for consistent event categorization.
    • Implemented subscription to auto-mode events to handle error event notifications.
    • Added a destroy() method for proper cleanup of event subscriptions.
    • Refactored task finalization logic for improved clarity and separated setting/clearing justFinishedAt.
    • Improved getFeatureDisplayName logic to prefer feature title over ID for notifications.
  • apps/server/src/services/ntfy-service.ts
    • Added new service NtfyService to handle sending push notifications via ntfy.sh.
    • Implemented functionality for endpoint validation, authentication (basic, token), and variable substitution in notification content.
    • Provided methods for building tags, adding authentication headers, and generating default titles/bodies based on event context.
  • apps/server/src/services/settings-service.ts
    • Added a data loss guard for ntfyEndpoints to prevent accidental overwrites with empty arrays.
    • Included ntfyEndpoints in the settings migration process.
  • apps/server/tests/unit/routes/running-agents.test.ts
    • Updated test data for running agents to include model and provider fields.
    • Added new test cases to verify the inclusion and backward compatibility of model and provider information.
  • apps/server/tests/unit/services/auto-loop-coordinator.test.ts
    • Added extensive unit tests for auto_mode_idle event emission timing, covering various scenarios including in-progress features and worktree contexts.
    • Included tests for graceful error handling when loading features for idle checks.
  • apps/server/tests/unit/services/auto-mode/facade-agent-runner.test.ts
    • Updated mock event emitter to include a subscribe method for testing purposes.
  • apps/server/tests/unit/services/event-hook-service.test.ts
    • Added mock fetch for testing ntfy hooks.
    • Introduced comprehensive tests for ntfy hook execution, covering endpoint configuration, default values, authentication, failure handling, and variable substitution.
  • apps/server/tests/unit/services/feature-state-manager.test.ts
    • Added tests to ensure feature titles are correctly used as notification titles, falling back to feature ID when necessary.
    • Included new tests for the handleAutoModeEventError and destroy methods.
  • apps/server/tests/unit/services/ntfy-service.test.ts
    • Added new unit test file for NtfyService, covering all core functionalities including endpoint validation, notification sending, authentication, error handling, and variable substitution.
  • apps/server/tests/unit/services/settings-service.test.ts
    • Added tests for ntfyEndpoints persistence, including data loss guards, adding/updating, and migration from local storage.
  • apps/ui/src/components/layout/project-switcher/components/notification-bell.tsx
    • Moved formatRelativeTime utility to lib/utils.ts for reusability.
    • Updated navigation logic to support deep-linking to the board view with a specific feature ID.
    • Added new icons for feature_error and auto_mode_error notification types.
  • apps/ui/src/components/session-manager.tsx
    • Adjusted session creation logic to select the new session immediately before invalidating the cache, preventing potential race conditions.
  • apps/ui/src/components/views/board-view.tsx
    • Modified BoardView to accept an initialFeatureId prop for deep-linking.
    • Implemented a useEffect hook to handle initial feature ID from URL parameters, switching to the correct worktree and opening the output modal.
    • Ensured branchName is always a string for new features.
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
    • Updated taskSummaryMap type to allow null values for task summaries.
    • Refined the type definition for effectiveTodos to include summary.
  • apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx
    • Added expand/collapse functionality to the PR description textarea.
    • Included prDescriptionModel in the list of phase labels.
  • apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx
    • Improved base branch selection logic for worktrees, specifically for remote branches, by stripping remote prefixes for display and handling explicit remote selection.
    • Updated isRemoteBaseBranch detection to consider explicitly selected remotes.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx
    • Changed spinner variant to 'primary' for consistency.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx
    • Changed spinner variant to 'primary' and corrected prop name isStartingDevServer.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
    • Corrected prop name isStartingDevServer.
  • apps/ui/src/components/views/notifications-view.tsx
    • Moved formatRelativeTime utility to lib/utils.ts.
    • Updated navigation logic to deep-link to the board view with a specific feature ID when clicking a notification.
    • Added new icons for feature_error and auto_mode_error notification types.
  • apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx
    • Added prDescriptionModel to the list of phase labels.
  • apps/ui/src/components/views/running-agents-view.tsx
    • Added Cpu icon to the running agents display.
    • Implemented display of model and provider information for running agents.
    • Added formatFeatureId utility for more readable feature IDs.
  • apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx
    • Added a new 'ntfy' action type for event hooks.
    • Implemented UI for configuring ntfy notifications, including endpoint selection, title, body, tags, emoji, click URL, and priority.
    • Added validation for JSON headers in HTTP actions.
  • apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
    • Introduced a new 'Endpoints' tab for managing ntfy.sh notification endpoints.
    • Added UI components (cards, dialog) for creating, editing, deleting, and toggling ntfy endpoints.
    • Updated HookCard to display icons and details for ntfy hooks.
  • apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx
    • Added prDescriptionModel to the list of phase labels.
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
    • Removed the 'Free' badge for basic tier models.
  • apps/ui/src/hooks/queries/use-worktrees.ts
    • Adjusted type assertion for remotesWithBranch to handle optionality.
  • apps/ui/src/hooks/use-agent-output-websocket.ts
    • Updated type definition for closeTimeoutRef.
    • Added type assertion for handleBacklogPlanEvent callback.
  • apps/ui/src/hooks/use-settings-migration.ts
    • Included ntfyEndpoints and featureTemplates in the settings migration process.
    • Refined the migration logic for agentModelBySession to handle various input types.
  • apps/ui/src/hooks/use-settings-sync.ts
    • Added ntfyEndpoints to the list of settings fields to synchronize with the server.
    • Refined the migration logic for agentModelBySession during settings refresh.
  • apps/ui/src/lib/electron.ts
    • Added optional model and provider fields to the RunningAgent interface.
  • apps/ui/src/lib/log-parser.ts
    • Corrected nullish coalescing for the result of match() when checking for multiple phases.
  • apps/ui/src/lib/utils.ts
    • Extended getModelDisplayName to correctly handle full Claude model IDs in addition to aliases.
    • Added formatRelativeTime utility function for displaying human-readable relative timestamps.
  • apps/ui/src/main.ts
    • Corrected type assertion for app.setDesktopName in Electron main process.
  • apps/ui/src/routes/board.lazy.tsx
    • Modified to extract featureId from URL search parameters and pass it as initialFeatureId to BoardView.
  • apps/ui/src/routes/board.tsx
    • Added a zod schema for validating the featureId search parameter in the board route.
  • apps/ui/src/store/app-store.ts
    • Added ntfyEndpoints to the application state.
    • Implemented setNtfyEndpoints action to manage and persist ntfy endpoint configurations.
  • apps/ui/src/store/types/state-types.ts
    • Imported NtfyEndpointConfig type.
    • Added ntfyEndpoints to the AppState and AppActions interfaces.
  • apps/ui/src/types/electron.d.ts
    • Added a new dev-server:starting event type to the onDevServerLogEvent callback.
  • apps/ui/tests/agent/start-new-chat-session.spec.ts
    • Removed a redundant session item click, relying on automatic selection of newly created sessions.
  • apps/ui/tests/features/feature-deep-link.spec.ts
    • Added new E2E tests to verify deep-linking functionality to the board view with a specific feature ID, including handling of valid/invalid IDs and no ID.
  • apps/ui/tests/settings/event-hooks-settings.spec.ts
    • Added new E2E tests for the event hooks settings section, focusing on ntfy endpoint management.
    • Verified dialog opening/closing, form resets (validating useEffect), enabled toggle functionality, form validation, and persistence of endpoints after page reload.
  • test/fixtures/projectA
    • Removed deprecated submodule reference.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant improvements, including more robust event emission for the auto-loop, support for ntfy.sh notifications, and several UI/UX enhancements. The refactoring of sleep intervals into constants and the consolidation of notification logic are great for maintainability. The fix for premature auto_mode_idle events is a critical improvement. I've provided a couple of suggestions to further improve robustness and code clarity. Overall, this is a high-quality set of changes.

Comment on lines +517 to +524
} catch (error) {
const errorInfo = classifyError(error);
logger.warn(
`Failed to load all features for idle check (projectPath=${projectPath}, branchName=${branchName}): ${errorInfo.message}`,
error
);
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

In the catch block, returning false could lead to prematurely emitting an auto_mode_idle event if loadAllFeaturesFn fails transiently. This behavior is confirmed by the test handles loadAllFeaturesFn error gracefully (falls back to emitting idle). However, to better align with the goal of preventing premature idle events, it would be more robust to return true in case of an error. This would prevent the system from becoming idle due to a temporary failure in loading features, making the auto-loop more resilient.

    } catch (error) {
      const errorInfo = classifyError(error);
      logger.warn(
        `Failed to load all features for idle check (projectPath=${projectPath}, branchName=${branchName}): ${errorInfo.message}`,
        error
      );
      // Return true to be pessimistic and prevent premature idle event on transient error.
      return true;
    }

Comment on lines 344 to 348
const effectiveBaseBranch = trimmedBaseBranch
? selectedRemote !== 'local' && !trimmedBaseBranch.includes('/')
? `${selectedRemote}/${trimmedBaseBranch}`
: trimmedBaseBranch
: undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This nested ternary for constructing effectiveBaseBranch is a bit dense and can be hard to read. For better maintainability and clarity, consider refactoring this logic into a more explicit if statement. This will make the intent clearer and reduce the chance of introducing bugs in the future.

      let effectiveBaseBranch: string | undefined;
      if (trimmedBaseBranch) {
        if (selectedRemote !== 'local' && !trimmedBaseBranch.includes('/')) {
          effectiveBaseBranch = `${selectedRemote}/${trimmedBaseBranch}`;
        } else {
          effectiveBaseBranch = trimmedBaseBranch;
        }
      }

Copy link
Contributor

@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: 19

🧹 Nitpick comments (4)
.gitignore (1)

74-75: Looks good; fixture artifact exclusion is correctly covered.

Adding test/fixtures/ is appropriate and matches the PR goal of keeping generated test artifacts out of VCS. Optional cleanup: test/fixtures/.worker-*/ is now redundant since the parent directory is already ignored.

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

In @.gitignore around lines 74 - 75, The .gitignore now ignores the entire
test/fixtures/ directory, so remove the redundant specific pattern
`test/fixtures/.worker-*/` to avoid duplication; keep the `test/fixtures/` entry
and delete the `test/fixtures/.worker-*/` line so only the parent directory
pattern remains.
apps/server/tests/unit/routes/running-agents.test.ts (1)

207-284: Test additions look good with minor redundancy.

The new tests at lines 207-256 provide comprehensive coverage for multi-provider scenarios (claude, codex, cursor). The backward compatibility test at lines 258-284 partially overlaps with the existing test at lines 81-108, which already verifies model: undefined and provider: undefined. Consider whether both backward compatibility tests are needed, or if one could be removed to reduce maintenance overhead.

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

In `@apps/server/tests/unit/routes/running-agents.test.ts` around lines 207 - 284,
The new backward-compatibility test "should handle agents without model
information (backward compatibility)" duplicates an earlier test that already
asserts model/provider are undefined; remove or consolidate the duplicate so
only one test verifies legacy agents, keeping the multi-provider test ("should
include model and provider information for running agents"); update references
to mockAutoModeService.getRunningAgents and createIndexHandler in the remaining
test as needed and ensure assertions still check
response.runningAgents[0].model/provider and response.success.
apps/ui/tests/features/feature-deep-link.spec.ts (1)

86-86: Replace fixed sleeps with condition-based waits to reduce flaky timing.

Hardcoded waitForTimeout calls can make these tests brittle and slower in CI.

🔧 Suggested fix
-    await page.waitForTimeout(2000);
+    // No fixed delay needed; the toPass block below already waits for card appearance.

@@
-      await page.keyboard.press('Escape');
-      await page.waitForTimeout(500);
+      await page.keyboard.press('Escape');
+      await expect(modal).toBeHidden({ timeout: 2000 });

@@
-    await page.waitForTimeout(2000);
-    const modalVisible = await modal.isVisible();
-    expect(modalVisible).toBe(false);
+    await expect(modal).toBeHidden({ timeout: 2000 });

@@
-    await page.waitForTimeout(1000);
-    const modalVisible = await modal.isVisible();
-    expect(modalVisible).toBe(false);
+    await expect(modal).toBeHidden({ timeout: 2000 });

Also applies to: 111-112, 148-150, 172-174

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

In `@apps/ui/tests/features/feature-deep-link.spec.ts` at line 86, Replace the
hardcoded sleeps (page.waitForTimeout) with condition-based waits to avoid
flakes: find each usage of page.waitForTimeout (including the occurrences around
lines referenced) and replace it with a targeted wait such as
page.waitForSelector or page.waitForResponse or playwright's
expect(locator).toBeVisible()/toHaveText() or page.waitForFunction that waits
for the specific DOM element, network response, or state change your test needs;
ensure you reference the element selector, API route, or condition used
elsewhere in this spec so the test only proceeds when the actual condition is
met instead of a fixed timeout.
apps/server/tests/unit/services/settings-service.test.ts (1)

26-35: Use a typed endpoint helper instead of any to prevent silent schema drift in tests.

The helper currently accepts/returns any, which can hide type regressions in ntfy endpoint shape.

💡 Proposed refactor
+import type { NtfyEndpointConfig } from '@/types/settings.js';
@@
-  function createTestNtfyEndpoint(overrides: Partial<any> = {}): any {
+  function createTestNtfyEndpoint(
+    overrides: Partial<NtfyEndpointConfig> = {}
+  ): NtfyEndpointConfig {
     return {
       id: `endpoint-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
       name: 'Test Endpoint',
       serverUrl: 'https://ntfy.sh',
       topic: 'test-topic',
       authType: 'none',
       enabled: true,
       ...overrides,
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/tests/unit/services/settings-service.test.ts` around lines 26 -
35, The helper createTestNtfyEndpoint currently uses untyped any which can mask
schema drift; change its signature to accept overrides: Partial<NtfyEndpoint>
and return type NtfyEndpoint (or the concrete ntfy endpoint interface used by
the settings service tests), update the function body to satisfy that interface,
and import the NtfyEndpoint/Endpoint type from the module that defines the ntfy
endpoint shape so TypeScript will catch mismatches in tests.
🤖 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/server/src/services/auto-loop-coordinator.ts`:
- Around line 489-497: featureBelongsToWorktree currently treats the main
worktree by checking !feature.branchName, which misses features stored with
branchName === 'main'; update featureBelongsToWorktree to mirror
getWorktreeAutoLoopKey's normalization (treat branchName === null or branchName
=== 'main' as main): when the incoming branchName is null or 'main' return true
for features that have no branchName OR feature.branchName === 'main'; otherwise
keep the exact match path (feature.branchName === branchName). Use the function
name featureBelongsToWorktree and the same normalization pattern used around
getWorktreeAutoLoopKey.

In `@apps/server/src/services/ntfy-service.ts`:
- Around line 103-115: The try block in the ntfy send path creates an
AbortController and timeoutId but only calls clearTimeout(timeoutId) after a
successful fetch, leaving timers scheduled on errors/abort; update the request
logic in the ntfy service (where AbortController, timeoutId,
DEFAULT_NTFY_TIMEOUT and fetch are used) to ensure clearTimeout(timeoutId) is
invoked in all outcomes by moving clearTimeout into a finally block (or
otherwise calling it in both catch and after await), so the timeout is always
cleared even when fetch throws or is aborted.

In `@apps/server/tests/unit/services/auto-loop-coordinator.test.ts`:
- Around line 1265-1304: Rename the test description to match the assertion:
update the it(...) string currently "'does NOT emit auto_mode_idle when features
are in backlog or ready status'" to a positive description like "'emits
auto_mode_idle when only backlog/ready features exist and no running/in_progress
features'"; the test body using coordinator.startAutoLoopForProject,
coordinator.stopAutoLoopForProject and expecting
mockEventBus.emitAutoModeEvent('auto_mode_idle', ...) can remain unchanged.

In `@apps/server/tests/unit/services/feature-state-manager.test.ts`:
- Around line 1498-1506: The test currently wraps subscribeCallback in an async
arrow and uses .not.toThrow(), which doesn't work for promises and can miss
rejections; replace that pattern by directly awaiting the promise assertion:
call subscribeCallback('auto-mode:event', { type: 'auto_mode_error', ... }) and
assert it resolves (e.g. await
expect(subscribeCallback(...)).resolves.toBeUndefined() or another appropriate
resolved value) so rejected promises fail the test; update the test around the
subscribeCallback invocation accordingly.

In `@apps/ui/src/components/views/board-view.tsx`:
- Around line 1135-1136: The branchName prop currently coerces to an empty
string using "?? ''" when addFeatureUseSelectedWorktreeBranch is false; change
this to pass undefined instead so branchName is unset for workMode='current'
(consistent with handleAddFeature's finalBranchName logic and handleBulkUpdate
behavior). Update the two occurrences that use
(addFeatureUseSelectedWorktreeBranch ? selectedWorktreeBranch : undefined) ?? ''
to simply (addFeatureUseSelectedWorktreeBranch ? selectedWorktreeBranch :
undefined) so branchName is undefined when not provided; keep the rest of the
call sites (selectedWorktreeBranch, addFeatureUseSelectedWorktreeBranch,
branchName) unchanged.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx`:
- Line 88: The code uses realtimeSummary ?? task.summary which treats stored
null as absent, undoing "clear" operations; update the resolution logic to check
Map.has(task.id) instead of null-coalescing so a stored null is treated as an
explicit cleared summary. Locate usages around taskSummaryMap/setTaskSummaryMap
and replace patterns like `realtimeSummary ?? task.summary` with a check such as
`taskSummaryMap.has(task.id) ? realtimeSummary : task.summary` (apply the same
fix to the other occurrences around lines 203-207) so that a Map entry of null
preserves the cleared state.

In `@apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx`:
- Line 103: The dialog's expanded state variable is not cleared on reset: update
the resetState function in the create-pr-dialog component to also reset
isDescriptionExpanded by calling setIsDescriptionExpanded(false) alongside the
other state resets so the description is collapsed when the dialog is reopened;
reference the isDescriptionExpanded and setIsDescriptionExpanded hooks and
ensure resetState calls setIsDescriptionExpanded(false).

In `@apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx`:
- Line 448: The collapsed base-branch label construction in CreateWorktreeDialog
is double-prefixing when baseBranch already includes the remote
(selectedRemote), so update the rendering logic that currently uses
{selectedRemote !== 'local' ? `${selectedRemote}/${baseBranch}` : baseBranch} to
conditionally prefix only when baseBranch does not already start with
`${selectedRemote}/`; use selectedRemote and baseBranch to guard with a
startsWith check and fall back to baseBranch when already qualified.
- Around line 341-348: Summary: Remote ref construction incorrectly skips
prepending the selected remote when the branch name contains a '/' (e.g.
feature/foo), causing refs like feature/foo instead of origin/feature/foo. Fix:
in the effectiveBaseBranch computation (variables trimmedBaseBranch,
selectedRemote, effectiveBaseBranch) replace the check
!trimmedBaseBranch.includes('/') with a check that the branch already starts
with the selected remote: !trimmedBaseBranch.startsWith(`${selectedRemote}/`) so
you only skip prepending when the branch is actually already qualified with the
chosen remote; otherwise prepend `${selectedRemote}/`.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx`:
- Around line 214-221: The current validation (isValid) only checks
ntfyEndpointId is non-empty, which allows saving stale IDs; update the
validation to verify that for actionType === 'ntfy' the chosen endpoint actually
exists in ntfyEndpoints by checking selectedEndpoint (or finding by
ntfyEndpointId) is truthy and its id matches ntfyEndpointId (and still trim the
id), so replace the ntfy branch in isValid with a check like
Boolean(selectedEndpoint) to prevent saving deleted/stale endpoints; ensure
selectedEndpoint is computed from ntfyEndpoints.find((e) => e.id ===
ntfyEndpointId) and used by the validation logic.
- Around line 267-277: The tab trigger spans currently use "hidden sm:inline"
which visually and accessibly hides the label on small viewports; update each
TabsTrigger (values "shell", "http", "ntfy") to keep labels available to
assistive tech by replacing the span className "hidden sm:inline" with "sr-only
sm:inline" (or alternatively add an appropriate aria-label on each TabsTrigger)
so the text is still exposed to screen readers on small screens while remaining
visually hidden until sm breakpoint.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 435-441: The endpoint card root div in EventHooksSection (the div
that builds its className via cn('flex items-center gap-3 p-3 rounded-lg
border', 'bg-background/50 hover:bg-background/80 transition-colors',
!endpoint.enabled && 'opacity-60')) needs a data-testid to satisfy the
Playwright test; add data-testid="endpoint-card" to that root element so the
test can reliably select existing endpoint cards (keep the attribute regardless
of endpoint.enabled).
- Around line 510-559: Editing endpoints currently overwrite secrets with blank
values and the form allows saving invalid auth configs; update the save and
validation logic so that handleSave preserves existing secrets when
editingEndpoint exists and the password/token inputs are left blank (use
editingEndpoint.password or editingEndpoint.token rather than empty strings),
only include username/password/token in the NtfyEndpointConfig when authType
requires them and values are non-empty after trim, and tighten isValid to
require a non-empty username+password for authType === 'basic' and a non-empty
token for authType === 'token' (in addition to the existing name/topic checks);
reference handleSave, editingEndpoint, setPassword/setToken usage,
NtfyEndpointConfig construction, and isValid for where to change logic.

In `@apps/ui/src/hooks/use-agent-output-websocket.ts`:
- Line 101: Replace the unsafe cast when subscribing to api.backlogPlan.onEvent
by adding a runtime type guard that verifies the incoming unknown is a valid
BacklogPlanEvent before calling handleBacklogPlanEvent; modify the subscription
line that assigns unsubscribe = api.backlogPlan.onEvent(...) to pass a wrapper
callback which checks the payload shape (e.g., required fields used by
formatBacklogPlanEventContent) and only calls handleBacklogPlanEvent(event) when
the guard returns true, otherwise log or ignore the malformed event to avoid
runtime failures in formatBacklogPlanEventContent.

In `@apps/ui/src/lib/utils.ts`:
- Around line 107-116: The display mapping in apps/ui/src/lib/utils.ts is
missing canonical Claude IDs so getModelDisplayName falls back to raw strings;
update the displayNames Record (and/or the normalization logic inside
getModelDisplayName) to include keys 'claude-haiku', 'claude-sonnet', and
'claude-opus' mapping to 'Claude Haiku', 'Claude Sonnet', and 'Claude Opus'
respectively so canonical IDs resolve to friendly names; locate the displayNames
object and add those entries (or add a normalization step in getModelDisplayName
that maps canonical IDs to the existing alias/full-ID entries).

In `@apps/ui/src/main.ts`:
- Line 59: Remove the use of the undocumented internal API call on app (the
expression casting to { setDesktopName(...) } and calling
setDesktopName('automaker.desktop')) and instead add a "desktopName":
"automaker.desktop" entry to the shipped package.json; update packaging/build
config if needed so the packaged app uses that package.json field. Locate the
offending call by searching for the symbol setDesktopName and the app cast in
main.ts, delete that line, and ensure the project’s package.json contains the
desktopName key so Linux desktop integration uses the documented mechanism.

In `@apps/ui/tests/features/feature-deep-link.spec.ts`:
- Around line 69-70: authenticateForTests returns a boolean but its result is
ignored; update each setup to capture the return value (e.g., const ok = await
authenticateForTests(page)) and assert it succeeded before continuing (either
throw an error or call your test assertion like expect(ok).toBe(true)) so
failures stop early; apply this change to the occurrences around
authenticateForTests in the feature-deep-link.spec.ts setup blocks (the calls on
and near lines referencing authenticateForTests).
- Around line 104-116: The deep-link URL is constructed by directly
interpolating featureId into page.goto which can break if the ID contains
URL-reserved characters; update the call that builds the URL (where featureId is
derived from cardTestId and passed to page.goto) to use an encoded value (e.g.,
replace the current `/board?featureId=${featureId}` usage with
`/board?featureId=${encodeURIComponent(featureId)}`) so the featureId is safely
URL-encoded before navigation.

---

Nitpick comments:
In @.gitignore:
- Around line 74-75: The .gitignore now ignores the entire test/fixtures/
directory, so remove the redundant specific pattern `test/fixtures/.worker-*/`
to avoid duplication; keep the `test/fixtures/` entry and delete the
`test/fixtures/.worker-*/` line so only the parent directory pattern remains.

In `@apps/server/tests/unit/routes/running-agents.test.ts`:
- Around line 207-284: The new backward-compatibility test "should handle agents
without model information (backward compatibility)" duplicates an earlier test
that already asserts model/provider are undefined; remove or consolidate the
duplicate so only one test verifies legacy agents, keeping the multi-provider
test ("should include model and provider information for running agents");
update references to mockAutoModeService.getRunningAgents and createIndexHandler
in the remaining test as needed and ensure assertions still check
response.runningAgents[0].model/provider and response.success.

In `@apps/server/tests/unit/services/settings-service.test.ts`:
- Around line 26-35: The helper createTestNtfyEndpoint currently uses untyped
any which can mask schema drift; change its signature to accept overrides:
Partial<NtfyEndpoint> and return type NtfyEndpoint (or the concrete ntfy
endpoint interface used by the settings service tests), update the function body
to satisfy that interface, and import the NtfyEndpoint/Endpoint type from the
module that defines the ntfy endpoint shape so TypeScript will catch mismatches
in tests.

In `@apps/ui/tests/features/feature-deep-link.spec.ts`:
- Line 86: Replace the hardcoded sleeps (page.waitForTimeout) with
condition-based waits to avoid flakes: find each usage of page.waitForTimeout
(including the occurrences around lines referenced) and replace it with a
targeted wait such as page.waitForSelector or page.waitForResponse or
playwright's expect(locator).toBeVisible()/toHaveText() or page.waitForFunction
that waits for the specific DOM element, network response, or state change your
test needs; ensure you reference the element selector, API route, or condition
used elsewhere in this spec so the test only proceeds when the actual condition
is met instead of a fixed timeout.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c0e460 and 3996cb8.

📒 Files selected for processing (49)
  • .gitignore
  • apps/server/src/services/auto-loop-coordinator.ts
  • apps/server/src/services/event-hook-service.ts
  • apps/server/src/services/feature-state-manager.ts
  • apps/server/src/services/ntfy-service.ts
  • apps/server/src/services/settings-service.ts
  • apps/server/tests/unit/routes/running-agents.test.ts
  • apps/server/tests/unit/services/auto-loop-coordinator.test.ts
  • apps/server/tests/unit/services/auto-mode/facade-agent-runner.test.ts
  • apps/server/tests/unit/services/event-hook-service.test.ts
  • apps/server/tests/unit/services/feature-state-manager.test.ts
  • apps/server/tests/unit/services/ntfy-service.test.ts
  • apps/server/tests/unit/services/settings-service.test.ts
  • apps/ui/src/components/layout/project-switcher/components/notification-bell.tsx
  • apps/ui/src/components/session-manager.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
  • apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/views/notifications-view.tsx
  • apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx
  • apps/ui/src/components/views/running-agents-view.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/hooks/queries/use-worktrees.ts
  • apps/ui/src/hooks/use-agent-output-websocket.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/log-parser.ts
  • apps/ui/src/lib/utils.ts
  • apps/ui/src/main.ts
  • apps/ui/src/routes/board.lazy.tsx
  • apps/ui/src/routes/board.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/store/types/state-types.ts
  • apps/ui/src/types/electron.d.ts
  • apps/ui/tests/agent/start-new-chat-session.spec.ts
  • apps/ui/tests/features/feature-deep-link.spec.ts
  • apps/ui/tests/settings/event-hooks-settings.spec.ts
  • libs/types/src/index.ts
  • libs/types/src/notification.ts
  • libs/types/src/settings.ts
  • test/fixtures/projectA
💤 Files with no reviewable changes (1)
  • test/fixtures/projectA

Comment on lines 1498 to 1506
// Should not throw - call the async function and verify it doesn't throw
await expect(
async () =>
await subscribeCallback('auto-mode:event', {
type: 'auto_mode_error',
message: 'Error',
projectPath: '/project',
})
).not.toThrow();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Async assertion here is misleading and can pass on rejected promises.

For async callbacks, toThrow() is not the right matcher pattern. This test can pass even if subscribeCallback rejects.

✅ Assertion fix
-      await expect(
-        async () =>
-          await subscribeCallback('auto-mode:event', {
-            type: 'auto_mode_error',
-            message: 'Error',
-            projectPath: '/project',
-          })
-      ).not.toThrow();
+      await expect(
+        (async () => {
+          await subscribeCallback('auto-mode:event', {
+            type: 'auto_mode_error',
+            message: 'Error',
+            projectPath: '/project',
+          });
+        })()
+      ).resolves.toBeUndefined();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Should not throw - call the async function and verify it doesn't throw
await expect(
async () =>
await subscribeCallback('auto-mode:event', {
type: 'auto_mode_error',
message: 'Error',
projectPath: '/project',
})
).not.toThrow();
// Should not throw - call the async function and verify it doesn't throw
await expect(
(async () => {
await subscribeCallback('auto-mode:event', {
type: 'auto_mode_error',
message: 'Error',
projectPath: '/project',
});
})()
).resolves.toBeUndefined();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/tests/unit/services/feature-state-manager.test.ts` around lines
1498 - 1506, The test currently wraps subscribeCallback in an async arrow and
uses .not.toThrow(), which doesn't work for promises and can miss rejections;
replace that pattern by directly awaiting the promise assertion: call
subscribeCallback('auto-mode:event', { type: 'auto_mode_error', ... }) and
assert it resolves (e.g. await
expect(subscribeCallback(...)).resolves.toBeUndefined() or another appropriate
resolved value) so rejected promises fail the test; update the test around the
subscribeCallback invocation accordingly.

// desktop entry's Icon for the taskbar instead of Electron's default.
// Must be called before any window is created.
app.setDesktopName('automaker.desktop');
(app as unknown as { setDesktopName(name: string): void }).setDesktopName('automaker.desktop');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and check Electron version
find . -name "package.json" -type f | head -5 | xargs grep -l electron | head -1

Repository: AutoMaker-Org/automaker

Length of output: 89


🏁 Script executed:

# Read the actual code context around line 59
cat -n apps/ui/src/main.ts | sed -n '50,70p'

Repository: AutoMaker-Org/automaker

Length of output: 1033


🏁 Script executed:

# Check if there are type definition files or declarations for Electron
find . -type f -name "*.d.ts" | xargs grep -l "setDesktopName" 2>/dev/null | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Get Electron version from package.json
cat apps/ui/package.json | grep -A 2 -B 2 "electron"

Repository: AutoMaker-Org/automaker

Length of output: 2114


🌐 Web query:

Electron 39 setDesktopName API Linux app documentation

💡 Result:

Electron (including v39) does not have an app.setDesktopName() / setDesktopName API. Instead, the Linux “desktop file name” Electron uses is configured via the desktopName field in your app’s embedded package.json. (docs.flatpak.org)

What to do (Linux)

Add this to your app’s package.json (the one shipped inside your app bundle / app.asar):

{
  "desktopName": "com.example.MyApp.desktop"
}

Electron otherwise defaults to {appname}.desktop. (docs.flatpak.org)

Why it matters

Using the correct desktop filename is important on Linux (e.g., Flatpak requires the desktop file name to match the Flatpak app id, and using the wrong name can cause issues like missing window icons under Wayland). (docs.flatpak.org)

It’s also tied to certain Linux integrations (e.g., progress/badge features implemented via UnityLauncherAPI expect a matching .desktop identity). (docs.flatpak.org)

Citations:


🌐 Web query:

Electron app.setDesktopName method availability supported versions

💡 Result:

app.setDesktopName(name) exists in Electron, but it’s effectively an internal / undocumented API (it does not appear in the current public app API docs) and therefore not guaranteed stable across releases. It is present in Electron’s internal typings (internal-electron.d.ts) as an internal App method. [1]

What it does / platforms

In Electron’s implementation, SetDesktopName() only does anything on Linux: it sets the CHROME_DESKTOP environment variable. [2]

Supported Electron versions (confirmed)

From publicly visible Electron source/patch contexts, SetDesktopName is present in:

  • Electron 22.x (e.g., FreeBSD’s Electron 22 port patch context includes App::SetDesktopName). [3]
  • Electron 32.x / 37.x (multiple FreeBSD port update contexts still include App::SetDesktopName). [4]

So, it’s available in at least Electron 22 → 37 (and likely earlier), but because it’s internal/undocumented you should treat it as unsupported for compatibility guarantees. [1]

Practical recommendation

If your goal is correct desktop integration on Linux, prefer setting the correct .desktop file metadata (often via your packager) rather than relying on this method, since it may change or disappear without a formal breaking-change notice. [1]

Sources: [1] [2] [3] [4]


Replace with documented desktopName in package.json instead of internal Electron API.

setDesktopName() is an internal, undocumented Electron API not present in public type definitions. The forced type cast bypasses type safety and depends on unstable internals that may change across versions. The recommended approach for Linux desktop integration is to set desktopName in the shipped package.json:

{
  "desktopName": "automaker.desktop"
}

This is the supported, stable method that survives Electron version upgrades.

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

In `@apps/ui/src/main.ts` at line 59, Remove the use of the undocumented internal
API call on app (the expression casting to { setDesktopName(...) } and calling
setDesktopName('automaker.desktop')) and instead add a "desktopName":
"automaker.desktop" entry to the shipped package.json; update packaging/build
config if needed so the packaged app uses that package.json field. Locate the
offending call by searching for the symbol setDesktopName and the app cast in
main.ts, delete that line, and ensure the project’s package.json contains the
desktopName key so Linux desktop integration uses the documented mechanism.

Comment on lines +777 to +783
/** Username for basic auth (required if authType === 'basic') */
username?: string;
/** Password for basic auth (required if authType === 'basic') */
password?: string;
/** Access token (required if authType === 'token') */
token?: string;
/** Default tags for notifications (comma-separated emoji codes) */
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ntfy credentials are modeled in general settings (plaintext risk).

password and token inside NtfyEndpointConfig place secrets in GlobalSettings flow. This conflicts with the file’s own separation of sensitive secrets into Credentials storage and increases leakage risk.

Copy link
Contributor

@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

🧹 Nitpick comments (2)
apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx (1)

341-348: Consider extracting remote-qualification logic into a shared helper.

The same prefixing rule is implemented here and again in the collapsed label render, which can drift over time.

♻️ Suggested refactor
+  const qualifyBaseBranchWithRemote = useCallback(
+    (value: string) =>
+      selectedRemote !== 'local' && !value.startsWith(`${selectedRemote}/`)
+        ? `${selectedRemote}/${value}`
+        : value,
+    [selectedRemote]
+  );
+
   // Pass the validated baseBranch if one was selected (otherwise defaults to HEAD).
   // When a remote is selected, prepend the remote name to form the full ref
   // (e.g. "main" with remote "origin" becomes "origin/main").
   const effectiveBaseBranch = trimmedBaseBranch
-    ? selectedRemote !== 'local' && !trimmedBaseBranch.startsWith(`${selectedRemote}/`)
-      ? `${selectedRemote}/${trimmedBaseBranch}`
-      : trimmedBaseBranch
+    ? qualifyBaseBranchWithRemote(trimmedBaseBranch)
     : undefined;
-  {selectedRemote !== 'local' && !baseBranch.startsWith(`${selectedRemote}/`)
-    ? `${selectedRemote}/${baseBranch}`
-    : baseBranch}
+  {qualifyBaseBranchWithRemote(baseBranch)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx`
around lines 341 - 348, The remote-qualification logic that builds the full ref
(currently in CreateWorktreeDialog when computing effectiveBaseBranch using
trimmedBaseBranch and selectedRemote) should be extracted into a shared helper
(e.g., qualifyRemoteBranch(remote: string, branch?: string): string | undefined)
and used both where effectiveBaseBranch is computed and in the collapsed label
render; update the code to call qualifyRemoteBranch(selectedRemote,
trimmedBaseBranch) instead of duplicating the conditional, ensure the helper
preserves the existing rules (no-op for 'local', avoid double-prefix if branch
already starts with `${remote}/`, and return undefined when branch is empty).
apps/ui/tests/features/feature-deep-link.spec.ts (1)

87-87: Replace condition checks at lines 150–152 and 175–177 with Playwright's assertion helpers.

Lines 150–152 and 175–177 follow a pattern of sleeping before checking if a modal is visible. Replace these with await expect(modal).toBeHidden({ timeout: ... }) for more reliable waits:

Suggested fix
    const modal = page.locator('[data-testid="agent-output-modal"]');
-   await page.waitForTimeout(2000);
-   const modalVisible = await modal.isVisible();
-   expect(modalVisible).toBe(false);
+   await expect(modal).toBeHidden({ timeout: 2000 });
    const modal = page.locator('[data-testid="agent-output-modal"]');
-   await page.waitForTimeout(1000);
-   const modalVisible = await modal.isVisible();
-   expect(modalVisible).toBe(false);
+   await expect(modal).toBeHidden({ timeout: 1000 });

Line 87's sleep serves a different purpose (allowing feature creation to complete) and should be left in place or replaced with a wait on the actual feature card element if flakiness is observed.

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

In `@apps/ui/tests/features/feature-deep-link.spec.ts` at line 87, Replace the
manual sleep-and-check pattern around the modal visibility (the blocks
referenced as lines 150–152 and 175–177) with Playwright's assertion helper by
awaiting expect(modal).toBeHidden({ timeout: <reasonable_ms> }) and remove the
preceding sleep/if visibility checks; keep the existing await
page.waitForTimeout(2000); at line 87 (or replace it with an explicit wait for
the feature card element if you prefer) so feature creation still completes
before proceeding.
🤖 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/ui/src/components/views/board-view.tsx`:
- Line 310: handledFeatureIdRef currently remains set after the first deep-link
is handled so reopening the same featureId never triggers the modal again; reset
handledFeatureIdRef.current = undefined when the feature context changes and
when the modal is closed. Specifically, in the effect or logic that watches
featureId (the code around where handledFeatureIdRef is read to decide to open
the modal) set handledFeatureIdRef.current = undefined when featureId becomes
undefined or changes, and also clear it in the modal close handler (e.g., the
function that closes the feature modal / onCloseFeatureModal or closeFeature
handler) so the same featureId can be handled again on a subsequent navigation.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 465-476: The icon-only action controls (the Switch and the two
Buttons rendering Pencil and Trash2) lack accessible names; update the Switch
component (used with endpoint.enabled and onToggle) and the Buttons (onEdit,
onDelete) to include explicit accessible labels (e.g., aria-label or
aria-labelledby) that describe the action and include identifying context (like
endpoint.name or endpoint.id) so screen readers can distinguish "Enable endpoint
X", "Edit endpoint X", and "Delete endpoint X"; ensure labels are unique and
consider adding a visually-hidden text alternative if aria-label is not
supported by your Button/Switch primitives.
- Around line 567-575: The current isValid check (used to enable saving in the
event hooks form) omits validating serverUrl, so add a serverUrl validity check
to the same boolean: require serverUrl.trim().length > 0 and a simple URL
validity test (e.g., try new URL(serverUrl) or ensure it starts with "http" /
contains a host) before allowing save; update the isValid expression
(referencing isValid, serverUrl, name, topic, authType, username, password,
token, editingEndpoint) to include this serverUrl validation so empty/invalid
server URLs cannot be persisted.

In `@apps/ui/src/lib/utils.ts`:
- Around line 228-241: formatRelativeTime currently treats future dates as "just
now" because diffSec can be negative; add a guard at the top of the function
(before the diffSec < 60 check) that checks if diffSec < 0 and returns a
sensible value (e.g., date.toLocaleDateString() or a short "in the future"
string) so future timestamps aren't misreported; update the logic around the
diffSec variable in formatRelativeTime to handle this case.

---

Nitpick comments:
In `@apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx`:
- Around line 341-348: The remote-qualification logic that builds the full ref
(currently in CreateWorktreeDialog when computing effectiveBaseBranch using
trimmedBaseBranch and selectedRemote) should be extracted into a shared helper
(e.g., qualifyRemoteBranch(remote: string, branch?: string): string | undefined)
and used both where effectiveBaseBranch is computed and in the collapsed label
render; update the code to call qualifyRemoteBranch(selectedRemote,
trimmedBaseBranch) instead of duplicating the conditional, ensure the helper
preserves the existing rules (no-op for 'local', avoid double-prefix if branch
already starts with `${remote}/`, and return undefined when branch is empty).

In `@apps/ui/tests/features/feature-deep-link.spec.ts`:
- Line 87: Replace the manual sleep-and-check pattern around the modal
visibility (the blocks referenced as lines 150–152 and 175–177) with
Playwright's assertion helper by awaiting expect(modal).toBeHidden({ timeout:
<reasonable_ms> }) and remove the preceding sleep/if visibility checks; keep the
existing await page.waitForTimeout(2000); at line 87 (or replace it with an
explicit wait for the feature card element if you prefer) so feature creation
still completes before proceeding.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3996cb8 and 53b7dfa.

📒 Files selected for processing (21)
  • .gitignore
  • apps/server/package.json
  • apps/server/src/services/auto-loop-coordinator.ts
  • apps/server/src/services/ntfy-service.ts
  • apps/server/tests/unit/routes/running-agents.test.ts
  • apps/server/tests/unit/services/auto-loop-coordinator.test.ts
  • apps/server/tests/unit/services/feature-state-manager.test.ts
  • apps/server/tests/unit/services/settings-service.test.ts
  • apps/ui/package.json
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
  • apps/ui/src/components/views/board-view/components/selection-action-bar.tsx
  • apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
  • apps/ui/src/hooks/use-agent-output-websocket.ts
  • apps/ui/src/lib/utils.ts
  • apps/ui/src/main.ts
  • apps/ui/tests/features/feature-deep-link.spec.ts
  • package.json
💤 Files with no reviewable changes (1)
  • apps/ui/src/main.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/server/package.json
  • package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx
  • .gitignore
  • apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx

gsxdsm and others added 2 commits February 28, 2026 18:00
* Changes from fix/orphaned-features

* fix: Handle feature migration failures and improve UI accessibility

* feat: Add event emission for worktree deletion and feature migration

* fix: Handle OpenCode model errors and prevent duplicate model IDs

* feat: Add summary dialog and async verify with loading state

* fix: Add type attributes to buttons and improve OpenCode model selection

* fix: Add null checks for onVerify callback and opencode model selection
Copy link
Contributor

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/ui/src/hooks/use-settings-migration.ts (1)

178-203: ⚠️ Potential issue | 🟠 Major

New migrated fields can be dropped during merge-based migration.

These fields are now parsed/hydrated/synced, but mergeSettings() still does not merge them from localStorage in the first-time merge path, so local values can be lost before sync-back.

💡 Proposed fix
 export function mergeSettings(
   serverSettings: GlobalSettings,
   localSettings: Partial<GlobalSettings> | null
 ): GlobalSettings {
@@
   if (
     (!serverSettings.eventHooks || serverSettings.eventHooks.length === 0) &&
     localSettings.eventHooks &&
     localSettings.eventHooks.length > 0
   ) {
     merged.eventHooks = localSettings.eventHooks;
   }

+  if (
+    (!serverSettings.ntfyEndpoints || serverSettings.ntfyEndpoints.length === 0) &&
+    localSettings.ntfyEndpoints &&
+    localSettings.ntfyEndpoints.length > 0
+  ) {
+    merged.ntfyEndpoints = localSettings.ntfyEndpoints;
+  }
+
+  if (
+    (!serverSettings.featureTemplates || serverSettings.featureTemplates.length === 0) &&
+    localSettings.featureTemplates &&
+    localSettings.featureTemplates.length > 0
+  ) {
+    merged.featureTemplates = localSettings.featureTemplates;
+  }
+
+  if (
+    (!serverSettings.knownDynamicModelIds || serverSettings.knownDynamicModelIds.length === 0) &&
+    localSettings.knownDynamicModelIds &&
+    localSettings.knownDynamicModelIds.length > 0
+  ) {
+    merged.knownDynamicModelIds = localSettings.knownDynamicModelIds;
+  }
+
+  if (
+    localSettings.defaultSortNewestCardOnTop != null &&
+    merged.defaultSortNewestCardOnTop == null
+  ) {
+    merged.defaultSortNewestCardOnTop = localSettings.defaultSortNewestCardOnTop;
+  }
+
   // Preserve new settings fields from localStorage if server has defaults
   // Use nullish coalescing to accept stored falsy values (e.g. false)

Also applies to: 815-817, 926-957

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

In `@apps/ui/src/hooks/use-settings-migration.ts` around lines 178 - 203, The
listed new settings (e.g., defaultSortNewestCardOnTop, enhancementModel,
validationModel, phaseModels, enabledCursorModels, cursorDefaultModel,
enabledOpencodeModels, opencodeDefaultModel, enabledDynamicModelIds,
disabledProviders, autoLoadClaudeMd, useClaudeCodeSystemPrompt, codex* fields,
keyboardShortcuts, mcpServers, promptCustomization, eventHooks, ntfyEndpoints,
featureTemplates) are parsed but not merged during the first-time merge path, so
local values can be dropped; update mergeSettings() (and the first-time merge
logic in use-settings-migration.ts) to include these properties in the
merge-from-localStorage step (i.e., explicitly copy/merge each of the above
fields from state/localStorage into the merged result) so new migrated fields
are preserved before sync-back.
♻️ Duplicate comments (2)
libs/types/src/settings.ts (1)

766-783: ⚠️ Potential issue | 🟠 Major

Avoid storing ntfy credentials in GlobalSettings.

At Line 779 and Line 782, endpoint secrets (password/token) are part of NtfyEndpointConfig, and at Line 1499 that config is stored under global settings. This increases plaintext leakage risk in settings sync/storage paths; keep secrets in Credentials and store only references in settings.

Also applies to: 1493-1500

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

In `@libs/types/src/settings.ts` around lines 766 - 783, NtfyEndpointConfig
currently contains secret fields (password, token) that are being persisted into
GlobalSettings; move secret storage out of NtfyEndpointConfig and into the
credentials system instead: remove password and token from NtfyEndpointConfig,
replace them with non-secret references (e.g., credentialId or secretRef),
update any code that writes/reads GlobalSettings to store only the reference,
and add logic in the credentials manager (Credentials) to persist/retrieve the
actual password/token; update all usages of NtfyEndpointConfig (creation,
validation, and consumers) to resolve secrets via the new reference rather than
reading password/token directly.
apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx (1)

579-588: ⚠️ Potential issue | 🟠 Major

Restrict endpoint serverUrl to HTTP(S) protocols.

Line 583 currently accepts any valid URL scheme. That still allows invalid ntfy endpoint configs (e.g., non-http(s)) to pass form validation.

💡 Proposed fix
   const isServerUrlValid = (() => {
     const trimmed = serverUrl.trim();
     if (!trimmed) return false;
     try {
-      new URL(trimmed);
-      return true;
+      const parsed = new URL(trimmed);
+      return parsed.protocol === 'http:' || parsed.protocol === 'https:';
     } catch {
       return false;
     }
   })();
#!/bin/bash
# Verify whether serverUrl validation enforces protocol restrictions.
rg -n -C3 "isServerUrlValid|new URL\\(trimmed\\)|protocol === 'http:'|protocol === 'https:'" \
  apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`
around lines 579 - 588, The current isServerUrlValid validation uses new
URL(trimmed) but accepts any scheme; update isServerUrlValid to parse the URL
(new URL(trimmed)) and then ensure the URL.protocol is either 'http:' or
'https:' before returning true, returning false for other protocols or parsing
errors; reference the isServerUrlValid constant, serverUrl variable, and the new
URL(trimmed) call when making this change.
🧹 Nitpick comments (3)
apps/ui/tests/features/feature-deep-link.spec.ts (2)

87-87: Replace hard wait with a condition-based wait.

Using waitForTimeout(2000) is discouraged in Playwright as it introduces unnecessary delays and can still be flaky if the operation takes longer. The subsequent toPass() block on lines 90-96 already waits for the feature card to appear, so this hard wait may be redundant.

♻️ Suggested improvement

Remove the hard wait since the following toPass() assertion already handles waiting for the feature card:

     await confirmAddFeature(page);
-    await page.waitForTimeout(2000);

     // Wait for the feature to appear in the backlog
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/tests/features/feature-deep-link.spec.ts` at line 87, Remove the hard
sleep await page.waitForTimeout(2000); in the feature-deep-link.spec.ts test and
rely on the existing condition-based wait instead (the subsequent toPass() block
that asserts the feature card visibility), or if an explicit wait is needed use
Playwright's waitForSelector/waitForLoadState tied to the same selector rather
than a fixed timeout; update the test to remove the redundant waitForTimeout
call and ensure the feature card assertion (the toPass() block) is the source of
truth for waiting.

108-113: Consider using a more robust modal dismissal pattern.

The waitForTimeout(500) after pressing Escape can be flaky. Instead, wait for the modal to become hidden.

♻️ Suggested improvement
     // Close any open modals first
     const modal = page.locator('[data-testid="agent-output-modal"]');
     if (await modal.isVisible()) {
       await page.keyboard.press('Escape');
-      await page.waitForTimeout(500);
+      await modal.waitFor({ state: 'hidden', timeout: 2000 });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/tests/features/feature-deep-link.spec.ts` around lines 108 - 113,
Replace the flaky fixed timeout after pressing Escape with an explicit wait for
the modal to be hidden: after calling page.keyboard.press('Escape') on the
'[data-testid="agent-output-modal"]' locator (modal), await the modal becoming
hidden (e.g., use modal.waitFor({ state: 'hidden' }) or
page.waitForSelector('[data-testid="agent-output-modal"]', { state: 'hidden' })
) instead of await page.waitForTimeout(500) so the test only proceeds once the
modal is actually dismissed.
apps/ui/src/store/types/state-types.ts (1)

632-633: Track the async action harmonization TODO with an issue.

This TODO sits in a central contract file; consider linking it to a tracked issue to avoid long-term API inconsistency across providers.

If you want, I can draft a small follow-up issue template enumerating the remaining action signatures to standardize.

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

In `@apps/ui/src/store/types/state-types.ts` around lines 632 - 633, Replace the
inline TODO about harmonizing provider action types with a tracked issue
reference: create an issue in your tracker listing the action signatures to
standardize (include setCursorDefaultModel, toggleCursorModel,
setGeminiDefaultModel and any other provider actions), capture the desired
change to return Promise<void>, and then update the TODO comment to reference
that issue ID/URL (e.g., "tracked in ISSUE-ID") so the contract file points to
the follow-up work rather than an untracked note. Ensure the issue includes a
concise checklist of the functions to change and the expected API behavior for
async persistence.
🤖 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/server/src/routes/worktree/routes/delete.ts`:
- Line 13: The import of FeatureLoader uses a relative service path; replace it
with the canonical shared-package import (import type { FeatureLoader } from
'@automaker/feature-loader') so the module is pulled from the shared package
namespace instead of the local services path; update the import statement
referencing FeatureLoader accordingly and ensure any tooling/tsconfig aliases
still resolve the '@automaker/feature-loader' package.

In `@apps/ui/src/components/views/board-view/components/list-view/list-view.tsx`:
- Around line 245-248: The header's displayed sort is out of sync because rows
use effectiveSortConfig (computed from sortNewestCardOnTop and sortConfig) but
ListHeader is still receiving the original sortConfig; update the component that
renders ListHeader to pass effectiveSortConfig (or a derived value from it)
instead of sortConfig so ListHeader's active column/direction reflects the
forced { column: 'createdAt', direction: 'desc' } when sortNewestCardOnTop is
true; ensure any handlers that update sort also reconcile with
effectiveSortConfig to keep UI and data ordering consistent (look for
effectiveSortConfig, sortNewestCardOnTop, sortConfig, and ListHeader in the
file).

In `@apps/ui/src/components/views/board-view/components/selection-action-bar.tsx`:
- Around line 57-68: The verify dialog is being closed unconditionally in the
finally block of handleConfirmVerify, which loses context on failure; change the
flow to await onVerify inside try, on success call setShowVerifyDialog(false),
in catch preserve the dialog (do not call setShowVerifyDialog(false)) and
optionally surface the error, and keep setIsVerifying(false) in a finally so
loading state is always cleared; update references in handleConfirmVerify to use
try { await onVerify(); setShowVerifyDialog(false); } catch (err) { /* keep
dialog open / set error state */ } finally { setIsVerifying(false); } to
implement this behavior.

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 164-173: When switching to the 'opencode' provider the current
code only calls onModelSelect(defaultModelId) if a model exists, so clicking
OpenCode does nothing when allOpencodeModels is empty; update the branch
handling provider === 'opencode' (and selectedProvider !== 'opencode') to always
switch the provider (call onProviderSelect('opencode')) first, then attempt to
pick a model as now (compute isDefaultModelAvailable/defaultModelId and call
onModelSelect(defaultModelId) if present), and if no models exist call
onModelSelect(null) or a dedicated clearModel handler so the UI enters OpenCode
mode and can render its empty/error state. Ensure you reference and use
opencodeDefaultModel, allOpencodeModels, onModelSelect, and onProviderSelect in
the change.
- Around line 42-48: The component currently uses two separate fetch paths
(useOpencodeModels and fetchOpencodeModels) which causes state drift between the
React Query data and the store-managed auto-enable/known-ID logic
(enabledDynamicModelIds, knownDynamicModelIds, dynamicOpencodeModels).
Consolidate into a single source of truth by either: (A) moving the auto-enable
and known-ID side effects into the React Query hook useOpencodeModels so it
updates the store (call the existing store actions that fetch/update
enabledDynamicModelIds and knownDynamicModelIds inside the hook), or (B) have
useOpencodeModels read from the store’s fetchOpencodeModels result (i.e., wrap
the store action in a react-query wrapper) and ensure opencode-settings-tab uses
the same unified path; update all callers (model-selector.tsx,
opencode-settings-tab) to consume only that unified API so auto-enable and
known-ID tracking always run.

In `@apps/ui/src/hooks/use-settings-sync.ts`:
- Around line 720-724: persistedKnownDynamicModelIds may be non-array and
calling .filter will throw; wrap the value with an array guard so
sanitizedKnownDynamicModelIds only runs filter on an actual array — e.g. coerce
persistedKnownDynamicModelIds to
Array.isArray(serverSettings.knownDynamicModelIds) ?
serverSettings.knownDynamicModelIds : currentAppState.knownDynamicModelIds (or
fallback to []), then compute sanitizedKnownDynamicModelIds by filtering that
safe array; update the variables referenced (persistedKnownDynamicModelIds,
sanitizedKnownDynamicModelIds, serverSettings.knownDynamicModelIds,
currentAppState.knownDynamicModelIds) in use-settings-sync.ts accordingly.

---

Outside diff comments:
In `@apps/ui/src/hooks/use-settings-migration.ts`:
- Around line 178-203: The listed new settings (e.g.,
defaultSortNewestCardOnTop, enhancementModel, validationModel, phaseModels,
enabledCursorModels, cursorDefaultModel, enabledOpencodeModels,
opencodeDefaultModel, enabledDynamicModelIds, disabledProviders,
autoLoadClaudeMd, useClaudeCodeSystemPrompt, codex* fields, keyboardShortcuts,
mcpServers, promptCustomization, eventHooks, ntfyEndpoints, featureTemplates)
are parsed but not merged during the first-time merge path, so local values can
be dropped; update mergeSettings() (and the first-time merge logic in
use-settings-migration.ts) to include these properties in the
merge-from-localStorage step (i.e., explicitly copy/merge each of the above
fields from state/localStorage into the merged result) so new migrated fields
are preserved before sync-back.

---

Duplicate comments:
In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 579-588: The current isServerUrlValid validation uses new
URL(trimmed) but accepts any scheme; update isServerUrlValid to parse the URL
(new URL(trimmed)) and then ensure the URL.protocol is either 'http:' or
'https:' before returning true, returning false for other protocols or parsing
errors; reference the isServerUrlValid constant, serverUrl variable, and the new
URL(trimmed) call when making this change.

In `@libs/types/src/settings.ts`:
- Around line 766-783: NtfyEndpointConfig currently contains secret fields
(password, token) that are being persisted into GlobalSettings; move secret
storage out of NtfyEndpointConfig and into the credentials system instead:
remove password and token from NtfyEndpointConfig, replace them with non-secret
references (e.g., credentialId or secretRef), update any code that writes/reads
GlobalSettings to store only the reference, and add logic in the credentials
manager (Credentials) to persist/retrieve the actual password/token; update all
usages of NtfyEndpointConfig (creation, validation, and consumers) to resolve
secrets via the new reference rather than reading password/token directly.

---

Nitpick comments:
In `@apps/ui/src/store/types/state-types.ts`:
- Around line 632-633: Replace the inline TODO about harmonizing provider action
types with a tracked issue reference: create an issue in your tracker listing
the action signatures to standardize (include setCursorDefaultModel,
toggleCursorModel, setGeminiDefaultModel and any other provider actions),
capture the desired change to return Promise<void>, and then update the TODO
comment to reference that issue ID/URL (e.g., "tracked in ISSUE-ID") so the
contract file points to the follow-up work rather than an untracked note. Ensure
the issue includes a concise checklist of the functions to change and the
expected API behavior for async persistence.

In `@apps/ui/tests/features/feature-deep-link.spec.ts`:
- Line 87: Remove the hard sleep await page.waitForTimeout(2000); in the
feature-deep-link.spec.ts test and rely on the existing condition-based wait
instead (the subsequent toPass() block that asserts the feature card
visibility), or if an explicit wait is needed use Playwright's
waitForSelector/waitForLoadState tied to the same selector rather than a fixed
timeout; update the test to remove the redundant waitForTimeout call and ensure
the feature card assertion (the toPass() block) is the source of truth for
waiting.
- Around line 108-113: Replace the flaky fixed timeout after pressing Escape
with an explicit wait for the modal to be hidden: after calling
page.keyboard.press('Escape') on the '[data-testid="agent-output-modal"]'
locator (modal), await the modal becoming hidden (e.g., use modal.waitFor({
state: 'hidden' }) or page.waitForSelector('[data-testid="agent-output-modal"]',
{ state: 'hidden' }) ) instead of await page.waitForTimeout(500) so the test
only proceeds once the modal is actually dismissed.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53b7dfa and 34508c7.

📒 Files selected for processing (33)
  • apps/server/src/index.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/server/src/routes/worktree/routes/delete.ts
  • apps/server/src/services/feature-loader.ts
  • apps/ui/src/components/dialogs/board-background-modal.tsx
  • apps/ui/src/components/ui/sheet.tsx
  • apps/ui/src/components/views/agent-view/components/agent-header.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx
  • apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
  • apps/ui/src/components/views/board-view/components/list-view/list-view.tsx
  • apps/ui/src/components/views/board-view/components/selection-action-bar.tsx
  • apps/ui/src/components/views/board-view/dialogs/create-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-actions.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-background.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • apps/ui/src/lib/utils.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/store/types/state-types.ts
  • apps/ui/tests/features/feature-deep-link.spec.ts
  • libs/types/src/event.ts
  • libs/types/src/feature.ts
  • libs/types/src/settings.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/lib/utils.ts

import { getErrorMessage, logError, isValidBranchName } from '../common.js';
import { execGitCommand } from '../../../lib/git.js';
import { createLogger } from '@automaker/utils';
import type { FeatureLoader } from '../../../services/feature-loader.js';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use shared-package import instead of direct services/feature-loader path.

This added import uses an old-style relative service path and should follow the shared-package import policy.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Always import from shared packages (@automaker/*), never from old paths like '../services/feature-loader' or '../lib/logger'.

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

In `@apps/server/src/routes/worktree/routes/delete.ts` at line 13, The import of
FeatureLoader uses a relative service path; replace it with the canonical
shared-package import (import type { FeatureLoader } from
'@automaker/feature-loader') so the module is pulled from the shared package
namespace instead of the local services path; update the import statement
referencing FeatureLoader accordingly and ensure any tooling/tsconfig aliases
still resolve the '@automaker/feature-loader' package.

Comment on lines 42 to 48
// Use React Query for OpenCode models so changes in settings are reflected immediately
const {
data: dynamicOpencodeModelsList = [],
isLoading: dynamicOpencodeLoading,
error: dynamicOpencodeError,
refetch: refetchOpencodeModels,
} = useOpencodeModels();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Locate useOpencodeModels implementation and call sites"
rg -n -C3 --type ts --type tsx '\buseOpencodeModels\b'

echo
echo "2) Check whether useOpencodeModels invokes or mirrors fetchOpencodeModels side effects"
rg -n -C4 --type ts --type tsx 'fetchOpencodeModels|knownDynamicModelIds|enabledDynamicModelIds'

echo
echo "Expected:"
echo "- Either the query hook calls into store fetch logic, OR"
echo "- query success path updates enabledDynamicModelIds/knownDynamicModelIds consistently."

Repository: AutoMaker-Org/automaker

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Locate useOpencodeModels implementation and call sites"
rg -n -C3 'useOpencodeModels'

echo
echo "2) Check for fetchOpencodeModels, knownDynamicModelIds, enabledDynamicModelIds"
rg -n -C4 'fetchOpencodeModels|knownDynamicModelIds|enabledDynamicModelIds'

echo
echo "3) Search for the actual hook file in hooks directory"
fd -e ts -e tsx 'hook' apps/ui/src/hooks/ 2>/dev/null | head -20

Repository: AutoMaker-Org/automaker

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== useOpencodeModels hook implementation ==="
sed -n '73,95p' apps/ui/src/hooks/queries/use-models.ts

echo
echo "=== Full hook and its query function ==="
cat -n apps/ui/src/hooks/queries/use-models.ts | sed -n '73,110p'

echo
echo "=== Check if queryFn calls fetchOpencodeModels or implements separately ==="
grep -n "fetchOpencodeModels\|queryFn" apps/ui/src/hooks/queries/use-models.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 2696


🏁 Script executed:

#!/bin/bash
set -euoripefail

echo "=== model-selector.tsx lines 40-90 (context around lines 42-48 and 69-88) ==="
sed -n '40,90p' apps/ui/src/components/views/board-view/shared/model-selector.tsx

echo
echo "=== Check if fetchOpencodeModels is called in the component ==="
rg -n "fetchOpencodeModels" apps/ui/src/components/views/board-view/shared/model-selector.tsx -C 3

Repository: AutoMaker-Org/automaker

Length of output: 741


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== model-selector.tsx lines 40-90 (context around lines 42-48 and 69-88) ==="
sed -n '40,90p' apps/ui/src/components/views/board-view/shared/model-selector.tsx

echo
echo "=== Check if fetchOpencodeModels is called in the component ==="
rg -n "fetchOpencodeModels" apps/ui/src/components/views/board-view/shared/model-selector.tsx -C 3

Repository: AutoMaker-Org/automaker

Length of output: 3248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== opencode-settings-tab.tsx - how it uses useOpencodeModels ==="
sed -n '36,65p' apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx

echo
echo "=== Search for sync logic in opencode-settings-tab ==="
rg -n "useEffect|fetchOpencodeModels|enabledDynamicModelIds" apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1612


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== fetchOpencodeModels implementation in app-store.ts ==="
sed -n '2879,2920p' apps/ui/src/store/app-store.ts

echo
echo "=== What API does fetchOpencodeModels call? ==="
sed -n '2900,2930p' apps/ui/src/store/app-store.ts | grep -A 20 "api\."

Repository: AutoMaker-Org/automaker

Length of output: 1352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full fetchOpencodeModels with API call and side effects ==="
sed -n '2879,2975p' apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 3697


Unify OpenCode model fetch logic — two separate data paths create state drift risk.

The component uses both useOpencodeModels() (React Query via Electron API) and fetchOpencodeModels() (Zustand store via HTTP API), but these are entirely separate implementations:

  • useOpencodeModels() returns raw model data with no side effects or store updates
  • fetchOpencodeModels() includes critical auto-enable logic that updates enabledDynamicModelIds and knownDynamicModelIds, persisting to server settings

The opencode-settings-tab syncs query data to the store's dynamicOpencodeModels, but this one-way sync bypasses the auto-enable and known-ID tracking logic in fetchOpencodeModels(). This creates risk where:

  • New model discovery and auto-enable behavior may not trigger consistently across the app
  • knownDynamicModelIds tracking becomes unreliable if some fetch paths skip the auto-enable logic
  • The model-selector.tsx calls both functions independently with no guarantee they stay in sync

Consider consolidating both into a single unified fetch mechanism (either enhance the React Query hook to include side effects, or have both consume the same store action).

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

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx` around
lines 42 - 48, The component currently uses two separate fetch paths
(useOpencodeModels and fetchOpencodeModels) which causes state drift between the
React Query data and the store-managed auto-enable/known-ID logic
(enabledDynamicModelIds, knownDynamicModelIds, dynamicOpencodeModels).
Consolidate into a single source of truth by either: (A) moving the auto-enable
and known-ID side effects into the React Query hook useOpencodeModels so it
updates the store (call the existing store actions that fetch/update
enabledDynamicModelIds and knownDynamicModelIds inside the hook), or (B) have
useOpencodeModels read from the store’s fetchOpencodeModels result (i.e., wrap
the store action in a react-query wrapper) and ensure opencode-settings-tab uses
the same unified path; update all callers (model-selector.tsx,
opencode-settings-tab) to consume only that unified API so auto-enable and
known-ID tracking always run.

Comment on lines 164 to 173
} else if (provider === 'opencode' && selectedProvider !== 'opencode') {
// Switch to OpenCode's default model (prefer configured default if it is actually enabled)
const isDefaultModelAvailable =
opencodeDefaultModel && allOpencodeModels.some((m) => m.id === opencodeDefaultModel);
const defaultModelId = isDefaultModelAvailable
? opencodeDefaultModel
: allOpencodeModels[0]?.id;
if (defaultModelId) {
onModelSelect(defaultModelId);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OpenCode provider switch can silently fail when no model is currently enabled.

If allOpencodeModels is empty, clicking the OpenCode provider button does nothing, so users can’t enter OpenCode mode to see its empty/error state.

💡 Suggested fix
     } else if (provider === 'opencode' && selectedProvider !== 'opencode') {
-      // Switch to OpenCode's default model (prefer configured default if it is actually enabled)
-      const isDefaultModelAvailable =
-        opencodeDefaultModel && allOpencodeModels.some((m) => m.id === opencodeDefaultModel);
-      const defaultModelId = isDefaultModelAvailable
-        ? opencodeDefaultModel
-        : allOpencodeModels[0]?.id;
-      if (defaultModelId) {
-        onModelSelect(defaultModelId);
-      }
+      // Always switch provider context, even when no enabled model is currently available
+      const defaultModelId = opencodeDefaultModel ?? allOpencodeModels[0]?.id;
+      if (defaultModelId) onModelSelect(defaultModelId);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx` around
lines 164 - 173, When switching to the 'opencode' provider the current code only
calls onModelSelect(defaultModelId) if a model exists, so clicking OpenCode does
nothing when allOpencodeModels is empty; update the branch handling provider ===
'opencode' (and selectedProvider !== 'opencode') to always switch the provider
(call onProviderSelect('opencode')) first, then attempt to pick a model as now
(compute isDefaultModelAvailable/defaultModelId and call
onModelSelect(defaultModelId) if present), and if no models exist call
onModelSelect(null) or a dedicated clearModel handler so the UI enters OpenCode
mode and can render its empty/error state. Ensure you reference and use
opencodeDefaultModel, allOpencodeModels, onModelSelect, and onProviderSelect in
the change.

Comment on lines 720 to 724
const persistedKnownDynamicModelIds =
serverSettings.knownDynamicModelIds ?? currentAppState.knownDynamicModelIds;
const sanitizedKnownDynamicModelIds = persistedKnownDynamicModelIds.filter(
(modelId) => !modelId.startsWith('amazon-bedrock/')
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a defensive array guard before filtering knownDynamicModelIds.

persistedKnownDynamicModelIds.filter(...) will throw if persisted settings are malformed (non-array), which can abort refresh/hydration.

🛡️ Suggested hardening
-    const persistedKnownDynamicModelIds =
-      serverSettings.knownDynamicModelIds ?? currentAppState.knownDynamicModelIds;
+    const persistedKnownDynamicModelIds = Array.isArray(serverSettings.knownDynamicModelIds)
+      ? serverSettings.knownDynamicModelIds
+      : currentAppState.knownDynamicModelIds;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/hooks/use-settings-sync.ts` around lines 720 - 724,
persistedKnownDynamicModelIds may be non-array and calling .filter will throw;
wrap the value with an array guard so sanitizedKnownDynamicModelIds only runs
filter on an actual array — e.g. coerce persistedKnownDynamicModelIds to
Array.isArray(serverSettings.knownDynamicModelIds) ?
serverSettings.knownDynamicModelIds : currentAppState.knownDynamicModelIds (or
fallback to []), then compute sanitizedKnownDynamicModelIds by filtering that
safe array; update the variables referenced (persistedKnownDynamicModelIds,
sanitizedKnownDynamicModelIds, serverSettings.knownDynamicModelIds,
currentAppState.knownDynamicModelIds) in use-settings-sync.ts accordingly.

Copy link
Contributor

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx (1)

407-421: ⚠️ Potential issue | 🟡 Minor

Add aria-labels to HookCard action controls for consistency.

EndpointCard (lines 465-488) includes aria-labels on its Switch and Buttons, but HookCard does not. Adding them here would ensure consistent accessibility across both card types.

♿ Suggested fix
       {/* Actions */}
       <div className="flex items-center gap-2">
-        <Switch checked={hook.enabled} onCheckedChange={onToggle} />
-        <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onEdit}>
+        <Switch
+          checked={hook.enabled}
+          onCheckedChange={onToggle}
+          aria-label={`${hook.enabled ? 'Disable' : 'Enable'} hook ${hook.name || 'unnamed'}`}
+        />
+        <Button
+          variant="ghost"
+          size="icon"
+          className="h-8 w-8"
+          onClick={onEdit}
+          aria-label={`Edit hook ${hook.name || 'unnamed'}`}
+        >
           <Pencil className="w-4 h-4" />
         </Button>
         <Button
           variant="ghost"
           size="icon"
           className="h-8 w-8 text-destructive hover:text-destructive"
           onClick={onDelete}
+          aria-label={`Delete hook ${hook.name || 'unnamed'}`}
         >
           <Trash2 className="w-4 h-4" />
         </Button>
       </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`
around lines 407 - 421, The HookCard action controls are missing aria-labels;
update the Switch and the two Button elements in the HookCard (the elements
using onToggle, onEdit, and onDelete) to include meaningful aria-label
attributes (e.g., "Enable hook", "Edit hook", "Delete hook" or include the hook
identifier/name for uniqueness) matching the pattern used in EndpointCard so
screen readers can identify each control consistently.
🧹 Nitpick comments (2)
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (1)

243-247: Consider decoupling effective sort deps to avoid unnecessary regroup/sort work.

When sortNewestCardOnTop is true, updates to sortConfig can still trigger statusGroups recomputation even though effective ordering stays createdAt desc. Using primitive effective keys in deps keeps behavior the same with less churn.

♻️ Suggested refactor
-  const effectiveSortConfig: SortConfig = useMemo(
-    () => (sortNewestCardOnTop ? { column: 'createdAt', direction: 'desc' } : sortConfig),
-    [sortNewestCardOnTop, sortConfig]
-  );
+  const effectiveSortColumn: SortColumn = sortNewestCardOnTop ? 'createdAt' : sortConfig.column;
+  const effectiveSortDirection: SortConfig['direction'] = sortNewestCardOnTop
+    ? 'desc'
+    : sortConfig.direction;
+
+  const effectiveSortConfig: SortConfig = useMemo(
+    () => ({ column: effectiveSortColumn, direction: effectiveSortDirection }),
+    [effectiveSortColumn, effectiveSortDirection]
+  );
@@
-        const sortedFeatures = sortFeatures(
-          features,
-          effectiveSortConfig.column,
-          effectiveSortConfig.direction
-        );
+        const sortedFeatures = sortFeatures(features, effectiveSortColumn, effectiveSortDirection);
@@
-  }, [columnFeaturesMap, pipelineConfig, effectiveSortConfig]);
+  }, [columnFeaturesMap, pipelineConfig, effectiveSortColumn, effectiveSortDirection]);

Also applies to: 257-262, 275-275

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

In `@apps/ui/src/components/views/board-view/components/list-view/list-view.tsx`
around lines 243 - 247, The memo for effectiveSortConfig and related memos
(e.g., statusGroups) recompute when the whole sortConfig object identity changes
even though the effective ordering is unchanged; update the useMemo dependency
to use primitive keys instead of the full object: compute a simple
effectiveSortKey (e.g., sortNewestCardOnTop ? 'createdAt:desc' :
`${sortConfig.column}:${sortConfig.direction}`) and use that string (or the two
primitives sortConfig.column and sortConfig.direction) in the dependency arrays
for effectiveSortConfig, statusGroups, and any other related useMemo hooks so
changes to unrelated fields or object identity won't trigger unnecessary
regroup/sort work.
apps/ui/src/components/views/board-view/components/selection-action-bar.tsx (1)

66-68: Consider surfacing a local failure message for retry clarity.

The empty catch keeps context (good), but adding inline error text/toast here would make retry intent clearer when upstream handlers don’t show an error.

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

In `@apps/ui/src/components/views/board-view/components/selection-action-bar.tsx`
around lines 66 - 68, In SelectionActionBar's try/catch around the action (the
empty catch before finally), add a local user-facing error notification so
failures are surfaced for retry clarity: either set a component state like
localError (and render inline error text near the dialog controls) or call the
app's toast/snackbar helper (e.g., showToast or useNotification) with a concise
message and optional error details; keep the dialog open as before and ensure
the finally block behavior is unchanged.
🤖 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/ui/tests/features/feature-deep-link.spec.ts`:
- Around line 28-58: The tests create shared filesystem fixtures in
test.beforeAll using projectPath and projectName, causing races under
fullyParallel; change to test.beforeEach to create a unique per-test project dir
(use TEST_TEMP_DIR and include testInfo.workerIndex and/or Date.now() or
testInfo.title in the name) and assign projectPath there, and move all
fs.writeFileSync/fs.mkdirSync fixture setup from test.beforeAll into that
beforeEach, ensuring teardown in test.afterEach removes the per-test directory;
update any usages that relied on the beforeAll-scoped projectPath to use the
per-test variable created in beforeEach.

---

Outside diff comments:
In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 407-421: The HookCard action controls are missing aria-labels;
update the Switch and the two Button elements in the HookCard (the elements
using onToggle, onEdit, and onDelete) to include meaningful aria-label
attributes (e.g., "Enable hook", "Edit hook", "Delete hook" or include the hook
identifier/name for uniqueness) matching the pattern used in EndpointCard so
screen readers can identify each control consistently.

---

Nitpick comments:
In `@apps/ui/src/components/views/board-view/components/list-view/list-view.tsx`:
- Around line 243-247: The memo for effectiveSortConfig and related memos (e.g.,
statusGroups) recompute when the whole sortConfig object identity changes even
though the effective ordering is unchanged; update the useMemo dependency to use
primitive keys instead of the full object: compute a simple effectiveSortKey
(e.g., sortNewestCardOnTop ? 'createdAt:desc' :
`${sortConfig.column}:${sortConfig.direction}`) and use that string (or the two
primitives sortConfig.column and sortConfig.direction) in the dependency arrays
for effectiveSortConfig, statusGroups, and any other related useMemo hooks so
changes to unrelated fields or object identity won't trigger unnecessary
regroup/sort work.

In `@apps/ui/src/components/views/board-view/components/selection-action-bar.tsx`:
- Around line 66-68: In SelectionActionBar's try/catch around the action (the
empty catch before finally), add a local user-facing error notification so
failures are surfaced for retry clarity: either set a component state like
localError (and render inline error text near the dialog controls) or call the
app's toast/snackbar helper (e.g., showToast or useNotification) with a concise
message and optional error details; keep the dialog open as before and ensure
the finally block behavior is unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34508c7 and 14c87eb.

📒 Files selected for processing (4)
  • apps/ui/src/components/views/board-view/components/list-view/list-view.tsx
  • apps/ui/src/components/views/board-view/components/selection-action-bar.tsx
  • apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
  • apps/ui/tests/features/feature-deep-link.spec.ts

…link test isolation

- Move project creation to beforeEach for per-test isolation
- Include workerIndex in project name to prevent parallel test conflicts
- Add afterEach cleanup to remove project directories after each test
- Maintain beforeAll for temp directory creation
@gsxdsm
Copy link
Collaborator Author

gsxdsm commented Mar 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Contributor

@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: 1

🤖 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/ui/tests/features/feature-deep-link.spec.ts`:
- Line 31: The beforeEach declaration uses an empty destructuring pattern async
({}, testInfo) which triggers the noEmptyPattern lint rule; replace the empty
pattern with a single intentionally ignored parameter name (prefixed with an
underscore) such as async (_fixtures, testInfo) or async (_unused, testInfo) in
the test.beforeEach callback to satisfy the linter while preserving the original
parameter order and behavior in feature-deep-link.spec.ts.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c449db and 958ec7b.

📒 Files selected for processing (1)
  • apps/ui/tests/features/feature-deep-link.spec.ts

gsxdsm and others added 2 commits February 28, 2026 21:43
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@gsxdsm gsxdsm merged commit 57bcb28 into AutoMaker-Org:v1.0.0rc Mar 1, 2026
6 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