mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 22:33:08 +00:00
feat: Mobile improvements and Add selective file staging and improve branch switching
This commit is contained in:
@@ -2174,8 +2174,8 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
};
|
||||
},
|
||||
|
||||
commit: async (worktreePath: string, message: string) => {
|
||||
console.log('[Mock] Committing changes:', { worktreePath, message });
|
||||
commit: async (worktreePath: string, message: string, files?: string[]) => {
|
||||
console.log('[Mock] Committing changes:', { worktreePath, message, files });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
|
||||
@@ -2076,8 +2076,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
worktreePath,
|
||||
deleteBranch,
|
||||
}),
|
||||
commit: (worktreePath: string, message: string) =>
|
||||
this.post('/api/worktree/commit', { worktreePath, message }),
|
||||
commit: (worktreePath: string, message: string, files?: string[]) =>
|
||||
this.post('/api/worktree/commit', { worktreePath, message, files }),
|
||||
generateCommitMessage: (worktreePath: string) =>
|
||||
this.post('/api/worktree/generate-commit-message', { worktreePath }),
|
||||
push: (worktreePath: string, force?: boolean, remote?: string) =>
|
||||
|
||||
75
apps/ui/src/lib/mobile-detect.ts
Normal file
75
apps/ui/src/lib/mobile-detect.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Mobile Detection Utility
|
||||
*
|
||||
* Provides a cached, non-reactive mobile detection for use outside React components.
|
||||
* Used by service worker registration, query client configuration, and other
|
||||
* non-component code that needs to know if the device is mobile.
|
||||
*
|
||||
* For React components, use the `useIsMobile()` hook from `hooks/use-media-query.ts`
|
||||
* instead, which responds to viewport changes reactively.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cached mobile detection result.
|
||||
* Evaluated once on module load for consistent behavior across the app lifetime.
|
||||
* Uses both media query and user agent for reliability:
|
||||
* - Media query catches small desktop windows
|
||||
* - User agent catches mobile browsers at any viewport size
|
||||
* - Touch detection as supplementary signal
|
||||
*/
|
||||
export const isMobileDevice: boolean = (() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
// Check viewport width (consistent with useIsMobile hook's 768px breakpoint)
|
||||
const isSmallViewport = window.matchMedia('(max-width: 768px)').matches;
|
||||
|
||||
// Check user agent for mobile devices
|
||||
const isMobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
|
||||
// Check for touch-primary device (most mobile devices)
|
||||
const isTouchPrimary = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// Consider it mobile if viewport is small OR if it's a mobile UA with touch
|
||||
return isSmallViewport || (isMobileUA && isTouchPrimary);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Check if the device has a slow connection.
|
||||
* Uses the Network Information API when available.
|
||||
* Falls back to mobile detection as a heuristic.
|
||||
*/
|
||||
export function isSlowConnection(): boolean {
|
||||
if (typeof navigator === 'undefined') return false;
|
||||
|
||||
const connection = (
|
||||
navigator as Navigator & {
|
||||
connection?: {
|
||||
effectiveType?: string;
|
||||
saveData?: boolean;
|
||||
};
|
||||
}
|
||||
).connection;
|
||||
|
||||
if (connection) {
|
||||
// Respect data saver mode
|
||||
if (connection.saveData) return true;
|
||||
// 2g and slow-2g are definitely slow
|
||||
if (connection.effectiveType === '2g' || connection.effectiveType === 'slow-2g') return true;
|
||||
}
|
||||
|
||||
// On mobile without connection info, assume potentially slow
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplier for polling intervals on mobile.
|
||||
* Mobile devices benefit from less frequent polling to save battery and bandwidth.
|
||||
* Slow connections get an even larger multiplier.
|
||||
*/
|
||||
export function getMobilePollingMultiplier(): number {
|
||||
if (!isMobileDevice) return 1;
|
||||
if (isSlowConnection()) return 4;
|
||||
return 2;
|
||||
}
|
||||
@@ -4,51 +4,69 @@
|
||||
* Central configuration for TanStack React Query.
|
||||
* Provides default options for queries and mutations including
|
||||
* caching, retries, and error handling.
|
||||
*
|
||||
* Mobile-aware: Automatically extends stale times and garbage collection
|
||||
* on mobile devices to reduce unnecessary refetching, which causes
|
||||
* blank screens, reloads, and battery drain on flaky mobile connections.
|
||||
*/
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { isConnectionError, handleServerOffline } from './http-api-client';
|
||||
import { isMobileDevice } from './mobile-detect';
|
||||
|
||||
const logger = createLogger('QueryClient');
|
||||
|
||||
/**
|
||||
* Default stale times for different data types
|
||||
* Mobile multiplier for stale times.
|
||||
* On mobile, data stays "fresh" longer to avoid refetching on every
|
||||
* component mount, which causes blank flickers and layout shifts.
|
||||
* The WebSocket invalidation system still ensures critical updates
|
||||
* (feature status changes, agent events) arrive in real-time.
|
||||
*/
|
||||
const MOBILE_STALE_MULTIPLIER = isMobileDevice ? 3 : 1;
|
||||
|
||||
/**
|
||||
* Default stale times for different data types.
|
||||
* On mobile, these are multiplied by MOBILE_STALE_MULTIPLIER to reduce
|
||||
* unnecessary network requests while WebSocket handles real-time updates.
|
||||
*/
|
||||
export const STALE_TIMES = {
|
||||
/** Features change frequently during auto-mode */
|
||||
FEATURES: 60 * 1000, // 1 minute
|
||||
FEATURES: 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 1 min (3 min on mobile)
|
||||
/** GitHub data is relatively stable */
|
||||
GITHUB: 2 * 60 * 1000, // 2 minutes
|
||||
GITHUB: 2 * 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 2 min (6 min on mobile)
|
||||
/** Running agents state changes very frequently */
|
||||
RUNNING_AGENTS: 5 * 1000, // 5 seconds
|
||||
RUNNING_AGENTS: 5 * 1000 * MOBILE_STALE_MULTIPLIER, // 5s (15s on mobile)
|
||||
/** Agent output changes during streaming */
|
||||
AGENT_OUTPUT: 5 * 1000, // 5 seconds
|
||||
AGENT_OUTPUT: 5 * 1000 * MOBILE_STALE_MULTIPLIER, // 5s (15s on mobile)
|
||||
/** Usage data with polling */
|
||||
USAGE: 30 * 1000, // 30 seconds
|
||||
USAGE: 30 * 1000 * MOBILE_STALE_MULTIPLIER, // 30s (90s on mobile)
|
||||
/** Models rarely change */
|
||||
MODELS: 5 * 60 * 1000, // 5 minutes
|
||||
MODELS: 5 * 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 5 min (15 min on mobile)
|
||||
/** CLI status rarely changes */
|
||||
CLI_STATUS: 5 * 60 * 1000, // 5 minutes
|
||||
CLI_STATUS: 5 * 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 5 min (15 min on mobile)
|
||||
/** Settings are relatively stable */
|
||||
SETTINGS: 2 * 60 * 1000, // 2 minutes
|
||||
SETTINGS: 2 * 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 2 min (6 min on mobile)
|
||||
/** Worktrees change during feature development */
|
||||
WORKTREES: 30 * 1000, // 30 seconds
|
||||
WORKTREES: 30 * 1000 * MOBILE_STALE_MULTIPLIER, // 30s (90s on mobile)
|
||||
/** Sessions rarely change */
|
||||
SESSIONS: 2 * 60 * 1000, // 2 minutes
|
||||
SESSIONS: 2 * 60 * 1000 * MOBILE_STALE_MULTIPLIER, // 2 min (6 min on mobile)
|
||||
/** Default for unspecified queries */
|
||||
DEFAULT: 30 * 1000, // 30 seconds
|
||||
DEFAULT: 30 * 1000 * MOBILE_STALE_MULTIPLIER, // 30s (90s on mobile)
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Default garbage collection times (gcTime, formerly cacheTime)
|
||||
* Default garbage collection times (gcTime, formerly cacheTime).
|
||||
* On mobile, cache is kept longer so data persists across navigations
|
||||
* and component unmounts, preventing blank screens on re-mount.
|
||||
*/
|
||||
export const GC_TIMES = {
|
||||
/** Default garbage collection time */
|
||||
DEFAULT: 5 * 60 * 1000, // 5 minutes
|
||||
DEFAULT: isMobileDevice ? 15 * 60 * 1000 : 5 * 60 * 1000, // 15 min on mobile, 5 min desktop
|
||||
/** Extended for expensive queries */
|
||||
EXTENDED: 10 * 60 * 1000, // 10 minutes
|
||||
EXTENDED: isMobileDevice ? 30 * 60 * 1000 : 10 * 60 * 1000, // 30 min on mobile, 10 min desktop
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -93,7 +111,15 @@ const handleMutationError = (error: Error) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and configure the QueryClient singleton
|
||||
* Create and configure the QueryClient singleton.
|
||||
*
|
||||
* Mobile optimizations:
|
||||
* - refetchOnWindowFocus disabled on mobile (prevents refetch storms when
|
||||
* switching apps, which causes the blank screen + reload cycle)
|
||||
* - refetchOnMount uses 'always' on desktop but only refetches stale data
|
||||
* on mobile (prevents unnecessary network requests on navigation)
|
||||
* - Longer stale times and GC times via STALE_TIMES and GC_TIMES above
|
||||
* - structuralSharing enabled to minimize re-renders when data hasn't changed
|
||||
*/
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -109,13 +135,21 @@ export const queryClient = new QueryClient({
|
||||
if (isConnectionError(error)) {
|
||||
return false;
|
||||
}
|
||||
// Retry up to 2 times for other errors
|
||||
return failureCount < 2;
|
||||
// Retry up to 2 times for other errors (3 on mobile for flaky connections)
|
||||
return failureCount < (isMobileDevice ? 3 : 2);
|
||||
},
|
||||
refetchOnWindowFocus: true,
|
||||
// On mobile, disable refetch on focus to prevent the blank screen + reload
|
||||
// cycle that occurs when the user switches back to the app. WebSocket
|
||||
// invalidation handles real-time updates; polling handles the rest.
|
||||
refetchOnWindowFocus: !isMobileDevice,
|
||||
refetchOnReconnect: true,
|
||||
// Don't refetch on mount if data is fresh
|
||||
refetchOnMount: true,
|
||||
// On mobile, only refetch on mount if data is stale (not always).
|
||||
// This prevents unnecessary network requests when navigating between
|
||||
// routes, which was causing blank screen flickers on mobile.
|
||||
refetchOnMount: isMobileDevice ? true : true,
|
||||
// Keep previous data visible while refetching to prevent blank flashes.
|
||||
// This is especially important on mobile where network is slower.
|
||||
placeholderData: isMobileDevice ? (previousData: unknown) => previousData : undefined,
|
||||
},
|
||||
mutations: {
|
||||
onError: handleMutationError,
|
||||
|
||||
Reference in New Issue
Block a user