feat(dashboard): rearrange agent + task detail panels

- AgentsView: agent metrics bar moves to the top of the scrolling
  content (was below the agent list); token-usage panel moves into
  the controls dropdown next to the heartbeat slider, which is now
  scrollable so the added content fits on short viewports.
- AgentsView mobile: 2x2 grid for the metric cards with tighter
  padding and smaller value/label text so labels don't crowd off
  the card on narrow phones.
- TaskDetailModal: new "Stats" tab at the end of the fixed tab list
  hosts the token-usage panel, removed from the inline definition
  body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-25 09:50:06 -07:00
parent 84fb696c86
commit a4b4cdb4d4
3 changed files with 43 additions and 10 deletions

View File

@@ -834,6 +834,29 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
/* Stats cards: drop to a 2x2 grid with tighter padding so they
don't squeeze the labels off the card on narrow phones. */
.agent-metrics-bar {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
.agent-metric-card {
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
min-width: 0;
}
.agent-metric-value {
font-size: 16px;
}
.agent-metric-label {
font-size: calc(var(--space-sm) + var(--space-xs) * 0.5);
}
.agent-org-chart { .agent-org-chart {
padding: var(--space-sm); padding: var(--space-sm);
padding-bottom: calc(var(--space-lg) + var(--space-sm)); padding-bottom: calc(var(--space-lg) + var(--space-sm));

View File

@@ -749,10 +749,11 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
<div <div
ref={controlsPanelRef} ref={controlsPanelRef}
id={controlsPanelId} id={controlsPanelId}
className="agent-controls-panel" className="agent-controls-panel agent-controls-panel--scrollable"
role="dialog" role="dialog"
aria-label="Agent controls" aria-label="Agent controls"
aria-modal="false" aria-modal="false"
style={{ maxHeight: "70vh", overflowY: "auto" }}
> >
<div className="agent-controls"> <div className="agent-controls">
<div className="agent-controls-filters"> <div className="agent-controls-filters">
@@ -846,11 +847,15 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
</small> </small>
</div> </div>
</div> </div>
<AgentTokenStatsPanel agents={displayAgents} />
</div> </div>
)} )}
<div className="agents-view-content"> <div className="agents-view-content">
<AgentMetricsBar stats={stats} />
<NewAgentDialog <NewAgentDialog
isOpen={isCreating} isOpen={isCreating}
onClose={() => setIsCreating(false)} onClose={() => setIsCreating(false)}
@@ -1266,8 +1271,6 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
)} )}
{/* Secondary sections after the main collection */} {/* Secondary sections after the main collection */}
<AgentMetricsBar stats={stats} />
<AgentTokenStatsPanel agents={displayAgents} />
<ActiveAgentsPanel agents={activeAgents} projectId={projectId} onAgentSelect={setSelectedAgentId} /> <ActiveAgentsPanel agents={activeAgents} projectId={projectId} onAgentSelect={setSelectedAgentId} />
</div> </div>

View File

@@ -157,7 +157,7 @@ function formatBytes(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
} }
type TabId = "definition" | "logs" | "changes" | "comments" | "model" | "workflow" | "documents" | `plugin-${string}`; type TabId = "definition" | "logs" | "changes" | "comments" | "model" | "workflow" | "documents" | "stats" | `plugin-${string}`;
interface TaskDetailModalProps { interface TaskDetailModalProps {
task: Task | TaskDetail; task: Task | TaskDetail;
@@ -1286,6 +1286,12 @@ export function TaskDetailModal({
> >
Workflow Workflow
</button> </button>
<button
className={`detail-tab${activeTab === "stats" ? " detail-tab-active" : ""}`}
onClick={() => setActiveTab("stats")}
>
Stats
</button>
{/* Plugin tabs */} {/* Plugin tabs */}
{pluginTabSlots.map((entry, index) => { {pluginTabSlots.map((entry, index) => {
const pluginTabId = `plugin-${index}` as TabId; const pluginTabId = `plugin-${index}` as TabId;
@@ -1391,6 +1397,13 @@ export function TaskDetailModal({
<div className="detail-section"> <div className="detail-section">
<PluginSlot slotId="task-detail-tab" projectId={projectId} /> <PluginSlot slotId="task-detail-tab" projectId={projectId} />
</div> </div>
) : activeTab === "stats" ? (
<div className="detail-section">
<TaskTokenStatsPanel
tokenUsage={workingTask.tokenUsage}
loading={detailLoading}
/>
</div>
) : ( ) : (
<> <>
{/* Summary section - only for done tasks with summary */} {/* Summary section - only for done tasks with summary */}
@@ -1482,12 +1495,6 @@ export function TaskDetailModal({
<div className="step-progress-empty">(no steps defined)</div> <div className="step-progress-empty">(no steps defined)</div>
)} )}
</div> </div>
<div className="detail-section">
<TaskTokenStatsPanel
tokenUsage={workingTask.tokenUsage}
loading={detailLoading}
/>
</div>
<div className="detail-section"> <div className="detail-section">
{!isEditingSpec && ( {!isEditingSpec && (
<div className="detail-spec-edit-trigger"> <div className="detail-spec-edit-trigger">