765 lines
22 KiB
Markdown
765 lines
22 KiB
Markdown
# Runtime Compact View Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add a compact/collapsed view mode to the AgentHealth runtime dashboard where each app is a small health-tinted card, expandable inline to the full agent table.
|
|
|
|
**Architecture:** Add `viewMode` and `expandedApps` state to `AgentHealth`. A `CompactAppCard` inline component renders the collapsed card. The existing `GroupCard`+`DataTable` renders expanded apps. A view toggle in the stat strip switches modes. All CSS uses design system variables with `color-mix()` for health tinting.
|
|
|
|
**Tech Stack:** React, CSS Modules, lucide-react, @cameleer/design-system components, localStorage
|
|
|
|
---
|
|
|
|
### Task 1: Add compact card CSS classes
|
|
|
|
**Files:**
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.module.css`
|
|
|
|
- [ ] **Step 1: Add compact grid class**
|
|
|
|
Append to `AgentHealth.module.css` after the `.groupGridSingle` block (line 94):
|
|
|
|
```css
|
|
/* Compact view grid */
|
|
.compactGrid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Add compact card base and health variant classes**
|
|
|
|
Append to `AgentHealth.module.css`:
|
|
|
|
```css
|
|
/* Compact app card */
|
|
.compactCard {
|
|
background: color-mix(in srgb, var(--card-accent) 8%, var(--bg-raised));
|
|
border: 1px solid color-mix(in srgb, var(--card-accent) 25%, transparent);
|
|
border-left: 3px solid var(--card-accent);
|
|
border-radius: var(--radius-md);
|
|
padding: 10px 12px;
|
|
cursor: pointer;
|
|
transition: background 150ms ease;
|
|
}
|
|
|
|
.compactCard:hover {
|
|
background: color-mix(in srgb, var(--card-accent) 12%, var(--bg-raised));
|
|
}
|
|
|
|
.compactCardSuccess { --card-accent: var(--success); }
|
|
.compactCardWarning { --card-accent: var(--warning); }
|
|
.compactCardError { --card-accent: var(--error); }
|
|
|
|
.compactCardHeader {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.compactCardName {
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.compactCardChevron {
|
|
color: var(--text-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.compactCardMeta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.compactCardLive {
|
|
font-weight: 600;
|
|
color: var(--card-accent);
|
|
}
|
|
|
|
.compactCardHeartbeat {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.compactCardHeartbeatWarn {
|
|
color: var(--card-accent);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Add expanded-in-compact-grid span class**
|
|
|
|
Append to `AgentHealth.module.css`:
|
|
|
|
```css
|
|
/* Expanded card inside compact grid */
|
|
.compactGridExpanded {
|
|
grid-column: span 2;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Add animation classes**
|
|
|
|
Append to `AgentHealth.module.css`:
|
|
|
|
```css
|
|
/* Expand/collapse animation wrapper */
|
|
.expandWrapper {
|
|
overflow: hidden;
|
|
transition: max-height 200ms ease, opacity 150ms ease;
|
|
}
|
|
|
|
.expandWrapperCollapsed {
|
|
max-height: 0;
|
|
opacity: 0;
|
|
}
|
|
|
|
.expandWrapperExpanded {
|
|
max-height: 1000px;
|
|
opacity: 1;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 5: Add view toggle classes and update stat strip**
|
|
|
|
Append to `AgentHealth.module.css`:
|
|
|
|
```css
|
|
/* View mode toggle */
|
|
.viewToggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.viewToggleBtn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border-subtle);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
transition: background 150ms ease, color 150ms ease;
|
|
}
|
|
|
|
.viewToggleBtn:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.viewToggleBtnActive {
|
|
background: var(--running);
|
|
border-color: var(--running);
|
|
color: #fff;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Update stat strip grid to add auto column**
|
|
|
|
In `AgentHealth.module.css`, change the `.statStrip` rule from:
|
|
|
|
```css
|
|
.statStrip {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 10px;
|
|
margin-bottom: 16px;
|
|
}
|
|
```
|
|
|
|
to:
|
|
|
|
```css
|
|
.statStrip {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr) auto;
|
|
gap: 10px;
|
|
margin-bottom: 16px;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 7: Add collapse button class for expanded cards**
|
|
|
|
Append to `AgentHealth.module.css`:
|
|
|
|
```css
|
|
/* Collapse button in expanded GroupCard header */
|
|
.collapseBtn {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
padding: 2px 4px;
|
|
border-radius: var(--radius-sm);
|
|
line-height: 1;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.collapseBtn:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add ui/src/pages/AgentHealth/AgentHealth.module.css
|
|
git commit -m "style: add compact view CSS classes for runtime dashboard"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Add view mode state and toggle to AgentHealth
|
|
|
|
**Files:**
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx:1-3` (imports)
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx:100-120` (state)
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx:339-402` (stat strip JSX)
|
|
|
|
- [ ] **Step 1: Add lucide-react imports**
|
|
|
|
In `AgentHealth.tsx`, change line 3 from:
|
|
|
|
```tsx
|
|
import { ExternalLink, RefreshCw, Pencil } from 'lucide-react';
|
|
```
|
|
|
|
to:
|
|
|
|
```tsx
|
|
import { ExternalLink, RefreshCw, Pencil, LayoutGrid, List, ChevronRight, ChevronDown } from 'lucide-react';
|
|
```
|
|
|
|
- [ ] **Step 2: Add view mode state**
|
|
|
|
In `AgentHealth.tsx`, after the `const [confirmDismissOpen, setConfirmDismissOpen] = useState(false);` line (line 116), add:
|
|
|
|
```tsx
|
|
const [viewMode, setViewMode] = useState<'compact' | 'expanded'>(() => {
|
|
const saved = localStorage.getItem('cameleer:runtime:viewMode');
|
|
return saved === 'expanded' ? 'expanded' : 'compact';
|
|
});
|
|
const [expandedApps, setExpandedApps] = useState<Set<string>>(new Set());
|
|
|
|
const toggleViewMode = useCallback((mode: 'compact' | 'expanded') => {
|
|
setViewMode(mode);
|
|
setExpandedApps(new Set());
|
|
localStorage.setItem('cameleer:runtime:viewMode', mode);
|
|
}, []);
|
|
|
|
const toggleAppExpanded = useCallback((appId: string) => {
|
|
setExpandedApps((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(appId)) next.delete(appId);
|
|
else next.add(appId);
|
|
return next;
|
|
});
|
|
}, []);
|
|
```
|
|
|
|
- [ ] **Step 3: Add view toggle to stat strip**
|
|
|
|
In `AgentHealth.tsx`, inside the stat strip `<div className={styles.statStrip}>`, after the last `<StatCard>` (the "Dead" one ending around line 401), add:
|
|
|
|
```tsx
|
|
<div className={styles.viewToggle}>
|
|
<button
|
|
className={`${styles.viewToggleBtn} ${viewMode === 'compact' ? styles.viewToggleBtnActive : ''}`}
|
|
onClick={() => toggleViewMode('compact')}
|
|
title="Compact view"
|
|
>
|
|
<LayoutGrid size={14} />
|
|
</button>
|
|
<button
|
|
className={`${styles.viewToggleBtn} ${viewMode === 'expanded' ? styles.viewToggleBtnActive : ''}`}
|
|
onClick={() => toggleViewMode('expanded')}
|
|
title="Expanded view"
|
|
>
|
|
<List size={14} />
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
- [ ] **Step 4: Verify the dev server compiles without errors**
|
|
|
|
Run: `cd ui && npx vite build --mode development 2>&1 | tail -5`
|
|
Expected: build succeeds (the toggle renders but has no visual effect on the grid yet)
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add ui/src/pages/AgentHealth/AgentHealth.tsx
|
|
git commit -m "feat: add view mode state and toggle to runtime dashboard"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Add CompactAppCard component and compact grid rendering
|
|
|
|
**Files:**
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx`
|
|
|
|
- [ ] **Step 1: Add lastHeartbeat helper to helpers section**
|
|
|
|
In `AgentHealth.tsx`, after the `appHealth()` function (after line 81), add:
|
|
|
|
```tsx
|
|
function latestHeartbeat(group: AppGroup): string | undefined {
|
|
let latest: string | undefined;
|
|
for (const inst of group.instances) {
|
|
if (inst.lastHeartbeat && (!latest || inst.lastHeartbeat > latest)) {
|
|
latest = inst.lastHeartbeat;
|
|
}
|
|
}
|
|
return latest;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Add CompactAppCard component**
|
|
|
|
In `AgentHealth.tsx`, after the `LOG_SOURCE_ITEMS` constant (after line 97), add:
|
|
|
|
```tsx
|
|
function CompactAppCard({ group, onExpand }: { group: AppGroup; onExpand: () => void }) {
|
|
const health = appHealth(group);
|
|
const heartbeat = latestHeartbeat(group);
|
|
const isHealthy = health === 'success';
|
|
|
|
const variantClass =
|
|
health === 'success' ? styles.compactCardSuccess
|
|
: health === 'warning' ? styles.compactCardWarning
|
|
: styles.compactCardError;
|
|
|
|
return (
|
|
<div
|
|
className={`${styles.compactCard} ${variantClass}`}
|
|
onClick={onExpand}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onExpand(); }}
|
|
>
|
|
<div className={styles.compactCardHeader}>
|
|
<span className={styles.compactCardName}>{group.appId}</span>
|
|
<ChevronRight size={14} className={styles.compactCardChevron} />
|
|
</div>
|
|
<div className={styles.compactCardMeta}>
|
|
<span className={styles.compactCardLive}>
|
|
{group.liveCount}/{group.instances.length} live
|
|
</span>
|
|
<span className={isHealthy ? styles.compactCardHeartbeat : styles.compactCardHeartbeatWarn}>
|
|
{heartbeat ? timeAgo(heartbeat) : '\u2014'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Replace the group cards grid with conditional rendering**
|
|
|
|
In `AgentHealth.tsx`, replace the entire group cards grid block (lines 515-569):
|
|
|
|
```tsx
|
|
{/* Group cards grid */}
|
|
<div className={isFullWidth ? styles.groupGridSingle : styles.groupGrid}>
|
|
{groups.map((group) => (
|
|
<GroupCard
|
|
...
|
|
</GroupCard>
|
|
))}
|
|
</div>
|
|
```
|
|
|
|
with:
|
|
|
|
```tsx
|
|
{/* Group cards grid */}
|
|
{viewMode === 'expanded' || isFullWidth ? (
|
|
<div className={isFullWidth ? styles.groupGridSingle : styles.groupGrid}>
|
|
{groups.map((group) => (
|
|
<GroupCard
|
|
key={group.appId}
|
|
title={group.appId}
|
|
accent={appHealth(group)}
|
|
headerRight={
|
|
<Badge
|
|
label={`${group.liveCount}/${group.instances.length} LIVE`}
|
|
color={appHealth(group)}
|
|
variant="filled"
|
|
/>
|
|
}
|
|
meta={
|
|
<div className={styles.groupMeta}>
|
|
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
|
|
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
|
|
<span>
|
|
<StatusDot
|
|
variant={
|
|
appHealth(group) === 'success'
|
|
? 'live'
|
|
: appHealth(group) === 'warning'
|
|
? 'stale'
|
|
: 'dead'
|
|
}
|
|
/>
|
|
</span>
|
|
</div>
|
|
}
|
|
footer={
|
|
group.deadCount > 0 ? (
|
|
<div className={styles.alertBanner}>
|
|
<span className={styles.alertIcon}>⚠</span>
|
|
<span>
|
|
Single point of failure —{' '}
|
|
{group.deadCount === group.instances.length
|
|
? 'no redundancy'
|
|
: `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
|
|
</span>
|
|
</div>
|
|
) : undefined
|
|
}
|
|
>
|
|
<DataTable<AgentInstance & { id: string }>
|
|
columns={instanceColumns as Column<AgentInstance & { id: string }>[]}
|
|
data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
|
|
onRowClick={handleInstanceClick}
|
|
pageSize={50}
|
|
flush
|
|
/>
|
|
</GroupCard>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className={styles.compactGrid}>
|
|
{groups.map((group) =>
|
|
expandedApps.has(group.appId) ? (
|
|
<div key={group.appId} className={styles.compactGridExpanded}>
|
|
<GroupCard
|
|
title={group.appId}
|
|
accent={appHealth(group)}
|
|
headerRight={
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
<Badge
|
|
label={`${group.liveCount}/${group.instances.length} LIVE`}
|
|
color={appHealth(group)}
|
|
variant="filled"
|
|
/>
|
|
<button
|
|
className={styles.collapseBtn}
|
|
onClick={() => toggleAppExpanded(group.appId)}
|
|
title="Collapse"
|
|
>
|
|
<ChevronDown size={14} />
|
|
</button>
|
|
</div>
|
|
}
|
|
meta={
|
|
<div className={styles.groupMeta}>
|
|
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
|
|
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
|
|
<span>
|
|
<StatusDot
|
|
variant={
|
|
appHealth(group) === 'success'
|
|
? 'live'
|
|
: appHealth(group) === 'warning'
|
|
? 'stale'
|
|
: 'dead'
|
|
}
|
|
/>
|
|
</span>
|
|
</div>
|
|
}
|
|
footer={
|
|
group.deadCount > 0 ? (
|
|
<div className={styles.alertBanner}>
|
|
<span className={styles.alertIcon}>⚠</span>
|
|
<span>
|
|
Single point of failure —{' '}
|
|
{group.deadCount === group.instances.length
|
|
? 'no redundancy'
|
|
: `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
|
|
</span>
|
|
</div>
|
|
) : undefined
|
|
}
|
|
>
|
|
<DataTable<AgentInstance & { id: string }>
|
|
columns={instanceColumns as Column<AgentInstance & { id: string }>[]}
|
|
data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
|
|
onRowClick={handleInstanceClick}
|
|
pageSize={50}
|
|
flush
|
|
/>
|
|
</GroupCard>
|
|
</div>
|
|
) : (
|
|
<CompactAppCard
|
|
key={group.appId}
|
|
group={group}
|
|
onExpand={() => toggleAppExpanded(group.appId)}
|
|
/>
|
|
),
|
|
)}
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
- [ ] **Step 4: Verify the dev server compiles without errors**
|
|
|
|
Run: `cd ui && npx vite build --mode development 2>&1 | tail -5`
|
|
Expected: build succeeds
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add ui/src/pages/AgentHealth/AgentHealth.tsx
|
|
git commit -m "feat: add compact app cards with inline expand to runtime dashboard"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Add expand/collapse animation for single card toggle
|
|
|
|
**Files:**
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx`
|
|
|
|
- [ ] **Step 1: Add useRef and useEffect to imports**
|
|
|
|
In `AgentHealth.tsx`, change line 1 from:
|
|
|
|
```tsx
|
|
import { useState, useMemo, useCallback } from 'react';
|
|
```
|
|
|
|
to:
|
|
|
|
```tsx
|
|
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
|
```
|
|
|
|
- [ ] **Step 2: Add animating state to track which apps are transitioning**
|
|
|
|
In `AgentHealth.tsx`, after the `toggleAppExpanded` callback, add:
|
|
|
|
```tsx
|
|
const [animatingApps, setAnimatingApps] = useState<Map<string, 'expanding' | 'collapsing'>>(new Map());
|
|
const animationTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
|
|
const animateToggle = useCallback((appIdToToggle: string) => {
|
|
// Clear any existing timer for this app
|
|
const existing = animationTimers.current.get(appIdToToggle);
|
|
if (existing) clearTimeout(existing);
|
|
|
|
const isCurrentlyExpanded = expandedApps.has(appIdToToggle);
|
|
|
|
if (isCurrentlyExpanded) {
|
|
// Collapsing: start animation, then remove from expandedApps after transition
|
|
setAnimatingApps((prev) => new Map(prev).set(appIdToToggle, 'collapsing'));
|
|
const timer = setTimeout(() => {
|
|
setExpandedApps((prev) => {
|
|
const next = new Set(prev);
|
|
next.delete(appIdToToggle);
|
|
return next;
|
|
});
|
|
setAnimatingApps((prev) => {
|
|
const next = new Map(prev);
|
|
next.delete(appIdToToggle);
|
|
return next;
|
|
});
|
|
animationTimers.current.delete(appIdToToggle);
|
|
}, 200);
|
|
animationTimers.current.set(appIdToToggle, timer);
|
|
} else {
|
|
// Expanding: add to expandedApps immediately, animate in
|
|
setExpandedApps((prev) => new Set(prev).add(appIdToToggle));
|
|
setAnimatingApps((prev) => new Map(prev).set(appIdToToggle, 'expanding'));
|
|
// Use requestAnimationFrame to ensure the collapsed state renders first
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
setAnimatingApps((prev) => {
|
|
const next = new Map(prev);
|
|
next.delete(appIdToToggle);
|
|
return next;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}, [expandedApps]);
|
|
|
|
// Cleanup timers on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
animationTimers.current.forEach((timer) => clearTimeout(timer));
|
|
};
|
|
}, []);
|
|
```
|
|
|
|
- [ ] **Step 3: Update compact grid to use animateToggle and animation classes**
|
|
|
|
In the compact grid section of the JSX, replace the expanded card wrapper and the `CompactAppCard`'s `onExpand` to use `animateToggle` instead of `toggleAppExpanded`.
|
|
|
|
For the expanded card inside the compact grid, wrap the `GroupCard` in the animation wrapper:
|
|
|
|
Change the expanded card branch from:
|
|
|
|
```tsx
|
|
expandedApps.has(group.appId) ? (
|
|
<div key={group.appId} className={styles.compactGridExpanded}>
|
|
<GroupCard
|
|
```
|
|
|
|
to:
|
|
|
|
```tsx
|
|
expandedApps.has(group.appId) ? (
|
|
<div
|
|
key={group.appId}
|
|
className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${
|
|
animatingApps.get(group.appId) === 'expanding'
|
|
? styles.expandWrapperCollapsed
|
|
: animatingApps.get(group.appId) === 'collapsing'
|
|
? styles.expandWrapperCollapsed
|
|
: styles.expandWrapperExpanded
|
|
}`}
|
|
>
|
|
<GroupCard
|
|
```
|
|
|
|
Update the collapse button onClick:
|
|
|
|
```tsx
|
|
onClick={() => animateToggle(group.appId)}
|
|
```
|
|
|
|
Update the `CompactAppCard`'s `onExpand`:
|
|
|
|
```tsx
|
|
<CompactAppCard
|
|
key={group.appId}
|
|
group={group}
|
|
onExpand={() => animateToggle(group.appId)}
|
|
/>
|
|
```
|
|
|
|
- [ ] **Step 4: Verify build**
|
|
|
|
Run: `cd ui && npx vite build --mode development 2>&1 | tail -5`
|
|
Expected: build succeeds
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add ui/src/pages/AgentHealth/AgentHealth.tsx
|
|
git commit -m "feat: add expand/collapse animation for compact card toggle"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Visual testing and polish
|
|
|
|
**Files:**
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.tsx` (if adjustments needed)
|
|
- Modify: `ui/src/pages/AgentHealth/AgentHealth.module.css` (if adjustments needed)
|
|
|
|
- [ ] **Step 1: Start the dev server**
|
|
|
|
Run: `cd ui && npm run dev`
|
|
|
|
- [ ] **Step 2: Test compact view default**
|
|
|
|
Open the runtime dashboard in a browser. Verify:
|
|
- All apps render as compact cards (tinted background, left border, health colors)
|
|
- The view toggle shows the grid icon as active
|
|
- Cards display app name, live count (x/y), last heartbeat
|
|
|
|
- [ ] **Step 3: Test single card expand**
|
|
|
|
Click a compact card. Verify:
|
|
- It expands inline with animation (fade in + grow)
|
|
- The expanded card shows the full agent table (DataTable)
|
|
- It spans 2 columns in the grid
|
|
- A collapse button (ChevronDown) appears in the header
|
|
|
|
- [ ] **Step 4: Test single card collapse**
|
|
|
|
Click the collapse button on an expanded card. Verify:
|
|
- It animates back to compact (fade out + shrink)
|
|
- Returns to compact card form
|
|
|
|
- [ ] **Step 5: Test expand all**
|
|
|
|
Click the list icon in the view toggle. Verify:
|
|
- All cards instantly switch to expanded GroupCards (no animation)
|
|
- The list icon is now active
|
|
|
|
- [ ] **Step 6: Test collapse all**
|
|
|
|
Click the grid icon in the view toggle. Verify:
|
|
- All cards instantly switch to compact cards (no animation)
|
|
- Per-app overrides are cleared
|
|
|
|
- [ ] **Step 7: Test localStorage persistence**
|
|
|
|
Verify:
|
|
- Switch to expanded mode, refresh the page — stays in expanded mode
|
|
- Switch to compact mode, refresh the page — stays in compact mode
|
|
|
|
- [ ] **Step 8: Test app-scoped view (single app route)**
|
|
|
|
Navigate to a specific app route (e.g., `/runtime/some-app`). Verify:
|
|
- The view toggle still shows but the app-scoped layout uses `groupGridSingle` (full width)
|
|
- Compact mode is not used when a specific app is selected (`isFullWidth`)
|
|
|
|
- [ ] **Step 9: Fix any visual issues found**
|
|
|
|
Adjust padding, font sizes, colors, or grid breakpoints as needed. All colors must use CSS variables.
|
|
|
|
- [ ] **Step 10: Commit**
|
|
|
|
```bash
|
|
git add ui/src/pages/AgentHealth/AgentHealth.tsx ui/src/pages/AgentHealth/AgentHealth.module.css
|
|
git commit -m "fix: polish compact view styling after visual testing"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: Update rules file
|
|
|
|
**Files:**
|
|
- Modify: `.claude/rules/ui.md`
|
|
|
|
- [ ] **Step 1: Add compact view to Runtime description**
|
|
|
|
In `.claude/rules/ui.md`, change:
|
|
|
|
```
|
|
- **Runtime** — live agent status, logs, commands (`ui/src/pages/RuntimeTab/`)
|
|
```
|
|
|
|
to:
|
|
|
|
```
|
|
- **Runtime** — live agent status, logs, commands (`ui/src/pages/RuntimeTab/`). AgentHealth supports compact view (dense health-tinted cards) and expanded view (full GroupCard+DataTable per app). View mode persisted to localStorage.
|
|
```
|
|
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add .claude/rules/ui.md
|
|
git commit -m "docs: add compact view to runtime section of ui rules"
|
|
```
|