Skip to content

Fix orphaned features when deleting worktrees#820

Merged
gsxdsm merged 7 commits intoAutoMaker-Org:v1.0.0rcfrom
gsxdsm:fix/orphaned-features
Feb 28, 2026
Merged

Fix orphaned features when deleting worktrees#820
gsxdsm merged 7 commits intoAutoMaker-Org:v1.0.0rcfrom
gsxdsm:fix/orphaned-features

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 28, 2026

Summary

  • Automatically move features associated with deleted worktree branches to the main branch to prevent data loss
  • Improve model name formatting in OpenCode provider by extracting and displaying tier/pricing suffixes
  • Enhance mobile responsiveness of agent header and plan approval dialog

Changes

Backend - Worktree Deletion (Fix for Orphaned Features)

  • Modified createWorktreeRoutes to accept FeatureLoader dependency
  • Updated createDeleteHandler to move features from deleted branches to main worktree
  • When a worktree with an associated branch is deleted, all features linked to that branch are automatically reassigned to the main branch (branchName: null)
  • Logs successful migrations and gracefully handles failures without blocking the deletion
  • Returns featuresMovedToMain count in the deletion response

Backend - OpenCode Model Display Names

  • Enhanced formatModelDisplayName in OpencodeProvider to properly parse nested model identifiers
  • Extracts and displays pricing tier/preview suffixes (e.g., "trinity-large-preview:free" → "Trinity Large Preview (Free)")
  • Strips pricing indicators from the base model name before formatting while preserving them in a parenthetical suffix
  • Improves clarity for models with pricing tiers like :free, :extended, :beta, :preview

Frontend - Mobile Responsiveness

  • Agent Header: Reduced padding/gaps for mobile, added responsive utilities for branch badge width, hid current tool indicator on small screens, responsive Clear button styling
  • Plan Approval Dialog: Made dialog full-screen on mobile with improved layout; responsive max-height for plan content area, textarea min-height adjusts for mobile
  • Card Badges: Added plan approval badge indicator (FileCheck icon with pulse animation) for features with generated plans ready for review
  • List View: Enhanced to show plan approval badge separately from regular plan badge, with appropriate styling and animation

Summary by CodeRabbit

  • New Features

    • OpenCode provider: dynamic model loading, selectable models, and improved model name display with tier labels.
    • Global "Newest-first" board sorting (dependency-aware) with per-board toggle and UI setting.
  • Improvements

    • Deleting a worktree can migrate matching features to main worktree and reports moved counts; emits migration/deletion events.
    • Feature creation timestamps for better ordering.
    • Compact responsive headers, refined dialogs/sheet controls, interactive Summary button, and verify action shows spinner and disables controls during confirmation.

@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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb7e4fe and 749f648.

📒 Files selected for processing (2)
  • apps/ui/src/components/views/board-view/components/selection-action-bar.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx

📝 Walkthrough

Walkthrough

createWorktreeRoutes now accepts an optional FeatureLoader and wires it into the worktree delete handler; worktree deletion migrates branch-scoped features to the main worktree and emits new events. UI and store changes add createdAt timestamps, newest-first sorting, OpenCode provider support, settings sync for dynamic models, and multiple UI/UX badge/layout refinements.

Changes

Cohort / File(s) Summary
Server: Worktree routing & delete
apps/server/src/index.ts, apps/server/src/routes/worktree/index.ts, apps/server/src/routes/worktree/routes/delete.ts
createWorktreeRoutes adds optional featureLoader param; delete handler factory now createDeleteHandler(events, featureLoader); delete flow migrates branch-bound features to main, emits feature:migrated per feature and worktree:deleted, returns featuresMovedToMain.
Server: Feature data & types
apps/server/src/services/feature-loader.ts, libs/types/src/feature.ts
Feature gains optional createdAt?: string; server assigns createdAt on create when missing.
Server: Opencode display
apps/server/src/providers/opencode-provider.ts
Model display formatting extracts base name and appends humanized tier suffixes (e.g., "(Free)").
Events types
libs/types/src/event.ts
Added event literals worktree:deleted and feature:migrated.
UI: OpenCode provider & model selector
apps/ui/src/components/views/board-view/shared/model-selector.tsx, apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx, apps/ui/src/lib/agent-context-parser.ts
Adds OpenCode provider support with dynamic model loading, normalization/deduplication, UI tab, parsing/formatting, and ModelSelector props extended (onModelSelect, testIdPrefix).
UI: Board sorting & createdAt flow
apps/ui/src/components/views/board-view.tsx, apps/ui/src/components/views/board-view/components/list-view/list-view.tsx, apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts, apps/ui/src/components/views/board-view/hooks/use-board-actions.ts
Introduces defaultSortNewestCardOnTop store field and sortNewestCardOnTop prop/hook option; dependency-aware newest-first sorting; features set createdAt on create; ListView and hooks accept/apply the flag.
UI: Plan approval badges & list-row
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
Adds "Plan ready for review" FileCheck badge when planSpec.status === 'generated'; list-row sorts fallback to createdAt or parses timestamp from feature id.
UI: Settings, store & sync
apps/ui/src/hooks/use-settings-migration.ts, apps/ui/src/hooks/use-settings-sync.ts, apps/ui/src/store/app-store.ts, apps/ui/src/store/types/state-types.ts, libs/types/src/settings.ts, apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
Adds defaultSortNewestCardOnTop and knownDynamicModelIds to settings/state/sync; new async store actions for OpenCode/dynamic model persistence; settings UI toggle added.
UI: Responsive refinements & Sheet
apps/ui/src/components/views/agent-view/components/agent-header.tsx, apps/ui/src/components/dialogs/board-background-modal.tsx, apps/ui/src/components/ui/sheet.tsx, apps/ui/src/components/views/board-view/hooks/use-board-background.ts
Responsive header/layout tweaks, safe-area-aware header padding, Sheet Close accepts data-slot and inline style, minor hook memoization refactor.
UI: Misc behavior & dialogs
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/plan-approval-dialog.tsx
Agent summary trigger made a button (opens dialog), verify flow supports async with spinner/disable-state, plan-approval dialog layout made responsive.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/UI
    participant Server as Server
    participant FL as FeatureLoader
    participant Events as EventEmitter

    Client->>Server: DELETE /worktree/:id
    activate Server
    Server->>FL: loadAllFeatures()
    activate FL
    FL-->>Server: features[]
    deactivate FL
    Server->>Server: filter features with branchName == deletedBranch
    loop per feature to migrate
      Server->>FL: update feature (clear branchName)
      activate FL
      FL-->>Server: success / error
      deactivate FL
      Server->>Events: emit feature:migrated (feature)
    end
    Server->>Events: emit worktree:deleted (worktree info)
    Server-->>Client: 200 + { featuresMovedToMain }
    deactivate Server
Loading
sequenceDiagram
    participant User as User
    participant UI as ModelSelector UI
    participant Store as App Store
    participant API as Server API

    User->>UI: choose OpenCode provider
    UI->>Store: request opencode models
    activate Store
    Store->>API: GET /opencode/models
    activate API
    API-->>Store: dynamic models
    deactivate API
    Store->>Store: sanitize, dedupe, filter by enabled/known IDs
    Store-->>UI: merged model list
    UI->>Store: setOpencodeDefaultModel(selectedModel)
    Store->>API: PATCH /settings (persist)
    API-->>Store: persisted
    deactivate Store
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Bug, Enhancement

Poem

🐇 I hopped through code and nudged a route,

Moved wayward features, cleaned the root.
OpenCode models, tidy and bright,
Newest cards leap into light.
A rabbit cheers — deploy takes flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% 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 'Fix orphaned features when deleting worktrees' directly and clearly summarizes the main change in the pull request, which addresses the core issue of preventing orphaned features when worktrees are deleted.

✏️ 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 introduces several significant improvements across the application, focusing on data integrity, user experience, and model management. It ensures that no features are lost when Git worktrees are deleted by migrating them to the main branch. Additionally, it refines the display of OpenCode AI models for better clarity and enhances the application's mobile responsiveness, making key interfaces more accessible on smaller screens. Finally, it adds visual cues for plan approvals and improves the backend handling and synchronization of dynamic OpenCode models.

Highlights

  • Orphaned Features Prevention: Features associated with a deleted Git worktree branch are now automatically moved to the main branch, preventing data loss and ensuring continuity.
  • Improved OpenCode Model Display: Model names from the OpenCode provider now correctly parse and display pricing tiers and preview suffixes (e.g., "Trinity Large Preview (Free)") for enhanced clarity.
  • Enhanced Mobile Responsiveness: The agent header and plan approval dialog have been optimized for better display and usability on mobile devices, improving the overall user experience.
  • Plan Approval Badges: New visual indicators have been added to Kanban cards and list views to highlight features with plans ready for review, making them easier to identify.
  • Dynamic OpenCode Model Management: The system now intelligently tracks and auto-enables newly discovered dynamic OpenCode models while respecting user-disabled models, with settings synchronized to the server.

🧠 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
  • apps/server/src/index.ts
    • Updated createWorktreeRoutes to include featureLoader dependency.
  • apps/server/src/providers/opencode-provider.ts
    • Enhanced formatModelDisplayName to extract nested model IDs and parse tier/pricing suffixes for better display.
  • apps/server/src/routes/worktree/index.ts
    • Imported FeatureLoader and passed it to the createDeleteHandler function.
  • apps/server/src/routes/worktree/routes/delete.ts
    • Modified createDeleteHandler to accept featureLoader.
    • Implemented logic to move features from a deleted branch to the main worktree.
    • Added featuresMovedToMain to the deletion response.
  • apps/ui/src/components/views/agent-view/components/agent-header.tsx
    • Adjusted padding, gaps, and badge width for mobile responsiveness.
    • Hid the current tool indicator on small screens.
    • Updated Clear button styling for mobile.
  • apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx
    • Imported FileCheck icon.
    • Added a showPlanApproval condition.
    • Rendered a new pulsing FileCheck badge for features with generated plans.
  • apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
    • Imported FileCheck icon.
    • Added logic to display a plan approval badge in the list view.
  • apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx
    • Made the dialog full-screen on mobile.
    • Adjusted max-height for plan content.
    • Made the textarea min-height responsive.
    • Updated footer button styling for mobile.
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
    • Imported OpenCodeIcon.
    • Integrated useOpencodeModels for dynamic data.
    • Added logic to filter and combine static and dynamic OpenCode models.
    • Included an OpenCode provider button.
    • Rendered the OpenCode model selection list.
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
    • Included enabledOpencodeModels in store selectors.
    • Filtered static OpenCode models based on enabled settings.
    • Refined the merging of static and dynamic OpenCode models.
  • apps/ui/src/hooks/use-settings-migration.ts
    • Added knownDynamicModelIds to the settings hydration process, including sanitization for "amazon-bedrock/" prefixes.
  • apps/ui/src/hooks/use-settings-sync.ts
    • Included knownDynamicModelIds in the list of settings fields to sync.
    • Included knownDynamicModelIds in the server settings refresh logic, with sanitization.
  • apps/ui/src/lib/agent-context-parser.ts
    • Added specific formatting rules for static OpenCode models.
    • Added a general rule for dynamic OpenCode models, including parsing nested IDs and stripping tier suffixes.
  • apps/ui/src/store/app-store.ts
    • Initialized knownDynamicModelIds in the initial state.
    • Updated OpenCode model management actions (setOpencodeDefaultModel, toggleOpencodeModel, setEnabledDynamicModelIds, toggleDynamicModel) to be asynchronous and sync with the server.
    • Implemented logic to auto-enable truly new dynamic OpenCode models while tracking all known ones.
  • apps/ui/src/store/types/state-types.ts
    • Added knownDynamicModelIds to the AppState interface.
    • Updated the return types of OpenCode model actions to Promise.
  • libs/types/src/settings.ts
    • Added knownDynamicModelIds property to the GlobalSettings interface.
    • Added knownDynamicModelIds to DEFAULT_GLOBAL_SETTINGS.
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 several valuable improvements. The backend fix to prevent orphaned features when deleting worktrees is well-implemented, and the logic to move features to the main branch is sound. I've suggested a small performance improvement to parallelize feature updates. The enhancements to OpenCode model name display and the frontend responsiveness for mobile are great additions that will improve the user experience. The new plan approval badge is also a nice touch for better visual feedback.

Comment on lines 145 to 149
for (const feature of affectedFeatures) {
await featureLoader.update(projectPath, feature.id, {
branchName: null,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The feature updates are currently performed sequentially in a for...of loop. For a branch with many associated features, this could be slow as each update waits for the previous one to complete. You can improve performance by running these updates in parallel using Promise.all.

            await Promise.all(
              affectedFeatures.map((feature) =>
                featureLoader.update(projectPath, feature.id, {
                  branchName: null,
                })
              )
            );

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

Caution

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

⚠️ Outside diff range comments (2)
apps/ui/src/components/views/agent-view/components/agent-header.tsx (1)

61-70: ⚠️ Potential issue | 🟠 Major

Add an accessible name for the mobile icon-only Clear button.

On Line 69, the label is hidden on small screens, so this becomes icon-only with no accessible name. Add aria-label (or screen-reader text) on the button.

Suggested fix
         {currentSessionId && messagesCount > 0 && (
           <Button
             variant="ghost"
             size="sm"
             onClick={onClearChat}
             disabled={isProcessing}
+            aria-label="Clear chat"
             className="text-muted-foreground hover:text-foreground h-8 w-8 p-0 sm:w-auto sm:px-3"
           >
             <Trash2 className="w-4 h-4 sm:mr-2" />
-            <span className="hidden sm:inline">Clear</span>
+            <span className="sr-only sm:not-sr-only sm:inline">Clear</span>
           </Button>
         )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/agent-view/components/agent-header.tsx` around
lines 61 - 70, The Clear button becomes icon-only on small screens and lacks an
accessible name; update the Button component (the one with props onClearChat,
disabled={isProcessing}, and child <Trash2 ... /> in agent-header.tsx) to
include an accessible label by adding an aria-label (e.g., aria-label="Clear
chat") or a visually hidden screen-reader-only span so the icon-only button is
properly announced to assistive technologies.
apps/ui/src/hooks/use-settings-migration.ts (1)

882-965: ⚠️ Potential issue | 🟠 Major

Missing persistence of knownDynamicModelIds in sync function.

The knownDynamicModelIds field is hydrated from settings (line 786) but is not included in buildSettingsUpdateFromStore(). This means the field won't be persisted when settings are synced to the server, defeating its purpose of tracking "all dynamic model IDs ever seen" across sessions.

🐛 Proposed fix to include knownDynamicModelIds in persistence
     enabledDynamicModelIds: state.enabledDynamicModelIds,
+    knownDynamicModelIds: state.knownDynamicModelIds,
     disabledProviders: state.disabledProviders,

Add after line 923 where enabledDynamicModelIds is already included.

🤖 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 882 - 965,
buildSettingsUpdateFromStore is not persisting knownDynamicModelIds so that
field (which is hydrated from settings) won’t be synced; update
buildSettingsUpdateFromStore to include knownDynamicModelIds in the returned
object (place it next to enabledDynamicModelIds) so the store’s
knownDynamicModelIds is persisted to the server when settings are synced.
🧹 Nitpick comments (2)
apps/ui/src/store/types/state-types.ts (1)

621-625: Action signatures updated to async for persistence.

The OpenCode-related actions now return Promise<void> to support asynchronous persistence. Note that this creates an API inconsistency with similar actions for other providers (e.g., setCursorDefaultModel, toggleCursorModel, setCodexDefaultModel) which remain synchronous.

If this is intentional (only OpenCode dynamic models require persistence), consider adding a brief comment explaining why these specific actions are async. If all provider model actions should eventually be async for persistence, consider tracking that as a follow-up task.

🤖 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 621 - 625, The OpenCode
action signatures (setOpencodeDefaultModel, toggleOpencodeModel,
setDynamicOpencodeModels, setEnabledDynamicModelIds, toggleDynamicModel) were
changed to return Promise<void> for async persistence while other provider
actions remain synchronous; either make them consistent or document the intent.
Fix by either (A) updating the other provider action types (e.g.,
setCursorDefaultModel, toggleCursorModel, setCodexDefaultModel) to return
Promise<void> as well to standardize async persistence across providers, or (B)
add a short inline comment above the OpenCode action declarations explaining
these specific actions are async due to persistence requirements and file a
follow-up TODO to harmonize provider APIs; reference the exact symbols above
when applying the change.
apps/ui/src/store/app-store.ts (1)

2919-2924: Deduplicate merged dynamic model IDs before storing.

updatedEnabledIds (Line 2921) is built via array concat; adding a uniqueness guard avoids duplicate IDs persisting in settings payloads.

Proposed refactor
-        const updatedEnabledIds =
-          trulyNewModelIds.length > 0
-            ? [...currentEnabledIds, ...trulyNewModelIds]
-            : currentEnabledIds;
+        const updatedEnabledIds =
+          trulyNewModelIds.length > 0
+            ? Array.from(new Set([...currentEnabledIds, ...trulyNewModelIds]))
+            : currentEnabledIds;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/store/app-store.ts` around lines 2919 - 2924, The merged
enabled-model IDs built in updatedEnabledIds currently just concatenates
currentEnabledIds and trulyNewModelIds which can introduce duplicates; change
its construction to deduplicate (e.g., create from a Set) so updatedEnabledIds
becomes the unique union of currentEnabledIds and trulyNewModelIds (preserving
order if needed), similar to how updatedKnownIds uses [...new
Set([...currentKnownIds, ...allFetchedIds])]; update the code that produces
updatedEnabledIds to use a Set-based dedupe (referencing updatedEnabledIds,
currentEnabledIds, trulyNewModelIds) before storing the settings.
🤖 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/index.ts`:
- Line 74: Replace the relative import of FeatureLoader with the shared package
import as per guidelines: change the import that currently reads import type {
FeatureLoader } from '../../services/feature-loader.js' to import type {
FeatureLoader } from '@automaker/feature-loader' (or the canonical `@automaker`
package that exports FeatureLoader). Ensure you only change the import statement
and keep the rest of the code using the FeatureLoader type unchanged.
- Around line 99-103: The route wiring for the delete endpoint must forward the
shared EventEmitter so the handler can emit websocket events: update the
router.post call that currently passes only featureLoader to call
createDeleteHandler with the EventEmitter instance (use createEventEmitter()
from lib/events.ts per guideline) alongside featureLoader; ensure you
import/create the emitter in this module and pass that emitter into
createDeleteHandler (which should match the updated signature
createDeleteHandler(events: EventEmitter, featureLoader?: FeatureLoader)),
leaving validatePathParams('projectPath','worktreePath') unchanged.

In `@apps/server/src/routes/worktree/routes/delete.ts`:
- Line 13: Replace the relative import of the FeatureLoader type with the shared
`@automaker` package import: update the import statement that currently reads
"import type { FeatureLoader } from '../../../services/feature-loader.js';" to
import the same symbol from the appropriate `@automaker` package (e.g. "import
type { FeatureLoader } from '@automaker/feature-loader'"), ensuring the
FeatureLoader type is referenced from the shared package rather than a relative
path.
- Around line 141-171: The current loop calling featureLoader.update in the try
block aborts on the first failure and leaves featuresMovedToMain inaccurate;
change the migration to iterate over affectedFeatures and, for each feature,
call featureLoader.update inside its own try/catch so a failing update logs a
warning (include feature.id and branchName) but does not stop the loop, only
increment featuresMovedToMain when an update succeeds, and retain the outer
try/catch for getAll failures; update logging to reflect per-feature failures if
any.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx`:
- Around line 292-294: The tooltip text in TooltipContent currently uses
mobile-specific wording ("tap to approve"); update the copy in the
TooltipContent instance inside card-badges.tsx to a neutral or action-agnostic
phrase such as "click or tap to approve" or "approve this plan" so it reads
correctly on both desktop and mobile; locate the TooltipContent element
(className="text-xs max-w-[250px]") and replace the inner <p> string
accordingly.

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 143-147: When switching to the 'opencode' provider in the provider
change logic, the fallback order incorrectly prefers allOpencodeModels[0] over
the configured opencodeDefaultModel; update the selection to prefer
opencodeDefaultModel first by setting defaultModelId to opencodeDefaultModel ||
allOpencodeModels[0]?.id || 'opencode-big-pickle' and then call
onModelSelect(defaultModelId) (references: provider, selectedProvider,
allOpencodeModels, opencodeDefaultModel, onModelSelect).

In `@apps/ui/src/lib/agent-context-parser.ts`:
- Around line 109-116: The code currently strips tier suffixes from lastSegment
(e.g., ":free", ":extended") and never re-attaches them, causing loss of tier
context; update the logic around lastSegment (the colonIdx slice and the final
return) to detect and save the suffix (e.g., using
lastSegment.match(/:(free|extended|beta|preview)$/i)), remove it only from the
base name for cleaning, then re-append the original suffix (formatted or left
as-is) to the cleaned display name before returning so the tier remains visible.

---

Outside diff comments:
In `@apps/ui/src/components/views/agent-view/components/agent-header.tsx`:
- Around line 61-70: The Clear button becomes icon-only on small screens and
lacks an accessible name; update the Button component (the one with props
onClearChat, disabled={isProcessing}, and child <Trash2 ... /> in
agent-header.tsx) to include an accessible label by adding an aria-label (e.g.,
aria-label="Clear chat") or a visually hidden screen-reader-only span so the
icon-only button is properly announced to assistive technologies.

In `@apps/ui/src/hooks/use-settings-migration.ts`:
- Around line 882-965: buildSettingsUpdateFromStore is not persisting
knownDynamicModelIds so that field (which is hydrated from settings) won’t be
synced; update buildSettingsUpdateFromStore to include knownDynamicModelIds in
the returned object (place it next to enabledDynamicModelIds) so the store’s
knownDynamicModelIds is persisted to the server when settings are synced.

---

Nitpick comments:
In `@apps/ui/src/store/app-store.ts`:
- Around line 2919-2924: The merged enabled-model IDs built in updatedEnabledIds
currently just concatenates currentEnabledIds and trulyNewModelIds which can
introduce duplicates; change its construction to deduplicate (e.g., create from
a Set) so updatedEnabledIds becomes the unique union of currentEnabledIds and
trulyNewModelIds (preserving order if needed), similar to how updatedKnownIds
uses [...new Set([...currentKnownIds, ...allFetchedIds])]; update the code that
produces updatedEnabledIds to use a Set-based dedupe (referencing
updatedEnabledIds, currentEnabledIds, trulyNewModelIds) before storing the
settings.

In `@apps/ui/src/store/types/state-types.ts`:
- Around line 621-625: The OpenCode action signatures (setOpencodeDefaultModel,
toggleOpencodeModel, setDynamicOpencodeModels, setEnabledDynamicModelIds,
toggleDynamicModel) were changed to return Promise<void> for async persistence
while other provider actions remain synchronous; either make them consistent or
document the intent. Fix by either (A) updating the other provider action types
(e.g., setCursorDefaultModel, toggleCursorModel, setCodexDefaultModel) to return
Promise<void> as well to standardize async persistence across providers, or (B)
add a short inline comment above the OpenCode action declarations explaining
these specific actions are async due to persistence requirements and file a
follow-up TODO to harmonize provider APIs; reference the exact symbols above
when applying the change.

ℹ️ 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 1b5a6c0.

📒 Files selected for processing (16)
  • 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/ui/src/components/views/agent-view/components/agent-header.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/dialogs/plan-approval-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.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/store/app-store.ts
  • apps/ui/src/store/types/state-types.ts
  • libs/types/src/settings.ts

import { createSyncHandler } from './routes/sync.js';
import { createUpdatePRNumberHandler } from './routes/update-pr-number.js';
import type { SettingsService } from '../../services/settings-service.js';
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.

⚠️ Potential issue | 🟠 Major

Use a shared @automaker/* import for FeatureLoader.

This newly added relative import conflicts with the TS/JS import rule.

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/index.ts` at line 74, Replace the relative
import of FeatureLoader with the shared package import as per guidelines: change
the import that currently reads import type { FeatureLoader } from
'../../services/feature-loader.js' to import type { FeatureLoader } from
'@automaker/feature-loader' (or the canonical `@automaker` package that exports
FeatureLoader). Ensure you only change the import statement and keep the rest of
the code using the FeatureLoader type unchanged.

Comment on lines 99 to 103
router.post(
'/delete',
validatePathParams('projectPath', 'worktreePath'),
createDeleteHandler(featureLoader)
);
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

Forward events to delete handler so this operation can emit WebSocket events.

The delete flow now mutates feature state, but this route wiring only passes featureLoader, so the handler cannot emit operation events through the shared emitter.

💡 Suggested wiring update
   router.post(
     '/delete',
     validatePathParams('projectPath', 'worktreePath'),
-    createDeleteHandler(featureLoader)
+    createDeleteHandler(events, featureLoader)
   );
// Companion update in apps/server/src/routes/worktree/routes/delete.ts
export function createDeleteHandler(events: EventEmitter, featureLoader?: FeatureLoader) { ... }

As per coding guidelines apps/server/src/**/*.{ts,js}: Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to the frontend via WebSocket.

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

In `@apps/server/src/routes/worktree/index.ts` around lines 99 - 103, The route
wiring for the delete endpoint must forward the shared EventEmitter so the
handler can emit websocket events: update the router.post call that currently
passes only featureLoader to call createDeleteHandler with the EventEmitter
instance (use createEventEmitter() from lib/events.ts per guideline) alongside
featureLoader; ensure you import/create the emitter in this module and pass that
emitter into createDeleteHandler (which should match the updated signature
createDeleteHandler(events: EventEmitter, featureLoader?: FeatureLoader)),
leaving validatePathParams('projectPath','worktreePath') unchanged.

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.

⚠️ Potential issue | 🟠 Major

Use a shared @automaker/* import for FeatureLoader.

This new relative service import violates the repository import rule for TS/JS files.

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, Replace the
relative import of the FeatureLoader type with the shared `@automaker` package
import: update the import statement that currently reads "import type {
FeatureLoader } from '../../../services/feature-loader.js';" to import the same
symbol from the appropriate `@automaker` package (e.g. "import type {
FeatureLoader } from '@automaker/feature-loader'"), ensuring the FeatureLoader
type is referenced from the shared package rather than a relative path.

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

♻️ Duplicate comments (2)
apps/server/src/routes/worktree/index.ts (1)

74-74: ⚠️ Potential issue | 🟠 Major

Use a shared @automaker/* import for FeatureLoader.

This still uses a relative service path that violates the repository import rule.

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/index.ts` at line 74, Replace the relative
import of FeatureLoader with the shared package export: locate the import
statement importing FeatureLoader (the line with "import type { FeatureLoader }
from '../../services/feature-loader.js'") and change it to import the type from
the shared package (e.g. `@automaker/feature-loader`) so the module follows the
`@automaker/`* import rule; ensure the imported symbol remains FeatureLoader and
update any path references or build configs if necessary.
apps/server/src/routes/worktree/routes/delete.ts (1)

13-13: ⚠️ Potential issue | 🟠 Major

Use the shared package import for FeatureLoader instead of a relative service path.

This import path violates the repository TS/JS import rule and was previously flagged.

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 file
imports the type FeatureLoader via a relative path; replace that relative import
with the canonical shared package import (e.g. import type { FeatureLoader }
from '@automaker/feature-loader' or the appropriate `@automaker/`* package that
exports FeatureLoader) so it follows the repository TS/JS import rule; update
the import statement that references FeatureLoader in the delete route module to
use the package export instead of '../../../services/feature-loader.js'.
🧹 Nitpick comments (1)
apps/ui/src/store/types/state-types.ts (1)

217-221: Update the dynamic-model persistence comment to match current behavior.

Line 217 says dynamic models are “session-only (not persisted)”, but the new action contracts and store logic now persist enabled/known dynamic model IDs. This comment is stale and misleading.

Proposed fix
-  // Dynamic models are session-only (not persisted) because they're discovered at runtime
-  // from `opencode models` CLI and depend on current provider authentication state
+  // Dynamic models are discovered at runtime from `opencode models` CLI.
+  // Enabled/known dynamic model IDs are persisted to preserve user choices
+  // across sessions while still allowing runtime discovery.
🤖 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 217 - 221, Update the
stale comment above the dynamic model fields to reflect that
enabledDynamicModelIds and knownDynamicModelIds are now persisted (no longer
session-only) instead of saying dynamic models are "session-only (not
persisted)"; specifically mention that dynamicOpencodeModels are discovered at
runtime while enabledDynamicModelIds and knownDynamicModelIds are persisted via
the new action contracts/store logic to track user selections across sessions,
and remove or reword the "not persisted" phrasing accordingly.
🤖 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`:
- Around line 19-20: The createDeleteHandler currently receives events
(EventEmitter) but never emits events after deletion or migration; update the
handler to emit appropriate events using the injected events emitter: after the
worktree deletion succeeds in createDeleteHandler emit a "worktree:deleted"
event with identifying payload (e.g., worktreeId and repo/project context) and
after any feature migration logic emits a "feature:migrated" (or
"feature:migration") event with the feature id, status and any migrated-to
worktree id; ensure these emits occur where delete/migrate operations complete
(inside the async handler code paths that currently perform deletion/migration)
so frontend subscribers via createEventEmitter() will receive realtime updates.

In `@apps/ui/src/components/views/board-view/components/list-view/list-row.tsx`:
- Line 127: Replace the device-specific tooltip text on the badge in
list-row.tsx: locate the Badge (or status badge) that sets tooltip: 'Plan ready
for review - tap to approve' and change it to neutral wording for all platforms
(e.g., 'Plan ready for review and approval' or 'Ready for review and approval')
so it no longer references touch-only actions; update the tooltip prop value on
the Badge/component rendering the status.

In `@apps/ui/src/components/views/board-view/components/list-view/list-view.tsx`:
- Around line 245-248: When sortNewestCardOnTop is true the UI uses
effectiveSortConfig ({ column: 'createdAt', direction: 'desc' }) for row
ordering but ListHeader still receives the original sortConfig, causing the
header indicator to diverge; update the component usage so ListHeader receives
effectiveSortConfig (or a derived displaySort equal to effectiveSortConfig)
instead of sortConfig and ensure any other places that render sort state (e.g.,
where sortConfig is passed down or used) also use effectiveSortConfig so the
header and rows remain consistent.

In `@apps/ui/src/components/views/board-view/hooks/use-board-background.ts`:
- Around line 14-22: backgroundSettings currently forces sortNewestCardOnTop to
the global default (defaultSortNewestCardOnTop) and thereby ignores any
persisted per-project value; either respect the per-project override by reading
perProjectSettings.sortNewestCardOnTop with a fallback to
defaultSortNewestCardOnTop in the useMemo that defines backgroundSettings (use
currentProject, boardBackgroundByProject and defaultBackgroundSettings to locate
perProjectSettings), or remove the per-project API setSortNewestCardOnTop in
use-board-background-settings.ts if you intend it to be global-only; update
backgroundSettings or the setter accordingly so saved values are actually
applied.

In `@apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts`:
- Around line 300-309: The sorting loop controlled by sortNewestCardOnTop is
clobbering the dependency-aware ordering previously applied to the backlog
column; update the loop in use-board-column-features so that when
sortNewestCardOnTop is true you skip sorting the backlog column (i.e., add a
guard like if (columnId === <backlogColumnKey>) continue) so the backlog column
retains the dependency/blocking order established earlier (use the same backlog
column key used in the dependency-ordering code), or alternatively add a clear
comment/documentation near sortNewestCardOnTop explaining that newest-first
intentionally overrides backlog dependency ordering.

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 62-67: Effect currently refires whenever opencodeModelsLoading
toggles to false while dynamicOpencodeModelsList is empty, causing repeated
fetch retries; modify the effect to perform the fetch only once (or until an
explicit reset) by adding a local flag/ref (e.g., attemptedOpencodeFetchRef or
opencodeFetchTried state) and set it true immediately when calling
fetchOpencodeModels; update the condition in the useEffect (which references
opencodeModelsLoading, dynamicOpencodeModelsList.length, fetchOpencodeModels) to
require that this "not yet attempted" flag is false before calling
fetchOpencodeModels, so subsequent transitions to loading=false won’t retrigger
fetches unless the flag is cleared intentionally.

In `@apps/ui/src/lib/agent-context-parser.ts`:
- Around line 109-118: The code currently appends the raw tier token
(tierSuffix) to the cleaned model name, exposing ID syntax; change this to
convert the captured tier token (use tierMatch[1]) into a human-friendly label
and append it in parentheses (e.g., " (Free)", " (Preview)", " (Extended)", "
(Beta)") with proper capitalization and spacing. Locate the logic around
tierMatch / tierSuffix / cleanedName in agent-context-parser.ts and replace the
raw suffix re-append with a small mapping or capitalize the captured group and
return `${cleanedName} (${capitalizedTier})` when tierMatch exists.

In `@apps/ui/src/store/app-store.ts`:
- Around line 1372-1380: toggleOpencodeModel currently appends model IDs to
enabledOpencodeModels without checking for existing entries, causing duplicates
to be saved; change the add-path in toggleOpencodeModel to only add when the
model is not already present (e.g., check
state.enabledOpencodeModels.includes(model) before pushing or build the new
array as [...new Set([...state.enabledOpencodeModels, model])]), and ensure the
same deduplicated array is what you pass to
getHttpApiClient().settings.updateGlobal and any other code paths that modify
enabledOpencodeModels so persisted settings never contain duplicates.
- Around line 1836-1843: The current setAllProjectsSortNewestCardOnTop only
updates existing keys in state.boardBackgroundByProject; change it to build
updated = { ...state.boardBackgroundByProject } and then iterate over the union
of project paths from state.boardBackgroundByProject and the global project list
(e.g. state.projects or state.projectsByPath — whichever holds all projects) and
set updated[projectPath] = { ...(updated[projectPath] ??
defaultBoardBackgroundSettings), sortNewestCardOnTop: enabled } so projects
without prior settings get an entry; keep references to
setAllProjectsSortNewestCardOnTop and state.boardBackgroundByProject when making
the change.

---

Duplicate comments:
In `@apps/server/src/routes/worktree/index.ts`:
- Line 74: Replace the relative import of FeatureLoader with the shared package
export: locate the import statement importing FeatureLoader (the line with
"import type { FeatureLoader } from '../../services/feature-loader.js'") and
change it to import the type from the shared package (e.g.
`@automaker/feature-loader`) so the module follows the `@automaker/`* import rule;
ensure the imported symbol remains FeatureLoader and update any path references
or build configs if necessary.

In `@apps/server/src/routes/worktree/routes/delete.ts`:
- Line 13: The file imports the type FeatureLoader via a relative path; replace
that relative import with the canonical shared package import (e.g. import type
{ FeatureLoader } from '@automaker/feature-loader' or the appropriate
`@automaker/`* package that exports FeatureLoader) so it follows the repository
TS/JS import rule; update the import statement that references FeatureLoader in
the delete route module to use the package export instead of
'../../../services/feature-loader.js'.

---

Nitpick comments:
In `@apps/ui/src/store/types/state-types.ts`:
- Around line 217-221: Update the stale comment above the dynamic model fields
to reflect that enabledDynamicModelIds and knownDynamicModelIds are now
persisted (no longer session-only) instead of saying dynamic models are
"session-only (not persisted)"; specifically mention that dynamicOpencodeModels
are discovered at runtime while enabledDynamicModelIds and knownDynamicModelIds
are persisted via the new action contracts/store logic to track user selections
across sessions, and remove or reword the "not persisted" phrasing accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b5a6c0 and a6d7e6a.

📒 Files selected for processing (25)
  • 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/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/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/hooks/use-board-background-settings.ts
  • 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/store/app-store.ts
  • apps/ui/src/store/defaults/background-settings.ts
  • apps/ui/src/store/types/state-types.ts
  • apps/ui/src/store/types/ui-types.ts
  • libs/types/src/feature.ts
  • libs/types/src/settings.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/types/src/settings.ts

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

♻️ Duplicate comments (2)
apps/server/src/routes/worktree/routes/delete.ts (1)

13-13: ⚠️ Potential issue | 🟠 Major

Replace the relative FeatureLoader import with the shared package import.

This reintroduces the old relative service-path pattern in a TS file.

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, Replace the
relative import of FeatureLoader with the shared package import; specifically,
change the import line that references '../../../services/feature-loader.js' to
import type { FeatureLoader } from '@automaker/feature-loader' (or the
org-scoped shared package that exports FeatureLoader) so the module uses the
canonical `@automaker` package instead of the old relative service path.
apps/ui/src/store/app-store.ts (1)

1386-1404: ⚠️ Potential issue | 🟡 Minor

Deduplicate dynamic model IDs before writing local/server state.

setEnabledDynamicModelIds and toggleDynamicModel can still persist duplicate IDs.

💡 Suggested fix
   setEnabledDynamicModelIds: async (ids) => {
-    set({ enabledDynamicModelIds: ids });
+    const dedupedIds = [...new Set(ids)];
+    set({ enabledDynamicModelIds: dedupedIds });
     try {
       const httpApi = getHttpApiClient();
-      await httpApi.settings.updateGlobal({ enabledDynamicModelIds: ids });
+      await httpApi.settings.updateGlobal({ enabledDynamicModelIds: dedupedIds });
     } catch (error) {
       logger.error('Failed to sync enabledDynamicModelIds:', error);
     }
   },
   toggleDynamicModel: async (modelId, enabled) => {
     set((state) => ({
       enabledDynamicModelIds: enabled
-        ? [...state.enabledDynamicModelIds, modelId]
+        ? [...new Set([...state.enabledDynamicModelIds, modelId])]
         : state.enabledDynamicModelIds.filter((id) => id !== modelId),
     }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/store/app-store.ts` around lines 1386 - 1404, Both
setEnabledDynamicModelIds and toggleDynamicModel can persist duplicate model
IDs; ensure you dedupe before updating local state and before sending to the
server. In setEnabledDynamicModelIds, replace direct set({
enabledDynamicModelIds: ids }) with a deduped list (e.g., Array.from(new
Set(ids))) and pass that deduped list to httpApi.settings.updateGlobal; in
toggleDynamicModel, when adding modelId compute the new list by merging current
get().enabledDynamicModelIds with modelId then dedupe (or only add if not
present) before calling set(...) and before await
httpApi.settings.updateGlobal(...). Use the existing functions get(), set(),
setEnabledDynamicModelIds/toggleDynamicModel and getHttpApiClient() to locate
the changes.
🧹 Nitpick comments (1)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

4-4: Prefer barrel export for intra-app UI component imports.

Line 4 adds another direct UI-component path import. In this app area, we should prefer the local components barrel export for better refactor stability.

Based on learnings: In the apps/ui codebase, when importing UI components within the same app, prefer barrel exports from ../components (i.e., import from the components index barrel) rather than direct path imports.

🤖 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` at line 4,
Replace the direct UI-component path import for AnthropicIcon, CursorIcon,
OpenAIIcon, and OpenCodeIcon with the apps/ui local components barrel export:
update the import that currently pulls those symbols from
'@/components/ui/provider-icon' to import the same named exports from the
app-level components index (the ../components barrel) so intra-app imports use
the centralized barrel for better refactor stability.
🤖 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/shared/model-selector.tsx`:
- Around line 66-75: The useEffect may call fetchOpencodeModels() even when the
OpenCode provider is disabled; update the effect that currently checks
opencodeModelsLoading, dynamicOpencodeModelsList.length and
opencodeFetchTriedRef to also verify the provider-enabled flag before triggering
fetchOpencodeModels() — e.g., add a guard such as opencodeProviderEnabled (or
the existing isOpenCodeEnabled flag) to the condition and to the dependency
array so fetchOpencodeModels() only runs when the provider is enabled.
- Around line 43-44: Destructure the error from useOpencodeModels (e.g., const {
data: dynamicOpencodeModelsList = [], isLoading: dynamicOpencodeLoading, error:
dynamicOpencodeError } = useOpencodeModels()) and add an error render branch in
the OpenCode models section of model-selector.tsx that shows when
dynamicOpencodeError is truthy and dynamicOpencodeLoading is false; follow the
same pattern used by the Codex error handling block (render a user-facing error
message plus a retry action or a link to Settings) so users see the failure and
can retry or navigate to settings.

---

Duplicate comments:
In `@apps/server/src/routes/worktree/routes/delete.ts`:
- Line 13: Replace the relative import of FeatureLoader with the shared package
import; specifically, change the import line that references
'../../../services/feature-loader.js' to import type { FeatureLoader } from
'@automaker/feature-loader' (or the org-scoped shared package that exports
FeatureLoader) so the module uses the canonical `@automaker` package instead of
the old relative service path.

In `@apps/ui/src/store/app-store.ts`:
- Around line 1386-1404: Both setEnabledDynamicModelIds and toggleDynamicModel
can persist duplicate model IDs; ensure you dedupe before updating local state
and before sending to the server. In setEnabledDynamicModelIds, replace direct
set({ enabledDynamicModelIds: ids }) with a deduped list (e.g., Array.from(new
Set(ids))) and pass that deduped list to httpApi.settings.updateGlobal; in
toggleDynamicModel, when adding modelId compute the new list by merging current
get().enabledDynamicModelIds with modelId then dedupe (or only add if not
present) before calling set(...) and before await
httpApi.settings.updateGlobal(...). Use the existing functions get(), set(),
setEnabledDynamicModelIds/toggleDynamicModel and getHttpApiClient() to locate
the changes.

---

Nitpick comments:
In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Line 4: Replace the direct UI-component path import for AnthropicIcon,
CursorIcon, OpenAIIcon, and OpenCodeIcon with the apps/ui local components
barrel export: update the import that currently pulls those symbols from
'@/components/ui/provider-icon' to import the same named exports from the
app-level components index (the ../components barrel) so intra-app imports use
the centralized barrel for better refactor stability.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6d7e6a and a77202f.

📒 Files selected for processing (9)
  • apps/server/src/routes/worktree/routes/delete.ts
  • apps/ui/src/components/views/board-view.tsx
  • 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/lib/agent-context-parser.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/store/types/state-types.ts
  • libs/types/src/event.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/ui/src/lib/agent-context-parser.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-background.ts

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.

🧹 Nitpick comments (1)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

489-495: Model list may render while loading is still in progress.

The loading state (lines 489-495) only displays when allOpencodeModels.length === 0. However, the model list (line 525) renders whenever allOpencodeModels.length > 0, regardless of loading state. If static models are enabled, users might see an incomplete list (static only) while dynamic models are still loading.

Consider guarding the model list to avoid showing a partial list during loading:

Proposed fix
           {/* Model list */}
-          {allOpencodeModels.length > 0 && (
+          {!opencodeModelsLoading && !dynamicOpencodeLoading && allOpencodeModels.length > 0 && (
             <div className="flex flex-col gap-2">

Also applies to: 524-525

🤖 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 489 - 495, The model list is rendered even while dynamic or static model
loading flags are true, causing partial lists; update the JSX so the model list
is only rendered when loading is finished by changing its condition to require
!opencodeModelsLoading && !dynamicOpencodeLoading && allOpencodeModels.length >
0 (apply the same guard where the list is currently shown), referencing the
opencodeModelsLoading, dynamicOpencodeLoading and allOpencodeModels variables in
model-selector.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 489-495: The model list is rendered even while dynamic or static
model loading flags are true, causing partial lists; update the JSX so the model
list is only rendered when loading is finished by changing its condition to
require !opencodeModelsLoading && !dynamicOpencodeLoading &&
allOpencodeModels.length > 0 (apply the same guard where the list is currently
shown), referencing the opencodeModelsLoading, dynamicOpencodeLoading and
allOpencodeModels variables in model-selector.tsx.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a77202f and dcb110d.

📒 Files selected for processing (2)
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/store/app-store.ts

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

♻️ Duplicate comments (1)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

161-165: ⚠️ Potential issue | 🟠 Major

Validate OpenCode default model before selecting it.

At Line 164, opencodeDefaultModel is used even if it is no longer enabled/available in allOpencodeModels. That can select a model not present in the rendered list.

💡 Proposed fix
-      const defaultModelId =
-        opencodeDefaultModel || allOpencodeModels[0]?.id || 'opencode-big-pickle';
+      const defaultModelId = allOpencodeModels.some((m) => m.id === opencodeDefaultModel)
+        ? opencodeDefaultModel
+        : allOpencodeModels[0]?.id || 'opencode-big-pickle';
       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 161 - 165, When switching to provider === 'opencode' in the branch that
handles selectedProvider !== 'opencode', validate that opencodeDefaultModel is
actually present in allOpencodeModels (e.g., check ids via
allOpencodeModels.some(m => m.id === opencodeDefaultModel)) before calling
onModelSelect; if it’s not present fall back to the first available model id
(allOpencodeModels[0]?.id) or the hardcoded 'opencode-big-pickle' default.
Update the selection logic around opencodeDefaultModel / allOpencodeModels /
onModelSelect to perform this presence check and only select
opencodeDefaultModel when it is enabled.
🧹 Nitpick comments (1)
apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx (1)

484-496: Consider adding type="button" for defensive coding.

The button element lacks an explicit type="button" attribute. While this matches the existing expand button pattern (lines 497-509), buttons without an explicit type default to type="submit", which could cause unintended form submissions if this component is ever rendered within a form context.

🛡️ Suggested defensive fix
 <button
   onClick={(e) => {
     e.stopPropagation();
     setIsSummaryDialogOpen(true);
   }}
   onPointerDown={(e) => e.stopPropagation()}
   onMouseDown={(e) => e.stopPropagation()}
   className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0 hover:opacity-80 transition-opacity cursor-pointer"
   title="View full summary"
+  type="button"
 >

Consider applying the same to the expand button at line 497 for consistency.

🤖 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/kanban-card/agent-info-panel.tsx`
around lines 484 - 496, The Summary button handler currently uses a plain
<button> (rendering the Sparkles icon and calling setIsSummaryDialogOpen) but
lacks an explicit type, so add type="button" to that button element to prevent
unintended form submissions; also apply the same type="button" defensive change
to the adjacent expand button (the other button that uses onClick to toggle
expansion) to keep behavior consistent.
🤖 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/components/selection-action-bar.tsx`:
- Around line 56-57: The handler handleConfirmVerify currently returns early
when onVerify is undefined, leaving the confirmation dialog open; update it to
either call the dialog close routine (e.g., invoke the existing
close/closeDialog/onClose function) or programmatically disable/hide the Confirm
action when onVerify is not provided. Specifically, change handleConfirmVerify
to check if (!onVerify) then close the dialog (or call the component's close
method) instead of returning no-op, and also update the Confirm button/element
to be disabled when onVerify is falsy so the user cannot trigger the dialog
without a handler.

In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 498-506: The retry button is calling the Zustand action
fetchOpencodeModels() while the OpenCode error state comes from the React Query
hook useOpencodeModels(); replace the store call with the hook's refetch:
destructure refetch from useOpencodeModels() (alongside data/isLoading/error)
and invoke refetch() in the button onClick instead of fetchOpencodeModels();
alternatively, if you must keep the store action, make fetchOpencodeModels()
call queryClient.invalidateQueries({ queryKey: queryKeys.models.opencode() })
after a successful fetch (as done in opencode-settings-tab.tsx) so the React
Query error state (dynamicOpencodeError/dynamicOpencodeLoading) is properly
refreshed.

---

Duplicate comments:
In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 161-165: When switching to provider === 'opencode' in the branch
that handles selectedProvider !== 'opencode', validate that opencodeDefaultModel
is actually present in allOpencodeModels (e.g., check ids via
allOpencodeModels.some(m => m.id === opencodeDefaultModel)) before calling
onModelSelect; if it’s not present fall back to the first available model id
(allOpencodeModels[0]?.id) or the hardcoded 'opencode-big-pickle' default.
Update the selection logic around opencodeDefaultModel / allOpencodeModels /
onModelSelect to perform this presence check and only select
opencodeDefaultModel when it is enabled.

---

Nitpick comments:
In
`@apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx`:
- Around line 484-496: The Summary button handler currently uses a plain
<button> (rendering the Sparkles icon and calling setIsSummaryDialogOpen) but
lacks an explicit type, so add type="button" to that button element to prevent
unintended form submissions; also apply the same type="button" defensive change
to the adjacent expand button (the other button that uses onClick to toggle
expansion) to keep behavior consistent.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcb110d and 86e282f.

📒 Files selected for processing (3)
  • 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/shared/model-selector.tsx

@gsxdsm
Copy link
Collaborator Author

gsxdsm commented Feb 28, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 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: 2

♻️ Duplicate comments (1)
apps/ui/src/components/views/board-view/components/selection-action-bar.tsx (1)

121-126: ⚠️ Potential issue | 🟡 Minor

Disable the Verify launcher when no handler is provided.

onVerify is optional, but the launcher button can still open the dialog when a handler is absent. Align launcher behavior with confirm button disablement to avoid dead-end UX.

💡 Suggested patch
             <Button
               variant="default"
               size="sm"
               onClick={handleVerifyClick}
-              disabled={selectedCount === 0}
+              disabled={selectedCount === 0 || !onVerify || isVerifying}
               className="h-8 bg-green-600 hover:bg-green-700 disabled:opacity-50"
               data-testid="selection-verify-button"
             >
🤖 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 121 - 126, The Verify launcher currently enables when there are
selections even if no onVerify handler exists; update the Button in
selection-action-bar (the Button with onClick={handleVerifyClick}) to be
disabled when either selectedCount === 0 or onVerify is falsy (e.g.,
disabled={selectedCount === 0 || !onVerify}) and also add a defensive guard
inside handleVerifyClick to return early if onVerify is not provided before
attempting to open the dialog, ensuring the UI never opens a dead-end dialog
when no handler is supplied.
🤖 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/shared/model-selector.tsx`:
- Around line 72-80: The effect currently calls fetchOpencodeModels() when
isOpencodeEnabled && !opencodeModelsLoading && dynamicOpencodeModelsList.length
=== 0 && !opencodeFetchTriedRef.current, but it omits the dynamicOpencodeLoading
flag from the guard and can fire while the React Query hook is still in-flight;
update the condition(s) around opencodeFetchTriedRef.current and
fetchOpencodeModels() to also require !dynamicOpencodeLoading so you never
trigger a second fetch while the useOpencodeModels() query is loading, and apply
the same change to the similar block that mirrors lines 82-86 (referencing
isOpencodeEnabled, opencodeModelsLoading, dynamicOpencodeModelsList,
opencodeFetchTriedRef, dynamicOpencodeLoading, and fetchOpencodeModels).
- Around line 164-169: The current logic may call onModelSelect with a hardcoded
'opencode-big-pickle' when opencodeDefaultModel isn't available, which can
select a disabled model; change the selection logic in the block that computes
isDefaultModelAvailable / defaultModelId so you only choose an id that exists in
allOpencodeModels (use allOpencodeModels[0]?.id if present) and do not fall back
to the literal 'opencode-big-pickle'; call onModelSelect only when a valid model
id is found (i.e., defaultModelId is defined and present in allOpencodeModels)
so we never programmatically select a disabled/non-loaded model (refer to
isDefaultModelAvailable, opencodeDefaultModel, allOpencodeModels,
defaultModelId, and onModelSelect).

---

Duplicate comments:
In `@apps/ui/src/components/views/board-view/components/selection-action-bar.tsx`:
- Around line 121-126: The Verify launcher currently enables when there are
selections even if no onVerify handler exists; update the Button in
selection-action-bar (the Button with onClick={handleVerifyClick}) to be
disabled when either selectedCount === 0 or onVerify is falsy (e.g.,
disabled={selectedCount === 0 || !onVerify}) and also add a defensive guard
inside handleVerifyClick to return early if onVerify is not provided before
attempting to open the dialog, ensuring the UI never opens a dead-end dialog
when no handler is supplied.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86e282f and fb7e4fe.

📒 Files selected for processing (3)
  • 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/shared/model-selector.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx

@gsxdsm
Copy link
Collaborator Author

gsxdsm commented Feb 28, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 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.

@gsxdsm gsxdsm merged commit 63b0a4f into AutoMaker-Org:v1.0.0rc Feb 28, 2026
6 checks passed
gsxdsm added a commit to gsxdsm/automaker that referenced this pull request Mar 1, 2026
* 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
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