feat: enhance dashboard styling and functionality for task changes and package extensions

This commit is contained in:
gsxdsm
2026-04-01 22:06:12 -07:00
parent 101e4672a5
commit 64295637f2
10 changed files with 264 additions and 14 deletions

View File

@@ -116,7 +116,14 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
AuthStorage: {
create: vi.fn(() => mockAuthStorage),
},
DefaultPackageManager: vi.fn().mockImplementation(() => ({
resolve: vi.fn().mockResolvedValue({ extensions: [] }),
})),
ModelRegistry: vi.fn().mockImplementation(() => mockModelRegistry),
SettingsManager: {
create: vi.fn(() => ({})),
},
getAgentDir: vi.fn(() => "/mock/agent/dir"),
discoverAndLoadExtensions: mockDiscoverAndLoadExtensions,
createExtensionRuntime: mockCreateExtensionRuntime,
}));

View File

@@ -181,7 +181,14 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
AuthStorage: {
create: vi.fn(() => mockAuthStorage),
},
DefaultPackageManager: vi.fn().mockImplementation(() => ({
resolve: vi.fn().mockResolvedValue({ extensions: [] }),
})),
ModelRegistry: vi.fn().mockImplementation(() => mockModelRegistry),
SettingsManager: {
create: vi.fn(() => ({})),
},
getAgentDir: vi.fn(() => "/mock/agent/dir"),
discoverAndLoadExtensions: mockDiscoverAndLoadExtensions,
createExtensionRuntime: mockCreateExtensionRuntime,
}));

View File

@@ -5,7 +5,7 @@ import { TaskStore, AutomationStore } from "@fusion/core";
import type { Settings, TaskDetail, PrInfo } from "@fusion/core";
import { createServer, GitHubClient } from "@fusion/dashboard";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees, NtfyNotifier, PrMonitor, PrCommentHandler, CronRunner, StuckTaskDetector } from "@fusion/engine";
import { AuthStorage, ModelRegistry, discoverAndLoadExtensions, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager, discoverAndLoadExtensions, getAgentDir, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
/**
* Prompt the user for a port number interactively.
@@ -453,7 +453,23 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
const modelRegistry = new ModelRegistry(authStorage);
try {
const extensionsResult = await discoverAndLoadExtensions([], cwd, undefined);
// Resolve extension paths from pi settings packages (npm, git, local).
// This picks up extensions like @howaboua/pi-glm-via-anthropic that
// register custom providers (e.g. glm-5.1) via registerProvider().
const agentDir = getAgentDir();
const piSettingsManager = SettingsManager.create(cwd, agentDir);
const packageManager = new DefaultPackageManager({
cwd,
agentDir,
settingsManager: piSettingsManager,
});
const resolvedPaths = await packageManager.resolve();
const packageExtensionPaths = resolvedPaths.extensions
.filter((r) => r.enabled)
.map((r) => r.path);
// Load all extensions: filesystem-discovered + package-resolved
const extensionsResult = await discoverAndLoadExtensions(packageExtensionPaths, cwd, undefined);
for (const { path, error } of extensionsResult.errors) {
console.log(`[extensions] Failed to load ${path}: ${error}`);

View File

@@ -111,7 +111,7 @@ export function ChangedFilesModal({ taskId, worktree, column, isOpen, onClose }:
) : null}
{selectedFile ? (
<div className="gm-diff-section" aria-label={`Diff for ${selectedFile.path}`}>
<div className="gm-diff-section changed-files-diff-section" aria-label={`Diff for ${selectedFile.path}`}>
<div className="file-browser-toolbar">
<div className="file-browser-file-info">
<strong>{selectedFile.path}</strong>

View File

@@ -17,6 +17,7 @@ import {
Unlink,
Play,
} from "lucide-react";
import type { ToastType } from "../hooks/useToast";
import type {
Mission,
MissionWithHierarchy,
@@ -29,8 +30,7 @@ import type {
FeatureStatus,
MilestoneWithSlices,
SliceWithFeatures,
} from "../api";
import type { ToastType } from "../hooks/useToast";
} from "./mission-types";
import {
fetchMissions,
createMission,

View File

@@ -82,7 +82,7 @@ export function TaskChangesTab({ taskId, worktree }: TaskChangesTabProps) {
if (loading) {
return (
<div className="detail-section">
<div className="detail-loading">
<div className="task-changes-state task-changes-state--loading">
<div className="loading-spinner" />
<span>Loading changes...</span>
</div>
@@ -93,7 +93,7 @@ export function TaskChangesTab({ taskId, worktree }: TaskChangesTabProps) {
if (error) {
return (
<div className="detail-section">
<div className="detail-error">
<div className="task-changes-state task-changes-state--error">
<AlertCircle size={16} />
<span>Error loading changes: {error}</span>
</div>
@@ -104,10 +104,10 @@ export function TaskChangesTab({ taskId, worktree }: TaskChangesTabProps) {
if (!worktree) {
return (
<div className="detail-section">
<div className="detail-empty-state">
<div className="task-changes-state task-changes-state--empty">
<FileCode size={24} />
<p>No worktree available for this task.</p>
<span className="detail-empty-hint">
<span className="task-changes-state-hint">
Changes will be shown once the task is in progress.
</span>
</div>
@@ -118,10 +118,10 @@ export function TaskChangesTab({ taskId, worktree }: TaskChangesTabProps) {
if (!diffData || diffData.files.length === 0) {
return (
<div className="detail-section">
<div className="detail-empty-state">
<div className="task-changes-state task-changes-state--empty">
<FileCode size={24} />
<p>No files modified.</p>
<span className="detail-empty-hint">
<span className="task-changes-state-hint">
The agent did not modify any files during execution.
</span>
</div>
@@ -174,7 +174,10 @@ export function TaskChangesTab({ taskId, worktree }: TaskChangesTabProps) {
<span className="changes-file-path" title={path}>
{path}
</span>
<span className="changes-file-stat" title={`+${fileEntry.additions} -${fileEntry.deletions}`}>
<span
className="changes-file-stat"
title={`+${fileEntry.additions} -${fileEntry.deletions}${fileEntry.size ? ` · ${formatFileSize(fileEntry.size)}` : ""}`}
>
+{fileEntry.additions} -{fileEntry.deletions}
</span>
</button>

View File

@@ -1,4 +1,4 @@
// Local type definitions for MissionManager
// Mission types for MissionManager - local copy to avoid module resolution issues
export type MissionStatus = "planning" | "active" | "blocked" | "complete" | "archived";
export type MilestoneStatus = "planning" | "active" | "blocked" | "complete";
@@ -41,6 +41,8 @@ export interface Slice {
features: MissionFeature[];
}
export type SliceWithFeatures = Slice;
export interface Milestone {
id: string;
missionId: string;
@@ -55,6 +57,8 @@ export interface Milestone {
slices: Slice[];
}
export type MilestoneWithSlices = Milestone;
export interface MissionWithHierarchy extends Mission {
milestones: Milestone[];
}

View File

@@ -2759,6 +2759,149 @@ body {
margin-top: 16px;
}
.task-changes-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-sm);
padding: var(--space-xl);
min-height: 180px;
border: 1px dashed var(--border);
border-radius: var(--radius-md);
background: var(--card);
text-align: center;
color: var(--text-muted);
}
.task-changes-state--loading,
.task-changes-state--error {
flex-direction: row;
min-height: auto;
padding: var(--space-md);
justify-content: flex-start;
border-style: solid;
}
.task-changes-state--error {
color: var(--color-error);
background: rgba(248, 81, 73, 0.08);
border-color: rgba(248, 81, 73, 0.25);
}
.task-changes-state-hint {
font-size: 12px;
color: var(--text-dim);
max-width: 440px;
line-height: 1.5;
}
.task-changes-tab {
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.changes-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
}
.changes-header h4 {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
margin: 0;
}
.changes-file-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.changes-file-item {
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--card);
overflow: hidden;
}
.changes-file-item.expanded {
border-color: var(--text-dim);
}
.changes-file-header {
width: 100%;
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
background: transparent;
border: none;
color: var(--text);
text-align: left;
cursor: pointer;
}
.changes-file-header:hover {
background: var(--card-hover);
}
.changes-file-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
flex-shrink: 0;
}
.changes-file-status {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
font-family: var(--font-mono);
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.changes-file-path {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--font-mono);
font-size: 12px;
}
.changes-file-stat {
flex-shrink: 0;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
}
.changes-file-content {
border-top: 1px solid var(--border);
}
.changes-diff-patch {
margin: 0;
padding: var(--space-sm) var(--space-md);
background: rgba(0, 0, 0, 0.14);
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
}
/* Summary section - styled for done tasks with success accent */
.detail-summary {
background: rgba(63, 185, 80, 0.08);
@@ -2796,6 +2939,34 @@ body {
/* Mobile responsive for spec tab */
@media (max-width: 768px) {
.changed-files-layout {
grid-template-columns: 1fr;
}
.changed-files-sidebar {
border-right: none;
border-bottom: 1px solid var(--border);
max-height: 220px;
}
.changed-files-content {
padding: var(--space-sm);
}
.changes-header {
flex-direction: column;
align-items: flex-start;
}
.changes-file-header {
flex-wrap: wrap;
}
.changes-file-stat {
width: 100%;
padding-left: 30px;
}
.detail-section--spec {
flex: 1;
min-height: 0;
@@ -11623,10 +11794,15 @@ html .column.drag-over * {
gap: var(--space-sm);
}
.changed-files-diff-section {
height: 100%;
}
.gm-diff-viewer {
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow: hidden;
background: var(--card);
}
.gm-diff-stat {
@@ -11647,7 +11823,7 @@ html .column.drag-over * {
font-size: 11px;
line-height: 1.5;
color: var(--text);
max-height: 300px;
max-height: 420px;
overflow: auto;
margin: 0;
white-space: pre-wrap;
@@ -11669,6 +11845,31 @@ html .column.drag-over * {
font-size: 13px;
}
.changed-files-layout {
display: grid;
grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
min-height: 60vh;
}
.changed-files-sidebar {
border-right: 1px solid var(--border);
background: var(--surface);
}
.changed-files-content {
padding: var(--space-md);
min-width: 0;
}
.changed-files-entry {
justify-content: flex-start;
gap: var(--space-sm);
}
.changed-files-badge {
margin-left: auto;
}
/* ── Commit Form ── */
.gm-commit-form {