fix(panel): overview reorder — quick actions + key cards first, team health second, queues last; PR Reviews + Social share a row

Removes the mobile order hack that hoisted decision queues above the
fold; the empty PR-Reviews state drops to the compact house convention.
This commit is contained in:
Renn F
2026-07-15 15:58:11 +02:00
parent 129c0041f2
commit 59ff610805
3 changed files with 110 additions and 68 deletions
@@ -97,4 +97,64 @@ describe("CommandCenter", () => {
expect(screen.queryByText("XPostQueueStub")).not.toBeInTheDocument();
expect(screen.queryByText("VideoPostQueueStub")).not.toBeInTheDocument();
});
it("renders every section", () => {
render(<CommandCenter />);
for (const stub of [
"QuickActionsBarStub",
"KeyMetricsPanelStub",
"AuditorAlertsPanelStub",
"UsageOverviewPanelStub",
"ScorecardOverviewPanelStub",
"TeamHealthCardsStub",
"CeoApprovalQueueStub",
"StrategySignalsPanelStub",
"PrReviewQueueStub",
"SocialSummaryCardStub",
"ReleaseProposalCardStub",
"PlaybookReviewQueueStub",
"RoadmapReviewQueueStub",
"ActiveBlockersPanelStub",
"RecentActivityFeedStub",
]) {
expect(screen.getByText(stub)).toBeInTheDocument();
}
});
it("orders sections top to bottom: quick actions/key cards, team health, then the rest", () => {
render(<CommandCenter />);
const order = [
"QuickActionsBarStub",
"KeyMetricsPanelStub",
"TeamHealthCardsStub",
"CeoApprovalQueueStub",
"PrReviewQueueStub",
"ReleaseProposalCardStub",
"PlaybookReviewQueueStub",
"RoadmapReviewQueueStub",
"ActiveBlockersPanelStub",
];
for (let i = 1; i < order.length; i++) {
const earlier = screen.getByText(order[i - 1]);
const later = screen.getByText(order[i]);
// earlier precedes later in the DOM.
expect(
earlier.compareDocumentPosition(later) &
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
}
});
it("pairs PR Reviews and Social side by side ahead of the release/playbook/roadmap cards", () => {
render(<CommandCenter />);
const prReview = screen.getByText("PrReviewQueueStub");
const social = screen.getByText("SocialSummaryCardStub");
const release = screen.getByText("ReleaseProposalCardStub");
// Same grid row: shared immediate parent.
expect(prReview.parentElement).toBe(social.parentElement);
expect(
prReview.compareDocumentPosition(release) &
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
});
@@ -95,13 +95,9 @@ export function CommandCenter() {
const hasError = errorOverview || errorFlags || errorTasks || errorActivity;
return (
// flex-col + explicit `order` (reset via md:order-none): below md the CEO
// decision queues and activity move above the fold; at md+ every item
// shares order:0 and falls back to plain source order (unchanged desktop
// layout).
<div className="flex flex-col gap-6">
{/* Header */}
<div className="order-1 flex items-center justify-between md:order-none">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">
RoboCo Command Center
@@ -133,72 +129,19 @@ export function CommandCenter() {
{/* Error indicator */}
{hasError && (
<div className="order-2 flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive md:order-none">
<div className="flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive">
<AlertCircle className="h-4 w-4 shrink-0" />
Some data failed to load. Use the header refresh button to try again.
</div>
)}
{/* CEO Approval Queue + Strategy Signals - side-by-side on lg+. Ordered
first on mobile — the CEO's decisions shouldn't be below the fold. */}
<div className="order-3 grid grid-cols-1 gap-6 md:order-none lg:grid-cols-2">
<CeoApprovalQueue />
<StrategySignalsPanel />
</div>
{/* External-PR review decision queue (hidden when empty) */}
<div className="order-4 md:order-none">
<PrReviewQueue />
</div>
{/* Gated release proposal (hidden when none open) */}
<div className="order-4 md:order-none">
<ReleaseProposalCard />
</div>
{/* Playbook review queue (hidden when no drafts) */}
<div className="order-4 md:order-none">
<PlaybookReviewQueue />
</div>
{/* Social (X + video) summary — the full queues + unified history live
on /social, avoiding a duplicated surface here. */}
<div className="order-4 md:order-none">
<SocialSummaryCard />
</div>
{/* Board roadmap queue (hidden when no cycle authored) */}
<div className="order-4 md:order-none">
<RoadmapReviewQueue />
</div>
{/* Blockers and Activity Row — activity brought up near the top on
mobile too, ahead of the Team Health / Quick Actions filler. */}
<div className="order-5 grid grid-cols-1 gap-6 md:order-none lg:grid-cols-2">
<ActiveBlockersPanel tasks={tasks} isLoading={loadingTasks} />
<RecentActivityFeed
activities={activity as Activity[] | undefined}
isLoading={loadingActivity}
/>
</div>
{/* Team Health */}
<section className="order-6 md:order-none">
<h2 className="text-lg font-semibold mb-4">Team Health</h2>
<TeamHealthCards
teams={overview?.health_status}
isLoading={loadingOverview}
/>
</section>
{/* Quick Actions */}
<section className="order-7 md:order-none">
{/* Section 1: Quick Actions + the four key cards */}
<section>
<h2 className="text-lg font-semibold mb-4">Quick Actions</h2>
<QuickActionsBar />
</section>
{/* Metrics, Alerts, Usage, and Performance Row */}
<div className="order-8 grid grid-cols-1 gap-6 md:order-none lg:grid-cols-2 xl:grid-cols-4">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-4">
<KeyMetricsPanel
metrics={overview?.key_metrics}
isLoading={loadingOverview}
@@ -207,6 +150,49 @@ export function CommandCenter() {
<UsageOverviewPanel />
<ScorecardOverviewPanel />
</div>
{/* Section 2: Team Health (team cards + Task Intake + Secretary) */}
<section>
<h2 className="text-lg font-semibold mb-4">Team Health</h2>
<TeamHealthCards
teams={overview?.health_status}
isLoading={loadingOverview}
/>
</section>
{/* Section 3: everything else */}
{/* CEO Approval Queue + Strategy Signals - side-by-side on lg+ */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<CeoApprovalQueue />
<StrategySignalsPanel />
</div>
{/* External-PR review decision queue + Social summary - side-by-side
on lg+ (both hidden-when-empty / compact, so pairing them avoids
two near-empty full-width rows) */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<PrReviewQueue />
<SocialSummaryCard />
</div>
{/* Gated release proposal (hidden when none open) */}
<ReleaseProposalCard />
{/* Playbook review queue (hidden when no drafts) */}
<PlaybookReviewQueue />
{/* Board roadmap queue (hidden when no cycle authored) */}
<RoadmapReviewQueue />
{/* Blockers and Activity Row */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<ActiveBlockersPanel tasks={tasks} isLoading={loadingTasks} />
<RecentActivityFeed
activities={activity as Activity[] | undefined}
isLoading={loadingActivity}
/>
</div>
</div>
);
}
@@ -151,13 +151,9 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) {
</CardHeader>
<CardContent>
{items.length === 0 && (
<div className="flex flex-col items-center justify-center py-10 text-center text-muted-foreground">
<GitPullRequest className="mb-3 h-8 w-8 opacity-50" />
<div className="text-center py-8 text-muted-foreground">
<GitPullRequest className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">No external PRs under review right now.</p>
<p className="mt-1 text-xs">
Inbound external/fork PRs the reviewer has looked at show up
here for your supersede / dismiss call.
</p>
</div>
)}
<div className="space-y-3">