chore: Enhance type safety and improve code consistency across components

- Added a new `typecheck` script in `package.json` for better type checking in the UI workspace.
- Refactored several components to remove unnecessary type assertions and improve type safety, particularly in `new-project-modal.tsx`, `edit-project-dialog.tsx`, and `task-progress-panel.tsx`.
- Updated event handling in `git-diff-panel.tsx` to use async functions for better error handling.
- Improved type definitions in various files, including `setup-view` and `electron.ts`, to ensure consistent usage of types across the codebase.
- Cleaned up global type definitions for better clarity and maintainability.

These changes aim to streamline the development process and reduce potential runtime errors.
This commit is contained in:
Shirone
2026-01-25 18:11:48 +01:00
parent b65037d995
commit 0fb471ca15
28 changed files with 320 additions and 125 deletions

View File

@@ -28,7 +28,11 @@ import type {
UpdateIdeaInput,
ConvertToFeatureOptions,
IdeationContextSources,
Feature,
IdeationStreamEvent,
IdeationAnalysisEvent,
} from '@automaker/types';
import type { InstallProgress } from '@/store/setup-store';
import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types';
import { getJSON, setJSON, removeItem } from './storage';
@@ -124,7 +128,7 @@ export interface IdeationAPI {
projectPath: string,
ideaId: string,
options?: ConvertToFeatureOptions
) => Promise<{ success: boolean; feature?: any; featureId?: string; error?: string }>;
) => Promise<{ success: boolean; feature?: Feature; featureId?: string; error?: string }>;
// Add suggestion directly to board as feature
addSuggestionToBoard: (
@@ -141,8 +145,8 @@ export interface IdeationAPI {
}>;
// Event subscriptions
onStream: (callback: (event: any) => void) => () => void;
onAnalysisEvent: (callback: (event: any) => void) => () => void;
onStream: (callback: (event: IdeationStreamEvent) => void) => () => void;
onAnalysisEvent: (callback: (event: IdeationAnalysisEvent) => void) => () => void;
}
export interface FileEntry {
@@ -186,6 +190,16 @@ export interface StatResult {
error?: string;
}
// Options for creating a pull request
export interface CreatePROptions {
projectPath?: string;
commitMessage?: string;
prTitle?: string;
prBody?: string;
baseBranch?: string;
draft?: boolean;
}
// Re-export types from electron.d.ts for external use
export type {
AutoModeEvent,
@@ -212,9 +226,6 @@ import type {
// Import HTTP API client (ES module)
import { getHttpApiClient, getServerUrlSync } from './http-api-client';
// Feature type - Import from app-store
import type { Feature } from '@/store/app-store';
// Running Agent type
export interface RunningAgent {
featureId: string;
@@ -749,7 +760,7 @@ export interface ElectronAPI {
};
// Setup API surface is implemented by the main process and mirrored by HttpApiClient.
// Keep this intentionally loose to avoid tight coupling between front-end and server types.
setup?: any;
setup?: SetupAPI;
agent?: {
start: (
sessionId: string,
@@ -950,13 +961,11 @@ export const isElectron = (): boolean => {
return false;
}
const w = window as any;
if (w.isElectron === true) {
if (window.isElectron === true) {
return true;
}
return !!w.electronAPI?.isElectron;
return !!window.electronAPI?.isElectron;
};
// Check if backend server is available
@@ -1030,7 +1039,7 @@ export const getCurrentApiMode = (): 'http' => {
// Debug helpers
if (typeof window !== 'undefined') {
(window as any).__checkApiMode = () => {
window.__checkApiMode = () => {
console.log('Current API mode:', getCurrentApiMode());
console.log('isElectron():', isElectron());
};
@@ -1413,8 +1422,8 @@ interface SetupAPI {
user: string | null;
error?: string;
}>;
onInstallProgress?: (callback: (progress: any) => void) => () => void;
onAuthProgress?: (callback: (progress: any) => void) => () => void;
onInstallProgress?: (callback: (progress: InstallProgress) => void) => () => void;
onAuthProgress?: (callback: (progress: InstallProgress) => void) => () => void;
}
// Mock Setup API implementation
@@ -1665,7 +1674,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
};
},
createPR: async (worktreePath: string, options?: any) => {
createPR: async (worktreePath: string, options?: CreatePROptions) => {
console.log('[Mock] Creating PR:', { worktreePath, options });
return {
success: true,
@@ -2927,7 +2936,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
console.log('[Mock] Getting all features for:', projectPath);
// Check if test has set mock features via global variable
const testFeatures = (window as any).__mockFeatures;
const testFeatures = window.__mockFeatures;
if (testFeatures !== undefined) {
return { success: true, features: testFeatures };
}

View File

@@ -162,9 +162,13 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
logger.info('Opening directory picker...');
// Try to show picker programmatically
if ('showPicker' in HTMLInputElement.prototype) {
// Note: showPicker() is available in modern browsers but not in standard TypeScript types
if (
'showPicker' in input &&
typeof (input as { showPicker?: () => void }).showPicker === 'function'
) {
try {
(input as any).showPicker();
(input as { showPicker: () => void }).showPicker();
logger.info('Using showPicker()');
} catch (error) {
logger.info('showPicker() failed, using click()', error);
@@ -263,11 +267,13 @@ export async function openFilePicker(options?: {
document.body.appendChild(input);
// Try to show picker programmatically
// Note: showPicker() is available in modern browsers but TypeScript types it as void
// In practice, it may return a Promise in some implementations, but we'll handle errors via try/catch
if ('showPicker' in HTMLInputElement.prototype) {
// Note: showPicker() is available in modern browsers but not in standard TypeScript types
if (
'showPicker' in input &&
typeof (input as { showPicker?: () => void }).showPicker === 'function'
) {
try {
(input as any).showPicker();
(input as { showPicker: () => void }).showPicker();
} catch {
// Fallback to click if showPicker fails
input.click();

View File

@@ -31,9 +31,15 @@ import type {
ConvertToFeatureOptions,
NotificationsAPI,
EventHistoryAPI,
CreatePROptions,
} from './electron';
import type { IdeationContextSources } from '@automaker/types';
import type { EventHistoryFilter } from '@automaker/types';
import type {
IdeationContextSources,
EventHistoryFilter,
IdeationStreamEvent,
IdeationAnalysisEvent,
Notification,
} from '@automaker/types';
import type { Message, SessionListItem } from '@/types/electron';
import type { Feature, ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
import type { WorktreeAPI, GitAPI, ModelDefinition, ProviderStatus } from '@/types/electron';
@@ -131,9 +137,7 @@ export const handleServerOffline = (): void => {
* Must be called early in Electron mode before making API requests.
*/
export const initServerUrl = async (): Promise<void> => {
// window.electronAPI is typed as ElectronAPI, but some Electron-only helpers
// (like getServerUrl) are not part of the shared interface. Narrow via `any`.
const electron = typeof window !== 'undefined' ? (window.electronAPI as any) : null;
const electron = typeof window !== 'undefined' ? window.electronAPI : null;
if (electron?.getServerUrl) {
try {
cachedServerUrl = await electron.getServerUrl();
@@ -249,7 +253,7 @@ export const isElectronMode = (): boolean => {
// Prefer a stable runtime marker from preload.
// In some dev/electron setups, method availability can be temporarily undefined
// during early startup, but `isElectron` remains reliable.
const api = window.electronAPI as any;
const api = window.electronAPI;
return api?.isElectron === true || !!api?.getApiKey;
};
@@ -266,7 +270,7 @@ export const checkExternalServerMode = async (): Promise<boolean> => {
}
if (typeof window !== 'undefined') {
const api = window.electronAPI as any;
const api = window.electronAPI;
if (api?.isExternalServerMode) {
try {
cachedExternalServerMode = Boolean(await api.isExternalServerMode());
@@ -2035,7 +2039,7 @@ export class HttpApiClient implements ElectronAPI {
this.post('/api/worktree/generate-commit-message', { worktreePath }),
push: (worktreePath: string, force?: boolean, remote?: string) =>
this.post('/api/worktree/push', { worktreePath, force, remote }),
createPR: (worktreePath: string, options?: any) =>
createPR: (worktreePath: string, options?: CreatePROptions) =>
this.post('/api/worktree/create-pr', { worktreePath, ...options }),
getDiffs: (projectPath: string, featureId: string) =>
this.post('/api/worktree/diffs', { projectPath, featureId }),
@@ -2762,18 +2766,18 @@ export class HttpApiClient implements ElectronAPI {
getPrompts: () => this.get('/api/ideation/prompts'),
onStream: (callback: (event: any) => void): (() => void) => {
onStream: (callback: (event: IdeationStreamEvent) => void): (() => void) => {
return this.subscribeToEvent('ideation:stream', callback as EventCallback);
},
onAnalysisEvent: (callback: (event: any) => void): (() => void) => {
onAnalysisEvent: (callback: (event: IdeationAnalysisEvent) => void): (() => void) => {
return this.subscribeToEvent('ideation:analysis', callback as EventCallback);
},
};
// Notifications API - project-level notifications
notifications: NotificationsAPI & {
onNotificationCreated: (callback: (notification: any) => void) => () => void;
onNotificationCreated: (callback: (notification: Notification) => void) => () => void;
} = {
list: (projectPath: string) => this.post('/api/notifications/list', { projectPath }),
@@ -2786,7 +2790,7 @@ export class HttpApiClient implements ElectronAPI {
dismiss: (projectPath: string, notificationId?: string) =>
this.post('/api/notifications/dismiss', { projectPath, notificationId }),
onNotificationCreated: (callback: (notification: any) => void): (() => void) => {
onNotificationCreated: (callback: (notification: Notification) => void): (() => void) => {
return this.subscribeToEvent('notification:created', callback as EventCallback);
},
};

View File

@@ -35,8 +35,8 @@ async function getDefaultDocumentsPath(): Promise<string | null> {
// In Electron mode, use the native getPath API directly from the preload script
// This returns the actual system Documents folder (e.g., C:\Users\<user>\Documents on Windows)
// Note: The HTTP client's getPath returns incorrect Unix-style paths for 'documents'
if (typeof window !== 'undefined' && (window as any).electronAPI?.getPath) {
const documentsPath = await (window as any).electronAPI.getPath('documents');
if (typeof window !== 'undefined' && window.electronAPI?.getPath) {
const documentsPath = await window.electronAPI.getPath('documents');
return joinPath(documentsPath, 'Automaker');
}