Files
Aperant/apps/frontend/src/preload/api/modules/roadmap-api.ts
T
Andy 4937d57453 auto-claude: 148-add-progress-persistence-and-status-indicators (#1464)
* auto-claude: subtask-1-1 - Extend RoadmapGenerationStatus type with startedAt and lastActivityAt

* auto-claude: subtask-1-2 - Add IPC channels for progress persistence: ROADMAP_PROGRESS_SAVE, ROADMAP_PROGRESS_LOAD, ROADMAP_PROGRESS_CLEAR

* auto-claude: subtask-1-3 - Add GENERATION_PROGRESS constant to AUTO_BUILD_PATHS

* auto-claude: subtask-2-1 - Add IPC handlers for roadmap progress persistence

Add three IPC handlers in roadmap-handlers.ts:
- ROADMAP_PROGRESS_SAVE: Persist progress state to generation_progress.json
- ROADMAP_PROGRESS_LOAD: Load persisted progress state from disk
- ROADMAP_PROGRESS_CLEAR: Delete the progress file on completion/error/stop

Follows existing patterns with snake_case JSON files and camelCase frontend.

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

* auto-claude: subtask-2-2 - Update agent-queue.ts to persist progress updates

* auto-claude: subtask-3-1 - Add preload API methods for progress persistence

Add saveRoadmapProgress, loadRoadmapProgress, and clearRoadmapProgress methods
to RoadmapAPI interface and implementation. These methods use the IPC channels
defined in subtask-1-2 to enable the renderer process to persist and restore
roadmap generation state.

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

* auto-claude: subtask-4-1 - Update loadRoadmap function to load persisted prog

- Update loadRoadmap to load persisted progress via loadRoadmapProgress API
- Restore startedAt and lastActivityAt timestamps when is_running is true
- Add fallback with current timestamps when no persisted progress found
- Add roadmap progress persistence methods to ElectronAPI interface
- Add browser mock implementations for progress persistence methods

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

* auto-claude: subtask-4-2 - Update setGenerationStatus action to include times

Updated setGenerationStatus action in roadmap-store.ts to automatically
manage timestamp fields:
- Sets startedAt when transitioning from idle to active phase
- Updates lastActivityAt on every status change during generation
- Clears both timestamps when generation stops (idle/complete/error)
- Preserves existing startedAt during active generation phases

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

* auto-claude: subtask-5-1 - Add elapsed time display with formatElapsedTime utility

- Add formatElapsedTime utility function for MM:SS and H:MM:SS formatting
- Add elapsedTime state with useEffect interval for real-time updates
- Display elapsed time with Clock icon next to progress indicator
- Calculate elapsed time from RoadmapGenerationStatus.startedAt field
- Use useCallback for memoized calculation function
- Clean up interval on phase change or component unmount
- Reset elapsed time when returning to idle phase

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

* auto-claude: subtask-5-2 - Add last activity timestamp display with formatTimeAgo utility

- Added formatTimeAgo utility function that formats timestamps into human-readable
  relative time strings (e.g., "just now", "5s ago", "2m ago", "1h ago")
- Added lastActivityDisplay state with useEffect interval to update every 5 seconds
- Display last activity timestamp next to elapsed time in progress bar section
- Added tooltip explaining "Last progress update received"
- Uses muted styling to differentiate from elapsed time

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

* auto-claude: subtask-5-3 - Add heartbeat animation indicator that pulses subtly

- Add HeartbeatIndicator component with subtle scale pulse (1.05x) animation
- Show "Processing" status with animated dot to indicate process is alive
- Respect useReducedMotion preference by disabling animation when enabled
- Integrate indicator into progress bar section next to percentage display

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

* auto-claude: subtask-6-1 - Add translation keys for roadmap progress UI text:

- Add roadmapProgress section with elapsedTime, lastActivity, staleWarning keys
- Add staleWarningTooltip with interpolation for minutes
- Add French translations for all new keys

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

* auto-claude: subtask-6-2 - Update RoadmapGenerationProgress to use translation keys

- Add useTranslation hook from react-i18next
- Convert hardcoded phase labels and descriptions to translation keys
- Convert step labels to translation keys
- Translate button text, tooltips, and progress labels
- Add translation keys to en/common.json and fr/common.json
- Pass translation function to child components

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

* fix: preserve persisted timestamps when restoring roadmap progress state

- Fix startedAt being overwritten with current time on reload by using
  status.startedAt ?? now when starting generation
- Fix lastActivityAt always being overwritten by using
  status.lastActivityAt ?? now to preserve passed timestamps
- Add documentation comment for SAVE/CLEAR IPC handlers explaining their
  purpose for API completeness

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

* fix: align IPC progress types and add validation

- Add PersistedRoadmapProgress type for IPC transport with string timestamps
- Update loadRoadmapProgress return type to use PersistedRoadmapProgress
- Remove unused isRunning field from persisted progress
- Add validation for JSON structure before using parsed data

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

* fix: validate phase value against allowed values when loading progress

Add validation to ensure the phase field from persisted progress file
matches one of the expected values (idle, analyzing, discovering,
generating, complete, error). Prevents TypeError in frontend component
when corrupted or manually edited files contain invalid phase values.

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

* fix: align progress persistence types and add date validation

- Update saveRoadmapProgress to use PersistedRoadmapProgress type
- Derive isRunning from phase instead of requiring it as parameter
- Add date validation when parsing persisted timestamps to handle
  corrupted date strings gracefully (returns current time as fallback)

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

* test: increase subprocess-spawn test timeout for Windows CI

Increase timeout from 15s to 30s for all subprocess spawn integration
tests. Dynamic imports are slower on Windows CI, causing intermittent
timeouts.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:30:24 +01:00

120 lines
4.5 KiB
TypeScript

import { IPC_CHANNELS } from '../../../shared/constants';
import type {
Roadmap,
RoadmapFeatureStatus,
RoadmapGenerationStatus,
PersistedRoadmapProgress,
Task,
IPCResult
} from '../../../shared/types';
import { createIpcListener, invokeIpc, sendIpc, IpcListenerCleanup } from './ipc-utils';
/**
* Roadmap API operations
*/
export interface RoadmapAPI {
// Operations
getRoadmap: (projectId: string) => Promise<IPCResult<Roadmap | null>>;
getRoadmapStatus: (projectId: string) => Promise<IPCResult<{ isRunning: boolean }>>;
saveRoadmap: (projectId: string, roadmap: Roadmap) => Promise<IPCResult>;
generateRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean, refreshCompetitorAnalysis?: boolean) => void;
refreshRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean, refreshCompetitorAnalysis?: boolean) => void;
stopRoadmap: (projectId: string) => Promise<IPCResult>;
updateFeatureStatus: (
projectId: string,
featureId: string,
status: RoadmapFeatureStatus
) => Promise<IPCResult>;
convertFeatureToSpec: (
projectId: string,
featureId: string
) => Promise<IPCResult<Task>>;
// Progress persistence
saveRoadmapProgress: (projectId: string, progress: PersistedRoadmapProgress) => Promise<IPCResult>;
loadRoadmapProgress: (projectId: string) => Promise<IPCResult<PersistedRoadmapProgress | null>>;
clearRoadmapProgress: (projectId: string) => Promise<IPCResult>;
// Event Listeners
onRoadmapProgress: (
callback: (projectId: string, status: RoadmapGenerationStatus) => void
) => IpcListenerCleanup;
onRoadmapComplete: (
callback: (projectId: string, roadmap: Roadmap) => void
) => IpcListenerCleanup;
onRoadmapError: (
callback: (projectId: string, error: string) => void
) => IpcListenerCleanup;
onRoadmapStopped: (
callback: (projectId: string) => void
) => IpcListenerCleanup;
}
/**
* Creates the Roadmap API implementation
*/
export const createRoadmapAPI = (): RoadmapAPI => ({
// Operations
getRoadmap: (projectId: string): Promise<IPCResult<Roadmap | null>> =>
invokeIpc(IPC_CHANNELS.ROADMAP_GET, projectId),
getRoadmapStatus: (projectId: string): Promise<IPCResult<{ isRunning: boolean }>> =>
invokeIpc(IPC_CHANNELS.ROADMAP_GET_STATUS, projectId),
saveRoadmap: (projectId: string, roadmap: Roadmap): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.ROADMAP_SAVE, projectId, roadmap),
generateRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean, refreshCompetitorAnalysis?: boolean): void =>
sendIpc(IPC_CHANNELS.ROADMAP_GENERATE, projectId, enableCompetitorAnalysis, refreshCompetitorAnalysis),
refreshRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean, refreshCompetitorAnalysis?: boolean): void =>
sendIpc(IPC_CHANNELS.ROADMAP_REFRESH, projectId, enableCompetitorAnalysis, refreshCompetitorAnalysis),
stopRoadmap: (projectId: string): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.ROADMAP_STOP, projectId),
updateFeatureStatus: (
projectId: string,
featureId: string,
status: RoadmapFeatureStatus
): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.ROADMAP_UPDATE_FEATURE, projectId, featureId, status),
convertFeatureToSpec: (
projectId: string,
featureId: string
): Promise<IPCResult<Task>> =>
invokeIpc(IPC_CHANNELS.ROADMAP_CONVERT_TO_SPEC, projectId, featureId),
// Progress persistence
saveRoadmapProgress: (projectId: string, progress: PersistedRoadmapProgress): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.ROADMAP_PROGRESS_SAVE, projectId, progress),
loadRoadmapProgress: (projectId: string): Promise<IPCResult<PersistedRoadmapProgress | null>> =>
invokeIpc(IPC_CHANNELS.ROADMAP_PROGRESS_LOAD, projectId),
clearRoadmapProgress: (projectId: string): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.ROADMAP_PROGRESS_CLEAR, projectId),
// Event Listeners
onRoadmapProgress: (
callback: (projectId: string, status: RoadmapGenerationStatus) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.ROADMAP_PROGRESS, callback),
onRoadmapComplete: (
callback: (projectId: string, roadmap: Roadmap) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.ROADMAP_COMPLETE, callback),
onRoadmapError: (
callback: (projectId: string, error: string) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.ROADMAP_ERROR, callback),
onRoadmapStopped: (
callback: (projectId: string) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.ROADMAP_STOPPED, callback)
});