perf(projects): lazy-load TaskMaster metadata per selected project

Why:

- /api/projects is a hot path (initial load, sidebar refresh, websocket sync).

- Scanning .taskmaster for every project on each call added avoidable fs I/O and payload size.

- TaskMaster metadata is only needed after selecting a specific project.

- Moving it to a project-scoped endpoint makes loading cost match user intent.

- The UI now hydrates TaskMaster state on selection and keeps it across refresh events.

- This prevents status flicker/regression while still removing global scan overhead.

- Selection fetches are sequence-guarded to block stale async responses on fast switching.

- isManuallyAdded was removed from responses to keep the public project contract minimal.

- Project dumps now use incrementing snapshot files to preserve history for debugging.

What changed:

- Added GET /api/projects/:projectName/taskmaster and getProjectTaskMaster().

- Removed TaskMaster detection from bulk getProjects().

- Added api.projectTaskmaster(...) plus selection-time hydration in frontend contexts.

- Merged cached taskmaster values into refreshed project lists for continuity.

- Removed isManuallyAdded from manual project payloads.
This commit is contained in:
Haileyesus
2026-04-24 13:30:04 +03:00
parent f99af1ff67
commit 15171e1428
5 changed files with 300 additions and 75 deletions

View File

@@ -28,7 +28,7 @@ import { spawn } from 'child_process';
import pty from 'node-pty';
import mime from 'mime-types';
import { getProjects, getSessions, renameProject, deleteSession, deleteProject, extractProjectDirectory, clearProjectDirectoryCache, searchConversations } from './projects.js';
import { getProjects, getSessions, renameProject, deleteSession, deleteProject, getProjectTaskMaster, extractProjectDirectory, clearProjectDirectoryCache, searchConversations } from './projects.js';
import { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter } from './claude-sdk.js';
import { spawnCursor, abortCursorSession, isCursorSessionActive, getActiveCursorSessions } from './cursor-cli.js';
import { queryCodex, abortCodexSession, isCodexSessionActive, getActiveCodexSessions } from './openai-codex.js';
@@ -428,6 +428,16 @@ app.get('/api/projects', authenticateToken, async (req, res) => {
}
});
app.get('/api/projects/:projectName/taskmaster', authenticateToken, async (req, res) => {
try {
const { projectName } = req.params;
const taskMasterDetails = await getProjectTaskMaster(projectName);
res.json(taskMasterDetails);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/projects/:projectName/sessions', authenticateToken, async (req, res) => {
try {
const { limit = 5, offset = 0 } = req.query;