Skip to content

Update 1.1.0rc with latest from 1.0.0rc#833

Open
gsxdsm wants to merge 3 commits intoAutoMaker-Org:v1.1.0rcfrom
gsxdsm:v1.1.0rc
Open

Update 1.1.0rc with latest from 1.0.0rc#833
gsxdsm wants to merge 3 commits intoAutoMaker-Org:v1.1.0rcfrom
gsxdsm:v1.1.0rc

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Mar 4, 2026

Summary by CodeRabbit

Release Notes

  • Performance Improvements

    • Development server output handling optimized for smoother real-time updates
    • Server logs now batched and displayed more efficiently, reducing UI flicker
    • Reduced unnecessary cache refreshes during feature updates
    • Improved WebSocket event handling with better batching strategies
  • Bug Fixes

    • Fixed potential race conditions in feature state management
    • Enhanced settings synchronization between client and server
  • Chores

    • Optimized build configuration and dependency management

gsxdsm and others added 3 commits March 3, 2026 22:50
…er-Org#828)

* Changes from fix/dev-server-hang

* fix: Address PR AutoMaker-Org#828 review feedback

- Reset RAF buffer on context changes (worktree switch, dev-server restart)
  to prevent stale output from flushing into new sessions
- Fix high-frequency WebSocket filter to catch auto-mode:event wrapping
  (auto_mode_progress is wrapped in auto-mode:event) and add feature:progress
- Reorder Vite aliases so explicit jsx-runtime entries aren't shadowed by
  the broad /^react(\/|$)/ regex (Vite uses first-match-wins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: Batch dev server logs and fix React module resolution order

* feat: Add fallback timer for flushing dev server logs in background tabs

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Changes from fix/board-react-crash

* fix: Prevent cascading re-renders and crashes from high-frequency WS events
* Changes from fix/event-hook-endpoint

* fix: Allow empty eventHooks/ntfyEndpoints to reconcile from server

Remove the `length > 0` guards in fast-hydrate reconciliation that
prevented intentional empty-array clears from syncing across clients.
Server-side wipe protection (`__allowEmpty*` escape hatches) already
ensures empty arrays in the server are intentional.

Addresses PR AutoMaker-Org#831 review feedback from CodeRabbit and Gemini.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

This pull request optimizes rendering performance and stabilizes state management by replacing direct QueryClient cache mutations with centralized persistFeatureUpdate in board-view hooks, introducing feature transition guards to prevent race conditions with WebSocket events, batching high-frequency logs and activities, refining WebSocket event logging levels for high-frequency messages, adjusting dev-server output throttling, and improving Vite configuration for module resolution and cache isolation.

Changes

Cohort / File(s) Summary
Server Logging & Throttling
apps/server/src/index.ts, apps/server/src/services/dev-server-service.ts, apps/server/tests/unit/services/dev-server-event-types.test.ts
Demoted high-frequency WebSocket event logging to debug level (dev-server:output, test-runner:output, feature:progress). Increased OUTPUT_THROTTLE_MS from 4ms to 100ms and OUTPUT_BATCH_SIZE from 4096 to 8192 bytes. Updated test wait time to accommodate new throttle timing.
Board-View Hooks Refactoring
apps/ui/src/components/views/board-view/hooks/use-board-actions.ts, apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts, apps/ui/src/components/views/board-view/hooks/use-board-features.ts, apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts, apps/ui/src/components/views/board-view/kanban-board.tsx
Replaced per-action direct QueryClient mutations (moveFeature, setQueryData) with centralized persistFeatureUpdate for optimistic updates. Updated loadFeatures to use query invalidation instead of refetch. Simplified cache-clearing logic and improved memoization with stable empty references. Removed useRef tracking for recently-completed features in favor of store-based reads.
Dev Server & Auto-Mode Optimization
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-server-logs.ts, apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts, apps/ui/src/hooks/use-auto-mode.ts
Introduced buffered, batched logging for dev-server output with requestAnimationFrame throttling. Increased reconciliation interval from 5s to 30s and added global event recording for non-output lifecycle events. Replaced direct addAutoModeActivity calls with batchedAddAutoModeActivity, batching activities every 100ms to reduce store update frequency.
Cache & State Management
apps/ui/src/hooks/use-query-invalidation.ts, apps/ui/src/lib/feature-transition-state.ts, apps/ui/src/lib/http-api-client.ts, apps/ui/src/store/app-store.ts, apps/ui/src/routes/__root.tsx
Added feature-transition-state module to track and guard against race conditions during feature updates. Implemented selective WebSocket message logging (suppressing high-frequency event types). Extended Settings API type with eventHooks and ntfyEndpoints. Added idempotent guards in store actions and updated event-hooks reconciliation logic. Included __allowEmpty* flags for empty array payloads.
Build & Module Configuration
apps/ui/vite.config.mts
Added cacheDir for per-worktree Vite cache isolation. Introduced explicit resolve aliases for react/jsx-runtime and react/jsx-dev-runtime. Expanded dedupe and optimizeDeps to include zustand, use-sync-external-store, and @xyflow/react.

Sequence Diagram

sequenceDiagram
    participant UI as User/Component
    participant Store as AppStore
    participant PersistFn as persistFeatureUpdate
    participant QueryCache as React Query
    participant Server as Backend API

    rect rgba(100, 150, 200, 0.5)
    Note over UI,Server: New Optimistic Update Flow
    UI->>Store: User action (e.g., move feature)
    UI->>PersistFn: Call persistFeatureUpdate(featureId, newStatus)
    PersistFn->>Store: markFeatureTransitioning(featureId)
    PersistFn->>QueryCache: Optimistic update (setQueryData)
    UI->>UI: Re-render with new status
    PersistFn->>Server: Send update request
    Server-->>QueryCache: Server response/invalidation
    QueryCache->>UI: Trigger refetch/invalidation
    UI->>UI: Re-render with server data
    PersistFn->>Store: unmarkFeatureTransitioning(featureId)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested labels

Refactor, cleanup, Performance

Poem

🐰 Twitching whiskers with delight,
Batches flowing, logs so light,
Transitions guarded, guards so tight,
Caches sync'd and rendering bright!
Performance hops, what a sight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 describes the main objective of the PR: merging updates from 1.0.0rc into 1.1.0rc, which is clearly reflected across all changed files.

✏️ 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 focuses on enhancing the application's performance and stability, particularly in areas involving real-time updates and state management. Key changes include optimizing WebSocket event handling to reduce log spam and UI re-renders, implementing more efficient state updates in the UI to prevent cascading re-renders, and introducing mechanisms to manage feature transition states, thereby mitigating race conditions. Additionally, the build configuration has been refined to ensure a more robust and consistent React environment.

Highlights

  • Performance Optimization: Significantly reduced UI re-renders and server log spam by throttling WebSocket events, batching UI updates, and optimizing Zustand store subscriptions for improved responsiveness.
  • Stability Improvements: Addressed race conditions and 'maximum update depth exceeded' errors in React by introducing feature transition state tracking and refining query invalidation logic.
  • State Management Refinements: Enhanced Zustand store idempotency and selector efficiency to prevent unnecessary updates and ensure consistent state behavior.
  • Build Configuration: Updated Vite configuration to ensure consistent React and Zustand instances across the application, resolving potential hook-related issues and improving build reliability.

🧠 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
    • Changed logging level for high-frequency WebSocket events from 'info' to 'debug' to reduce log spam.
    • Removed redundant payload details from event logging.
    • Updated WebSocket connection warning from 'logger.info' to 'logger.warn'.
  • apps/server/src/services/dev-server-service.ts
    • Increased 'OUTPUT_THROTTLE_MS' for dev server output from 4ms to 100ms.
    • Increased 'OUTPUT_BATCH_SIZE' for dev server output from 4096 to 8192.
  • apps/server/tests/unit/services/dev-server-event-types.test.ts
    • Adjusted 'setTimeout' duration in dev server event tests from 100ms to 250ms to align with new throttling settings.
  • apps/ui/src/components/views/board-view/hooks/use-board-actions.ts
    • Removed 'useQueryClient' import and usage.
    • Removed 'moveFeature' from 'useAppStore' subscription.
    • Replaced direct 'moveFeature' calls with 'persistFeatureUpdate' for status changes.
    • Removed direct 'updateFeature' calls where 'persistFeatureUpdate' handles the update.
    • Introduced 'markFeatureTransitioning' and 'unmarkFeatureTransitioning' for 'handleForceStopFeature'.
    • Updated dependency arrays for several 'useCallback' hooks.
  • apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts
    • Removed 'useRef' for 'prevFeatureIdsRef'.
    • Modified 'useEffect' for clearing recently completed features to use 'useAppStore.getState()' and reduce dependencies.
  • apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts
    • Removed 'moveFeature' from 'useAppStore' subscription.
    • Replaced direct 'moveFeature' calls with 'persistFeatureUpdate'.
    • Updated dependency array for 'handleDragEnd' callback.
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
    • Removed 'refetch: loadFeatures' from 'useFeatures' destructuring.
    • Simplified auto mode event handling in 'useEffect', removing redundant 'removeRunningTask' logic and specific project path logging.
    • Removed 'eslint-disable-next-line' comment for 'loadFeatures' dependency.
  • apps/ui/src/components/views/board-view/kanban-board.tsx
    • Added 'EMPTY_FEATURE_IDS' constant to provide a stable empty Set for 'selectedFeatureIds' prop.
    • Updated 'selectedFeatureIds' default prop to use 'EMPTY_FEATURE_IDS'.
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-server-logs.ts
    • Implemented 'pendingOutputRef', 'rafIdRef', and 'timerIdRef' for batching log output.
    • Added 'resetPendingOutput' and 'flushPendingOutput' functions for managing log buffer.
    • Modified 'clearLogs' to call 'resetPendingOutput'.
    • Refactored 'appendLogs' to use 'requestAnimationFrame' and 'setTimeout' for batched updates.
    • Added 'useEffect' for cleanup of pending RAF on unmount.
    • Called 'resetPendingOutput' when 'dev-server:started' event is received.
    • Updated dependency array for the main 'useEffect' hook.
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts
    • Increased 'STATE_RECONCILE_INTERVAL_MS' from 5 seconds to 30 seconds.
    • Integrated 'useEventRecencyStore' to 'recordGlobalEvent' for dev server lifecycle events (excluding output).
    • Updated dependency array for the main 'useEffect' hook.
  • apps/ui/src/hooks/use-auto-mode.ts
    • Added 'EMPTY_TASKS' constant.
    • Refactored 'useAutoMode' to use a targeted 'useShallow' selector for worktree-specific auto mode state, improving re-render performance.
    • Implemented 'prevTasksRef' and 'useMemo' to stabilize 'runningAutoTasks' array reference.
    • Introduced 'pendingActivitiesRef', 'flushTimerRef', and 'batchedAddAutoModeActivity' for batching auto mode activity updates.
    • Updated all calls to 'addAutoModeActivity' to use 'batchedAddAutoModeActivity'.
    • Added 'useEffect' for cleanup of the flush timer on unmount.
    • Updated dependency arrays for 'stopAutoModeFeature' and the main 'useEffect' hook.
  • apps/ui/src/hooks/use-query-invalidation.ts
    • Imported 'isAnyFeatureTransitioning'.
    • Removed 'auto_mode_started' and 'auto_mode_stopped' from 'FEATURE_LIST_INVALIDATION_EVENTS'.
    • Added a condition to 'invalidateQueries' for feature lists to skip invalidation if any feature is transitioning.
  • apps/ui/src/lib/feature-transition-state.ts
    • Added new file to track features in transition state.
    • Exported 'markFeatureTransitioning', 'unmarkFeatureTransitioning', and 'isAnyFeatureTransitioning' functions to manage a module-level set of transitioning feature IDs.
  • apps/ui/src/lib/http-api-client.ts
    • Modified WebSocket 'onmessage' handler to only log non-high-frequency events at 'info' level.
    • Updated 'GlobalSettings' interface to include 'eventHooks' and 'ntfyEndpoints' with their respective structures.
  • apps/ui/src/routes/__root.tsx
    • Simplified the condition for reconciling 'eventHooks' by removing the 'serverHooks.length > 0' check.
  • apps/ui/src/store/app-store.ts
    • Made 'removeRunningTask' idempotent by checking if the task exists before modifying the state.
    • Made 'addRecentlyCompletedFeature' idempotent by checking if the feature ID is already in the set.
    • Made 'clearRecentlyCompletedFeatures' idempotent by checking if the set is already empty.
    • Modified 'updateEventHooks' and 'updateNtfyEndpoints' to include '__allowEmptyEventHooks' and '__allowEmptyNtfyEndpoints' flags when sending empty arrays to the API.
  • apps/ui/vite.config.mts
    • Added 'cacheDir' to 'node_modules/.vite' to localize Vite's dependency optimization cache.
    • Expanded 'resolve.alias' to explicitly map 'react/jsx-runtime' and 'react/jsx-dev-runtime' to the root 'node_modules'.
    • Expanded 'dedupe' array to include 'zustand', 'use-sync-external-store', and '@xyflow/react' for better dependency management.
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 a significant set of performance optimizations and refactorings across the UI and server. The changes focus on reducing log spam, preventing unnecessary UI re-renders by improving state management with Zustand and React Query, and fixing race conditions related to optimistic updates. The refactoring to centralize feature state updates is a great improvement for maintainability. Overall, these are excellent changes that should noticeably improve the application's performance and stability, especially on mobile devices. I have one minor suggestion to improve code clarity.

Note: Security Review did not run due to the size of the PR.

// invalidateQueries() has time to settle before WS-driven invalidations
// are allowed through again. Without this, a WS event arriving during
// the refetch window would trigger a conflicting invalidation.
setTimeout(() => unmarkFeatureTransitioning(feature.id), 500);
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 500ms delay is a magic number. To improve readability and maintainability, consider extracting it into a named constant at the top of the file, for example: const UNMARK_TRANSITION_DELAY_MS = 500;. This makes the purpose of the delay clearer and simplifies future adjustments.

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

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-query-invalidation.ts (1)

182-191: ⚠️ Potential issue | 🟠 Major

Skipped invalidations are dropped with no catch-up refresh.

At Line 187, invalidations are skipped during transitions, but there’s no deferred replay once transitions end. If no subsequent qualifying event arrives, the feature list can stay stale.

Suggested fix (defer + drain once transition window ends)
- if (FEATURE_LIST_INVALIDATION_EVENTS.includes(event.type) && !isAnyFeatureTransitioning()) {
-   queryClient.invalidateQueries({
-     queryKey: queryKeys.features.all(currentProjectPath),
-   });
- }
+ if (FEATURE_LIST_INVALIDATION_EVENTS.includes(event.type)) {
+   if (isAnyFeatureTransitioning()) {
+     pendingFeatureListInvalidationRef.current = true;
+     scheduleFeatureListDrain();
+   } else {
+     pendingFeatureListInvalidationRef.current = false;
+     queryClient.invalidateQueries({
+       queryKey: queryKeys.features.all(currentProjectPath),
+     });
+   }
+ }
// Add near other refs in the hook:
const pendingFeatureListInvalidationRef = useRef(false);
const featureListDrainTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

function scheduleFeatureListDrain() {
  if (featureListDrainTimerRef.current) return;
  featureListDrainTimerRef.current = setTimeout(() => {
    featureListDrainTimerRef.current = null;
    if (pendingFeatureListInvalidationRef.current && !isAnyFeatureTransitioning()) {
      pendingFeatureListInvalidationRef.current = false;
      queryClient.invalidateQueries({ queryKey: queryKeys.features.all(currentProjectPath) });
    }
  }, 650);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/hooks/use-query-invalidation.ts` around lines 182 - 191,
FEATURE_LIST_INVALIDATION_EVENTS-based invalidations are currently dropped when
isAnyFeatureTransitioning() is true, leaving the feature list stale if no later
event arrives; add a deferred replay: introduce
pendingFeatureListInvalidationRef and featureListDrainTimerRef in the hook, when
an invalidation is skipped set pendingFeatureListInvalidationRef.current = true
and call scheduleFeatureListDrain() which starts a single 650ms timer (no-op if
already set); when the timer fires clear the timer ref, check
!isAnyFeatureTransitioning() and pendingFeatureListInvalidationRef.current, then
clear pending and call queryClient.invalidateQueries({ queryKey:
queryKeys.features.all(currentProjectPath) }); ensure the timer is cleared on
unmount to avoid leaks and place the refs/handler alongside the other hook refs.
🧹 Nitpick comments (4)
apps/server/src/index.ts (1)

601-611: Consider expanding the high-frequency event list.

The list currently includes dev-server:output, test-runner:output, and feature:progress, but the EventType definition in libs/types/src/event.ts shows additional streaming/progress events that may also be high-frequency:

  • agent:stream — streaming agent output
  • ideation:stream — streaming ideation output
  • worktree:init-output — worktree initialization output
  • test-runner:progress — test progress updates

If these events fire frequently, they'll still log at info level and may contribute to the same log spam issue this change addresses.

♻️ Suggested expansion
     const isHighFrequency =
-      type === 'dev-server:output' || type === 'test-runner:output' || type === 'feature:progress';
+      type === 'dev-server:output' ||
+      type === 'test-runner:output' ||
+      type === 'test-runner:progress' ||
+      type === 'feature:progress' ||
+      type === 'agent:stream' ||
+      type === 'ideation:stream' ||
+      type === 'worktree:init-output';

Alternatively, consider using a Set for O(1) lookup and easier maintenance:

♻️ Alternative: Use a Set constant
const HIGH_FREQUENCY_EVENTS = new Set([
  'dev-server:output',
  'test-runner:output',
  'test-runner:progress',
  'feature:progress',
  'agent:stream',
  'ideation:stream',
  'worktree:init-output',
]);

// Then in the handler:
const isHighFrequency = HIGH_FREQUENCY_EVENTS.has(type);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/index.ts` around lines 601 - 611, The current high-frequency
check (isHighFrequency) only includes 'dev-server:output', 'test-runner:output',
and 'feature:progress' and misses other streaming/progress events; update it to
include 'agent:stream', 'ideation:stream', 'worktree:init-output', and
'test-runner:progress' and refactor to use a constant Set (e.g.,
HIGH_FREQUENCY_EVENTS) for O(1) lookups; replace the inline boolean expression
with HIGH_FREQUENCY_EVENTS.has(type) and keep using logger.debug for those types
and logger.info otherwise to avoid log spam.
apps/ui/vite.config.mts (2)

270-270: Consider centralizing singleton dependency names to avoid config drift.

resolve.dedupe and optimizeDeps.include now share several package names. Extracting a shared constant will reduce future mismatch risk.

♻️ Proposed refactor
+const SINGLETON_DEPS = [
+  'react',
+  'react-dom',
+  'zustand',
+  'use-sync-external-store',
+  '@xyflow/react',
+] as const;
...
-      dedupe: ['react', 'react-dom', 'zustand', 'use-sync-external-store', '@xyflow/react'],
+      dedupe: [...SINGLETON_DEPS],
...
       include: [
-        'react',
-        'react-dom',
+        ...SINGLETON_DEPS,
         'react/jsx-runtime',
         'react/jsx-dev-runtime',
-        'use-sync-external-store',
         'use-sync-external-store/shim',
         'use-sync-external-store/shim/with-selector',
-        'zustand',
-        '@xyflow/react',
       ],

Also applies to: 367-376

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

In `@apps/ui/vite.config.mts` at line 270, The dedupe list in vite.config.mts is
duplicated across resolve.dedupe and optimizeDeps.include causing potential
drift; define a single shared constant (e.g., const SINGLETON_DEPS or
SHARED_DEDUPE) containing
['react','react-dom','zustand','use-sync-external-store','@xyflow/react'] and
reference that constant in both resolve.dedupe and optimizeDeps.include (also
update the other occurrence around the 367-376 block) so both locations read
from the same source of truth.

257-264: Consider using createRequire + require.resolve() for jsx-runtime aliases.

The jsx-runtime aliases are solving a real CJS/ESM interop problem (as documented at lines 310 and 363–366), and the suggested refactor using createRequire from Node's module package is the idiomatic ESM approach. This would be more maintainable than hardcoded relative paths. The current approach works, but require.resolve() provides better abstraction if the install layout ever changes.

♻️ Proposed refactor
+import { createRequire } from 'module';
 ...
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const require = createRequire(import.meta.url);
 ...
         {
           find: 'react/jsx-runtime',
-          replacement: path.resolve(__dirname, '../../node_modules/react/jsx-runtime.js'),
+          replacement: require.resolve('react/jsx-runtime'),
         },
         {
           find: 'react/jsx-dev-runtime',
-          replacement: path.resolve(__dirname, '../../node_modules/react/jsx-dev-runtime.js'),
+          replacement: require.resolve('react/jsx-dev-runtime'),
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/vite.config.mts` around lines 257 - 264, Replace the hardcoded
path.resolve(__dirname, '../../node_modules/react/jsx-runtime.js') and
react/jsx-dev-runtime alias entries with Node's createRequire + require.resolve
to robustly resolve the package entrypoints; specifically, import createRequire
from 'module' (or use a createRequire call near the top of vite.config.mts),
create a require for __filename, and use require.resolve('react/jsx-runtime')
and require.resolve('react/jsx-dev-runtime') in the replacement fields for the
existing alias objects so the aliases reference resolved module paths instead of
brittle relative paths.
apps/ui/src/lib/feature-transition-state.ts (1)

17-18: Scope transition checks to the affected entity, not globally.

At Line 17, a global boolean means one transitioning feature can suppress unrelated feature-list invalidations. Prefer a scoped check (feature/worktree/project key) so only the conflicting transition is gated.

Refactor direction
- export function isAnyFeatureTransitioning(): boolean {
-   return transitioningFeatures.size > 0;
- }
+ // Keep global helper if needed, but add scoped checks for callers that can key events.
+ export function isFeatureTransitioning(featureKey: string): boolean {
+   return transitioningFeatures.has(featureKey);
+ }
+
+ export function isAnyFeatureTransitioning(): boolean {
+   return transitioningFeatures.size > 0;
+ }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/lib/feature-transition-state.ts` around lines 17 - 18, The
current global check in isAnyFeatureTransitioning() uses
transitioningFeatures.size and blocks unrelated invalidations; change it to a
scoped check that accepts a unique key (e.g., featureId, worktreeId or composite
project key) and tests only entries for that key in the transitioningFeatures
collection. Add a new function (or overload) like
isFeatureTransitioning(scopeKey: string): boolean that inspects
transitioningFeatures.has(scopeKey) or filters by scope in the map/set, update
callers to pass the proper scope (feature/worktree/project) instead of relying
on the global isAnyFeatureTransitioning(), and keep the original function only
for backward-compatibility if needed by delegating to a scoped check that
returns true only when any scope matches.
🤖 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/hooks/use-board-actions.ts`:
- Around line 949-954: The success toasts in handleCompleteFeature and
handleUnarchiveFeature are shown immediately without confirming persistence;
change these handlers to await the result from persistFeatureUpdate (update
persistFeatureUpdate to return a success boolean or result if it doesn't
already), and only call toast.success (using truncateDescription for the
message) when the awaited result indicates success; if persistence fails, show a
toast.error or appropriate failure feedback instead and handle any rollback
logic. Ensure you update both handleCompleteFeature and handleUnarchiveFeature
to await persistFeatureUpdate and gate toast.success on the returned success
flag.

In
`@apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-server-logs.ts`:
- Around line 236-241: The cleanup currently only runs on unmount which allows
buffered logs to flush when worktreePath changes; update the effect in
use-dev-server-logs so resetPendingOutput is also invoked when worktreePath
changes by including worktreePath in the effect dependencies (i.e., useEffect
cleanup should run on worktreePath change as well as unmount), ensuring
resetPendingOutput is called in the cleanup returned by that effect to clear
buffered logs when switching worktrees.

In `@apps/ui/src/hooks/use-auto-mode.ts`:
- Around line 221-253: The batching currently iterates and calls
addAutoModeActivity per item (causing many store updates) and the unmount
cleanup clears the timer without flushing pendingActivitiesRef; fix by adding a
single flush function (e.g., flushPendingActivities) that reads
pendingActivitiesRef, resets it and calls the store once with the whole batch
(either via a new addAutoModeActivities API or by extending addAutoModeActivity
to accept an array / single transaction), replace the for-loop in
batchedAddAutoModeActivity with a single call to that flush, and in the
useEffect cleanup call flushPendingActivities before clearing flushTimerRef so
no queued activities are dropped (refer to pendingActivitiesRef, flushTimerRef,
batchedAddAutoModeActivity, addAutoModeActivity, and the useEffect cleanup).

---

Outside diff comments:
In `@apps/ui/src/hooks/use-query-invalidation.ts`:
- Around line 182-191: FEATURE_LIST_INVALIDATION_EVENTS-based invalidations are
currently dropped when isAnyFeatureTransitioning() is true, leaving the feature
list stale if no later event arrives; add a deferred replay: introduce
pendingFeatureListInvalidationRef and featureListDrainTimerRef in the hook, when
an invalidation is skipped set pendingFeatureListInvalidationRef.current = true
and call scheduleFeatureListDrain() which starts a single 650ms timer (no-op if
already set); when the timer fires clear the timer ref, check
!isAnyFeatureTransitioning() and pendingFeatureListInvalidationRef.current, then
clear pending and call queryClient.invalidateQueries({ queryKey:
queryKeys.features.all(currentProjectPath) }); ensure the timer is cleared on
unmount to avoid leaks and place the refs/handler alongside the other hook refs.

---

Nitpick comments:
In `@apps/server/src/index.ts`:
- Around line 601-611: The current high-frequency check (isHighFrequency) only
includes 'dev-server:output', 'test-runner:output', and 'feature:progress' and
misses other streaming/progress events; update it to include 'agent:stream',
'ideation:stream', 'worktree:init-output', and 'test-runner:progress' and
refactor to use a constant Set (e.g., HIGH_FREQUENCY_EVENTS) for O(1) lookups;
replace the inline boolean expression with HIGH_FREQUENCY_EVENTS.has(type) and
keep using logger.debug for those types and logger.info otherwise to avoid log
spam.

In `@apps/ui/src/lib/feature-transition-state.ts`:
- Around line 17-18: The current global check in isAnyFeatureTransitioning()
uses transitioningFeatures.size and blocks unrelated invalidations; change it to
a scoped check that accepts a unique key (e.g., featureId, worktreeId or
composite project key) and tests only entries for that key in the
transitioningFeatures collection. Add a new function (or overload) like
isFeatureTransitioning(scopeKey: string): boolean that inspects
transitioningFeatures.has(scopeKey) or filters by scope in the map/set, update
callers to pass the proper scope (feature/worktree/project) instead of relying
on the global isAnyFeatureTransitioning(), and keep the original function only
for backward-compatibility if needed by delegating to a scoped check that
returns true only when any scope matches.

In `@apps/ui/vite.config.mts`:
- Line 270: The dedupe list in vite.config.mts is duplicated across
resolve.dedupe and optimizeDeps.include causing potential drift; define a single
shared constant (e.g., const SINGLETON_DEPS or SHARED_DEDUPE) containing
['react','react-dom','zustand','use-sync-external-store','@xyflow/react'] and
reference that constant in both resolve.dedupe and optimizeDeps.include (also
update the other occurrence around the 367-376 block) so both locations read
from the same source of truth.
- Around line 257-264: Replace the hardcoded path.resolve(__dirname,
'../../node_modules/react/jsx-runtime.js') and react/jsx-dev-runtime alias
entries with Node's createRequire + require.resolve to robustly resolve the
package entrypoints; specifically, import createRequire from 'module' (or use a
createRequire call near the top of vite.config.mts), create a require for
__filename, and use require.resolve('react/jsx-runtime') and
require.resolve('react/jsx-dev-runtime') in the replacement fields for the
existing alias objects so the aliases reference resolved module paths instead of
brittle relative paths.

ℹ️ Review info
Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e297001d-fbc7-4345-9bb3-0cdf903e1e26

📥 Commits

Reviewing files that changed from the base of the PR and between c17c453 and c429a52.

📒 Files selected for processing (17)
  • apps/server/src/index.ts
  • apps/server/src/services/dev-server-service.ts
  • apps/server/tests/unit/services/dev-server-event-types.test.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-actions.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-server-logs.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts
  • apps/ui/src/hooks/use-auto-mode.ts
  • apps/ui/src/hooks/use-query-invalidation.ts
  • apps/ui/src/lib/feature-transition-state.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/routes/__root.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/vite.config.mts

Comment on lines +949 to +954
persistFeatureUpdate(feature.id, { status: 'completed' as const });
toast.success('Feature completed', {
description: `Archived: ${truncateDescription(feature.description)}`,
});
},
[updateFeature, persistFeatureUpdate]
[persistFeatureUpdate]
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

Success toasts can be incorrect when persistence fails.

handleCompleteFeature and handleUnarchiveFeature show success immediately after calling persistFeatureUpdate, but that persistence path can rollback on API failure. This can leave users seeing a success toast even when the status change did not stick. Gate success notifications on confirmed persistence outcome (e.g., make persistFeatureUpdate return a success flag/result and await it here).

Also applies to: 971-983

🤖 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/hooks/use-board-actions.ts` around
lines 949 - 954, The success toasts in handleCompleteFeature and
handleUnarchiveFeature are shown immediately without confirming persistence;
change these handlers to await the result from persistFeatureUpdate (update
persistFeatureUpdate to return a success boolean or result if it doesn't
already), and only call toast.success (using truncateDescription for the
message) when the awaited result indicates success; if persistence fails, show a
toast.error or appropriate failure feedback instead and handle any rollback
logic. Ensure you update both handleCompleteFeature and handleUnarchiveFeature
to await persistFeatureUpdate and gate toast.success on the returned success
flag.

Comment on lines +236 to +241
// Clean up pending RAF on unmount to prevent state updates after unmount
useEffect(() => {
return () => {
resetPendingOutput();
};
}, [resetPendingOutput]);
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

Reseting only on unmount can leak buffered logs across worktree switches.

Line 236 currently scopes cleanup to unmount, but buffered content can still flush after a worktreePath change and append output from the previous worktree into the new state.

💡 Suggested fix
-  // Clean up pending RAF on unmount to prevent state updates after unmount
-  useEffect(() => {
-    return () => {
-      resetPendingOutput();
-    };
-  }, [resetPendingOutput]);
+  // Reset buffered output whenever subscription scope changes, and on unmount
+  useEffect(() => {
+    resetPendingOutput();
+    return () => {
+      resetPendingOutput();
+    };
+  }, [worktreePath, autoSubscribe, resetPendingOutput]);
📝 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
// Clean up pending RAF on unmount to prevent state updates after unmount
useEffect(() => {
return () => {
resetPendingOutput();
};
}, [resetPendingOutput]);
// Reset buffered output whenever subscription scope changes, and on unmount
useEffect(() => {
resetPendingOutput();
return () => {
resetPendingOutput();
};
}, [worktreePath, autoSubscribe, resetPendingOutput]);
🤖 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/worktree-panel/hooks/use-dev-server-logs.ts`
around lines 236 - 241, The cleanup currently only runs on unmount which allows
buffered logs to flush when worktreePath changes; update the effect in
use-dev-server-logs so resetPendingOutput is also invoked when worktreePath
changes by including worktreePath in the effect dependencies (i.e., useEffect
cleanup should run on worktreePath change as well as unmount), ensuring
resetPendingOutput is called in the cleanup returned by that effect to clear
buffered logs when switching worktrees.

Comment on lines +221 to +253
// Batch addAutoModeActivity calls to reduce Zustand set() frequency.
// Without batching, each WS event (especially auto_mode_progress which fires
// rapidly during streaming) triggers a separate set() → all subscriber selectors
// re-evaluate → on mobile this overwhelms React's batching → crash.
// This batches activities in a ref and flushes them in a single set() call.
const pendingActivitiesRef = useRef<Parameters<typeof addAutoModeActivity>[0][]>([]);
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const batchedAddAutoModeActivity = useCallback(
(activity: Parameters<typeof addAutoModeActivity>[0]) => {
pendingActivitiesRef.current.push(activity);
if (!flushTimerRef.current) {
flushTimerRef.current = setTimeout(() => {
const batch = pendingActivitiesRef.current;
pendingActivitiesRef.current = [];
flushTimerRef.current = null;
// Flush all pending activities in a single store update
for (const act of batch) {
addAutoModeActivity(act);
}
}, 100);
}
},
[addAutoModeActivity]
);

// Cleanup flush timer on unmount
useEffect(() => {
return () => {
if (flushTimerRef.current) {
clearTimeout(flushTimerRef.current);
}
};
}, []);
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

Activity batching currently still emits per-item store updates and can lose queued events.

The flush loop calls addAutoModeActivity once per activity, so high-frequency bursts still trigger many store updates. Also, unmount cleanup clears the timer without flushing pendingActivitiesRef, which can drop recent activities.

💡 Suggested fix (single flush path + unmount flush)
-  const pendingActivitiesRef = useRef<Parameters<typeof addAutoModeActivity>[0][]>([]);
+  const pendingActivitiesRef = useRef<Parameters<typeof addAutoModeActivity>[0][]>([]);
   const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const flushPendingActivities = useCallback(() => {
+    const batch = pendingActivitiesRef.current;
+    if (batch.length === 0) return;
+    pendingActivitiesRef.current = [];
+
+    // One store update for the whole batch (preserves current prepend behavior).
+    useAppStore.setState((state) => {
+      const entries = batch
+        .map((activity) => ({
+          ...activity,
+          id: Math.random().toString(36).slice(2),
+          timestamp: new Date(),
+        }))
+        .reverse();
+      return {
+        autoModeActivityLog: [...entries, ...state.autoModeActivityLog].slice(0, 100),
+      };
+    });
+  }, []);

   const batchedAddAutoModeActivity = useCallback(
     (activity: Parameters<typeof addAutoModeActivity>[0]) => {
       pendingActivitiesRef.current.push(activity);
       if (!flushTimerRef.current) {
         flushTimerRef.current = setTimeout(() => {
-          const batch = pendingActivitiesRef.current;
-          pendingActivitiesRef.current = [];
           flushTimerRef.current = null;
-          // Flush all pending activities in a single store update
-          for (const act of batch) {
-            addAutoModeActivity(act);
-          }
+          flushPendingActivities();
         }, 100);
       }
     },
-    [addAutoModeActivity]
+    [flushPendingActivities]
   );

   useEffect(() => {
     return () => {
       if (flushTimerRef.current) {
         clearTimeout(flushTimerRef.current);
+        flushTimerRef.current = null;
       }
+      flushPendingActivities();
     };
-  }, []);
+  }, [flushPendingActivities]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/hooks/use-auto-mode.ts` around lines 221 - 253, The batching
currently iterates and calls addAutoModeActivity per item (causing many store
updates) and the unmount cleanup clears the timer without flushing
pendingActivitiesRef; fix by adding a single flush function (e.g.,
flushPendingActivities) that reads pendingActivitiesRef, resets it and calls the
store once with the whole batch (either via a new addAutoModeActivities API or
by extending addAutoModeActivity to accept an array / single transaction),
replace the for-loop in batchedAddAutoModeActivity with a single call to that
flush, and in the useEffect cleanup call flushPendingActivities before clearing
flushTimerRef so no queued activities are dropped (refer to
pendingActivitiesRef, flushTimerRef, batchedAddAutoModeActivity,
addAutoModeActivity, and the useEffect cleanup).

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