feat(KB-618): add multi-project support to dashboard

- Add project API methods and types (fetchProjects, registerProject, fetchProjectHealth, etc.)
- Add ProjectCard component with health metrics, status badges, and pause/resume actions
- Add server-side project management routes for multi-project orchestration
- Add ActivityFeed component with grouped entries and project badges
- Add SetupWizard component with 5-step project creation flow
- Fix TypeScript errors in server routes for listProjects and getGlobalConcurrencyState
This commit is contained in:
gsxdsm
2026-03-31 23:19:03 -07:00
parent fa9b277c32
commit 4b39bf1f4c
52 changed files with 498 additions and 1643 deletions

View File

@@ -133,7 +133,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
blockedBy: row.blockedBy || undefined,
paused: row.paused ? true : undefined,
baseBranch: row.baseBranch || undefined,
baseCommitSha: row.baseCommitSha || undefined,
modelPresetId: row.modelPresetId || undefined,
modelProvider: row.modelProvider || undefined,
modelId: row.modelId || undefined,
@@ -158,7 +157,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
mergeDetails: fromJson<import("./types.js").MergeDetails>(row.mergeDetails),
breakIntoSubtasks: row.breakIntoSubtasks ? true : undefined,
enabledWorkflowSteps: (() => { const e = fromJson<string[]>(row.enabledWorkflowSteps); return e && e.length > 0 ? e : undefined; })(),
modifiedFiles: (() => { const m = fromJson<string[]>(row.modifiedFiles); return m && m.length > 0 ? m : undefined; })(),
};
}
@@ -169,15 +167,15 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
this.db.prepare(`
INSERT OR REPLACE INTO tasks (
id, title, description, "column", status, size, reviewLevel, currentStep,
worktree, blockedBy, paused, baseBranch, baseCommitSha, modelPresetId, modelProvider,
worktree, blockedBy, paused, baseBranch, modelPresetId, modelProvider,
modelId, validatorModelProvider, validatorModelId, mergeRetries, error,
summary, thinkingLevel, createdAt, updatedAt, columnMovedAt,
dependencies, steps, log, attachments, steeringComments,
comments, workflowStepResults, prInfo, issueInfo, mergeDetails,
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles
breakIntoSubtasks, enabledWorkflowSteps
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
`).run(
task.id,
@@ -192,7 +190,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
task.blockedBy ?? null,
task.paused ? 1 : 0,
task.baseBranch ?? null,
task.baseCommitSha ?? null,
task.modelPresetId ?? null,
task.modelProvider ?? null,
task.modelId ?? null,
@@ -217,7 +214,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
toJsonNullable(task.mergeDetails),
task.breakIntoSubtasks ? 1 : 0,
toJson(task.enabledWorkflowSteps || []),
toJson(task.modifiedFiles || []),
);
this.db.bumpLastModified();
}
@@ -879,7 +875,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async updateTask(
id: string,
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; baseCommitSha?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null },
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null },
): Promise<Task> {
return this.withTaskLock(id, async () => {
// Validate that task doesn't depend on itself
@@ -929,7 +925,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
if (updates.paused !== undefined) task.paused = updates.paused || undefined;
if (updates.baseBranch !== undefined) task.baseBranch = updates.baseBranch;
if (updates.baseCommitSha !== undefined) task.baseCommitSha = updates.baseCommitSha;
if (updates.size !== undefined) task.size = updates.size;
if (updates.reviewLevel !== undefined) task.reviewLevel = updates.reviewLevel;
if (updates.mergeRetries !== undefined) task.mergeRetries = updates.mergeRetries;
@@ -968,11 +963,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
} else if (updates.workflowStepResults !== undefined) {
task.workflowStepResults = updates.workflowStepResults;
}
if (updates.modifiedFiles === null) {
task.modifiedFiles = undefined;
} else if (updates.modifiedFiles !== undefined) {
task.modifiedFiles = updates.modifiedFiles;
}
task.updatedAt = new Date().toISOString();
await this.atomicWriteTaskJson(dir, task);
@@ -1475,10 +1465,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
breakIntoSubtasks: task.breakIntoSubtasks,
paused: task.paused,
baseBranch: task.baseBranch,
baseCommitSha: task.baseCommitSha,
mergeRetries: task.mergeRetries,
error: task.error,
modifiedFiles: task.modifiedFiles,
};
// Write to archivedTasks table in SQLite
@@ -1693,18 +1681,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
this.recentlyWritten.clear();
}
/**
* Gracefully shut down: stop watching and close the database connection.
* Ensures WAL is checkpointed so no pending writes are lost.
*/
close(): void {
this.stopWatching();
if (this._db) {
this._db.close();
this._db = null;
}
}
/**
* Mark a file path as recently written by an in-process mutation
* so the watcher will skip it.
@@ -2319,10 +2295,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
breakIntoSubtasks: task.breakIntoSubtasks,
paused: task.paused,
baseBranch: task.baseBranch,
baseCommitSha: task.baseCommitSha,
mergeRetries: task.mergeRetries,
error: task.error,
modifiedFiles: task.modifiedFiles,
};
// Write to archivedTasks table
@@ -2385,8 +2359,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
validatorModelProvider: entry.validatorModelProvider,
validatorModelId: entry.validatorModelId,
breakIntoSubtasks: entry.breakIntoSubtasks,
modifiedFiles: entry.modifiedFiles,
// Intentionally NOT restoring: worktree, status, blockedBy, paused, baseBranch, baseCommitSha, error, steeringComments
// Intentionally NOT restoring: worktree, status, blockedBy, paused, baseBranch, error, steeringComments
};
// Write task.json