feat(tui): git view, project-scoped task stats, polished layout/theme
- Add a Git interactive view (hotkey [t]/[4]): branch + ahead/behind,
recent commits with detail strip, staged/unstaged/untracked files
panel, branches list, worktrees panel, and a [P] push modal with
pre-flight commit list and capture of stdout/stderr. ←→ cycles
status → branches → worktrees → commits → changes; ↑↓ navigates
rows in the focused list. 5s poll while mounted.
- Project-scoped task stats: BoardView pushes its selected project
path into the controller; refreshTUIStats now reads from a per-
project TaskStore via a shared getProjectStore helper. Project
switch triggers an immediate refresh via onBoardScopeChange.
- Unified MainHeader used by status and interactive modes — section
tabs ([1]–[5]) and interactive view tabs ([b]/[a]/[g]/[t]) always
visible; previously the interactive header replaced the main one.
- Logs panel keeps its size when expanding a single entry.
- 'f' cycles severity filter from any panel in status mode.
- BoardView: explicit cross-view shortcuts so g/a/t always switch
views regardless of input-handler ordering.
- BoardView column overlap fix: flexShrink={0} on structural rows.
- TaskCard always shows the title with wrap="wrap"; single short-id
pill, no duplicate id-as-title fallback.
- Stats panel reorg: StatRow helper, bold section headers, narrow-
width wrap for Heap and Memory trailing fragments.
- Lighter blue palette: cyanBright fg accents / cyan active bg /
logo gradient whiteBright → white → cyanBright → cyan → blue.
- System stats sampler: RSS, heap (V8 limit-aware color), external,
CPU%, load avg, system used/free. Heap thresholds scale off
--max-old-space-size automatically.
- Test fixture updated for the new InteractiveData.git block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,34 @@ function makeInteractiveData(opts: {
|
||||
getSettings: async () => settings,
|
||||
updateSettings: async (_partial: Partial<SettingsValues>) => {},
|
||||
listModels: () => models,
|
||||
git: {
|
||||
getStatus: async () => ({
|
||||
branch: "main",
|
||||
detached: false,
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
staged: [],
|
||||
unstaged: [],
|
||||
untracked: [],
|
||||
remoteUrl: "",
|
||||
lastFetchAt: null,
|
||||
}),
|
||||
listCommits: async () => [],
|
||||
showCommit: async () => ({
|
||||
sha: "",
|
||||
shortSha: "",
|
||||
subject: "",
|
||||
authorName: "",
|
||||
relativeTime: "",
|
||||
isoTime: "",
|
||||
body: "",
|
||||
stat: "",
|
||||
}),
|
||||
listBranches: async () => [],
|
||||
listWorktrees: async () => [],
|
||||
push: async () => ({ success: true, output: "" }),
|
||||
fetch: async () => ({ success: true, output: "" }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,10 @@ export class DashboardTUI {
|
||||
systemInfo: SystemInfo | null = null;
|
||||
taskStats: TaskStats | null = null;
|
||||
systemStats: SystemStats | null = null;
|
||||
// When set, dashboard.ts refreshes task stats from this project path
|
||||
// instead of the launch cwd. Mirrors BoardView's selected project.
|
||||
boardScopedProjectPath: string | null = null;
|
||||
private boardScopeListener: ((path: string | null) => void) | null = null;
|
||||
settings: SettingsValues | null = null;
|
||||
callbacks: TUICallbacks | null = null;
|
||||
isRunning = false;
|
||||
@@ -132,6 +136,20 @@ export class DashboardTUI {
|
||||
this.notify();
|
||||
}
|
||||
|
||||
setBoardScopedProjectPath(path: string | null): void {
|
||||
if (this.boardScopedProjectPath === path) return;
|
||||
this.boardScopedProjectPath = path;
|
||||
this.boardScopeListener?.(path);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
onBoardScopeChange(listener: (path: string | null) => void): () => void {
|
||||
this.boardScopeListener = listener;
|
||||
return () => {
|
||||
if (this.boardScopeListener === listener) this.boardScopeListener = null;
|
||||
};
|
||||
}
|
||||
|
||||
/** Sample process memory + CPU% in-place. Called from the sampler timer. */
|
||||
sampleSystemStats(): void {
|
||||
const mem = process.memoryUsage();
|
||||
|
||||
@@ -17,4 +17,9 @@ export type {
|
||||
InteractiveData,
|
||||
ProjectItem,
|
||||
TaskItem,
|
||||
GitStatus,
|
||||
GitCommit,
|
||||
GitCommitDetail,
|
||||
GitBranch,
|
||||
GitWorktree,
|
||||
} from "./state.js";
|
||||
|
||||
@@ -8,7 +8,7 @@ export type SectionId = "logs" | "system" | "utilities" | "stats" | "settings";
|
||||
|
||||
export type AppMode = "status" | "interactive";
|
||||
|
||||
export type InteractiveView = "board" | "agents" | "settings";
|
||||
export type InteractiveView = "board" | "agents" | "settings" | "git";
|
||||
|
||||
export interface SystemInfo {
|
||||
host: string;
|
||||
@@ -124,6 +124,50 @@ export interface ModelItem {
|
||||
contextWindow: number;
|
||||
}
|
||||
|
||||
// ── Git view types ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface GitStatus {
|
||||
branch: string;
|
||||
detached: boolean;
|
||||
ahead: number;
|
||||
behind: number;
|
||||
staged: Array<{ status: string; path: string }>;
|
||||
unstaged: Array<{ status: string; path: string }>;
|
||||
untracked: Array<{ path: string }>;
|
||||
remoteUrl: string;
|
||||
lastFetchAt: number | null;
|
||||
}
|
||||
|
||||
export interface GitCommit {
|
||||
sha: string;
|
||||
shortSha: string;
|
||||
subject: string;
|
||||
authorName: string;
|
||||
relativeTime: string;
|
||||
isoTime: string;
|
||||
}
|
||||
|
||||
export interface GitCommitDetail extends GitCommit {
|
||||
body: string;
|
||||
stat: string;
|
||||
}
|
||||
|
||||
export interface GitBranch {
|
||||
name: string;
|
||||
shortSha: string;
|
||||
relativeTime: string;
|
||||
isCurrent: boolean;
|
||||
upstreamTrack: string;
|
||||
}
|
||||
|
||||
export interface GitWorktree {
|
||||
path: string;
|
||||
branch: string;
|
||||
sha: string;
|
||||
isCurrent: boolean;
|
||||
isLocked: boolean;
|
||||
}
|
||||
|
||||
export interface InteractiveData {
|
||||
listProjects: () => Promise<ProjectItem[]>;
|
||||
listTasks: (projectPath: string) => Promise<TaskItem[]>;
|
||||
@@ -135,6 +179,15 @@ export interface InteractiveData {
|
||||
getSettings: () => Promise<SettingsValues>;
|
||||
updateSettings: (partial: Partial<SettingsValues>) => Promise<void>;
|
||||
listModels: () => ModelItem[];
|
||||
git: {
|
||||
getStatus: (projectPath: string) => Promise<GitStatus>;
|
||||
listCommits: (projectPath: string, limit?: number) => Promise<GitCommit[]>;
|
||||
showCommit: (projectPath: string, sha: string) => Promise<GitCommitDetail>;
|
||||
listBranches: (projectPath: string) => Promise<GitBranch[]>;
|
||||
listWorktrees: (projectPath: string) => Promise<GitWorktree[]>;
|
||||
push: (projectPath: string) => Promise<{ success: boolean; output: string }>;
|
||||
fetch: (projectPath: string) => Promise<{ success: boolean; output: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
// ── Dashboard state (mutable, shared between controller and App) ───────────────
|
||||
|
||||
Reference in New Issue
Block a user