feat(web): mobile action strip — tappable urgency pills replace stat cards

On mobile (<=767px), replace the 4 stat cards with a single-row
MobileActionStrip showing non-zero urgency pills (respond/merge/review)
with colored dots. Tapping a pill calls setExpandedLevel and scrolls to
the accordion board via id="mobile-board". Hides stat cards, dashboard
subtitle, and board-section-head on mobile to target ~92px hero height.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashish Huddar 2026-03-24 18:46:48 +05:30
parent 0d2804fcf9
commit 3845bd582f
2 changed files with 166 additions and 10 deletions

View File

@ -1957,27 +1957,111 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
/* -- Hero section stacks vertically -- */
.dashboard-hero__content {
flex-direction: column;
padding: 12px;
gap: 12px;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
padding: 0 12px;
gap: 8px;
min-height: 44px;
}
.dashboard-hero__primary {
flex-direction: column;
flex-direction: row;
flex-basis: auto;
gap: 12px;
flex: 1;
align-items: center;
gap: 8px;
min-height: 44px;
}
.dashboard-hero__heading {
flex: 1;
min-width: 0;
}
.dashboard-subtitle {
display: none;
}
/* Hide stat cards on mobile — replaced by the action strip */
.dashboard-stat-cards {
width: 100%;
margin-left: 0;
display: none;
}
.dashboard-hero__meta {
width: 100%;
flex-shrink: 0;
margin-left: 0;
}
/* ── Mobile action strip ─────────────────────────────────────────────── */
.mobile-action-strip {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
flex-shrink: 0;
}
.mobile-action-strip::-webkit-scrollbar {
display: none;
}
.mobile-action-strip--all-good {
align-items: center;
}
.mobile-action-strip__all-good {
font-size: 11px;
color: var(--color-text-tertiary);
white-space: nowrap;
}
.mobile-action-pill {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border: 1px solid var(--color-border-default);
background: var(--color-bg-surface);
cursor: pointer;
white-space: nowrap;
min-height: 32px;
transition:
background 0.12s ease,
border-color 0.12s ease;
}
.mobile-action-pill:hover,
.mobile-action-pill:active {
background: var(--color-bg-hover);
border-color: var(--color-border-strong);
}
.mobile-action-pill__dot {
width: 7px;
height: 7px;
border-radius: 999px;
flex-shrink: 0;
}
.mobile-action-pill__count {
font-size: 13px;
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.mobile-action-pill__label {
font-size: 11px;
font-weight: 500;
color: var(--color-text-secondary);
text-transform: lowercase;
}
/* -- Kanban board: stack columns vertically -- */
.kanban-board {
flex-direction: column;

View File

@ -327,7 +327,17 @@ export function Dashboard({
</p>
</div>
</div>
<StatusCards stats={liveStats} />
{isMobile ? (
<MobileActionStrip
grouped={grouped}
onPillTap={(level) => {
setExpandedLevel(level);
document.getElementById("mobile-board")?.scrollIntoView({ behavior: "smooth", block: "start" });
}}
/>
) : (
<StatusCards stats={liveStats} />
)}
</div>
<div className="dashboard-hero__meta">
@ -438,7 +448,7 @@ export function Dashboard({
</div>
{isMobile ? (
<div className="accordion-board">
<div id="mobile-board" className="accordion-board">
{MOBILE_KANBAN_ORDER.map((level) => (
<AttentionZone
key={level}
@ -696,6 +706,68 @@ function ProjectMetric({ label, value, tone }: { label: string; value: number; t
);
}
const MOBILE_ACTION_STRIP_LEVELS = [
{
level: "respond" as const,
label: "respond",
color: "var(--color-status-error)",
},
{
level: "merge" as const,
label: "merge",
color: "var(--color-status-ready)",
},
{
level: "review" as const,
label: "review",
color: "var(--color-accent-orange)",
},
] satisfies Array<{ level: AttentionLevel; label: string; color: string }>;
function MobileActionStrip({
grouped,
onPillTap,
}: {
grouped: Record<AttentionLevel, DashboardSession[]>;
onPillTap: (level: AttentionLevel) => void;
}) {
const activePills = MOBILE_ACTION_STRIP_LEVELS.filter(
({ level }) => grouped[level].length > 0,
);
if (activePills.length === 0) {
return (
<div className="mobile-action-strip mobile-action-strip--all-good">
<span className="mobile-action-strip__all-good">All clear agents are working</span>
</div>
);
}
return (
<div className="mobile-action-strip" role="navigation" aria-label="Urgency quick-nav">
{activePills.map(({ level, label, color }) => (
<button
key={level}
type="button"
className="mobile-action-pill"
onClick={() => onPillTap(level)}
aria-label={`${grouped[level].length} ${label} — scroll to section`}
>
<span
className="mobile-action-pill__dot"
style={{ background: color }}
aria-hidden="true"
/>
<span className="mobile-action-pill__count" style={{ color }}>
{grouped[level].length}
</span>
<span className="mobile-action-pill__label">{label}</span>
</button>
))}
</div>
);
}
function StatusCards({ stats }: { stats: DashboardStats }) {
if (stats.totalSessions === 0) {
return (