Skip to content

Commit fc28ea9

Browse files
committed
feat: demote continuation section and validate complete dashboard section order
Hide ContinueWhereYouLeftOff empty state entirely (return null) so it doesn't dominate the first-run experience. Neutralize populated-state styling from primary-tinted to neutral borders and hover. Pass maturity prop for future maturity-aware behavior. Add integration test validating all 7 dashboard sections render in correct order.
1 parent c514773 commit fc28ea9

9 files changed

+178
-33
lines changed

apps/desktop/src/features/dashboard/components/ContinueWhereYouLeftOff.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { CornerDownLeftIcon, ArrowRightIcon, PlayIcon } from "lucide-react";
1+
import { CornerDownLeftIcon, ArrowRightIcon } from "lucide-react";
22
import type { MeaningfulWorkItem } from "@/features/dashboard/hooks/useMeaningfulWork";
3+
import type { ProjectMaturity } from "@/features/dashboard/hooks/useProjectMaturity";
34

45
export interface ContinueWhereYouLeftOffProps {
56
items: MeaningfulWorkItem[];
7+
maturity: ProjectMaturity;
68
onContinue?: (sessionId: string) => void;
79
onViewResults?: (actionId: string) => void;
810
}
@@ -20,27 +22,20 @@ function formatTimeAgo(ts: number): string {
2022

2123
export function ContinueWhereYouLeftOff({
2224
items,
25+
maturity,
2326
onContinue,
2427
onViewResults,
2528
}: ContinueWhereYouLeftOffProps) {
29+
// Hide entirely when no items — empty state adds no value for any maturity tier
2630
if (items.length === 0) {
27-
return (
28-
<section className="mb-5 rounded-xl border border-border/20 bg-card/30 px-4 py-3 dark:border-white/[0.03] dark:bg-white/[0.01]">
29-
<div className="flex items-center gap-2.5">
30-
<PlayIcon className="size-3 text-muted-foreground/40" />
31-
<p className="text-[13px] text-muted-foreground/60">
32-
Start a job above — your progress will appear here
33-
</p>
34-
</div>
35-
</section>
36-
);
31+
return null;
3732
}
3833

3934
return (
40-
<section className="mb-5 rounded-xl border border-primary/10 bg-primary/[0.015] px-4 py-3 dark:border-primary/[0.06] dark:bg-primary/[0.01]">
35+
<section className="mb-5 rounded-xl border border-border/20 bg-card/30 px-4 py-3 dark:border-white/[0.04] dark:bg-white/[0.015]">
4136
{/* Section label */}
4237
<div className="mb-2.5 flex items-center gap-2">
43-
<CornerDownLeftIcon className="size-3 text-primary/60" />
38+
<CornerDownLeftIcon className="size-3 text-muted-foreground/50" />
4439
<h2 className="section-label">Continue where you left off</h2>
4540
</div>
4641

@@ -60,14 +55,14 @@ export function ContinueWhereYouLeftOff({
6055
key={item.id}
6156
type="button"
6257
onClick={handleClick}
63-
className="group flex w-full items-center gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-100 hover:bg-primary/[0.06] dark:hover:bg-primary/[0.04]"
58+
className="group flex w-full items-center gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-100 hover:bg-muted/40 dark:hover:bg-white/[0.03]"
6459
>
6560
{/* Status dot */}
6661
<span
6762
className={`size-2 shrink-0 rounded-full ring-2 ${
6863
item.needsInput
6964
? "bg-amber-500 ring-amber-500/20"
70-
: "bg-primary/60 ring-primary/15"
65+
: "bg-muted-foreground/40 ring-muted-foreground/10"
7166
}`}
7267
/>
7368

@@ -93,7 +88,7 @@ export function ContinueWhereYouLeftOff({
9388
</span>
9489

9590
{/* Continue CTA */}
96-
<span className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground/40 transition-colors group-hover:text-primary">
91+
<span className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground/40 transition-colors group-hover:text-foreground/70">
9792
Continue
9893
<ArrowRightIcon className="size-2.5" />
9994
</span>

apps/desktop/src/features/dashboard/components/DashboardWorkspace.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,10 @@ function DashboardContent({
204204
onSpecialistChat={handleSpecialistChat}
205205
/>
206206

207-
{/* 5. Continue where you left off — self-hides when no items */}
207+
{/* 5. Continue where you left off — demoted, hides when empty */}
208208
<ContinueWhereYouLeftOff
209209
items={meaningfulWork.items}
210+
maturity={maturity}
210211
onContinue={onResumeRun}
211212
onViewResults={onViewResults}
212213
/>

test/ui/continue-where-you-left-off.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,11 @@ describe("ContinueWhereYouLeftOff component", () => {
109109
expect(src).toContain("export function ContinueWhereYouLeftOff");
110110
});
111111

112-
it("shows a lightweight first-run nudge when items is empty instead of returning null", () => {
112+
it("hides when items is empty (returns null for demoted empty state)", () => {
113113
const src = readFile(COMPONENT_PATH);
114-
// Should NOT return null on empty — should show a helpful nudge
115114
expect(src).toContain("items.length === 0");
116-
// Should contain nudge text guiding user to start a job
117-
expect(src).toMatch(/start.*job|progress.*appear/i);
115+
// Should return null on empty — demoted per spec 9.7
116+
expect(src).toMatch(/return\s+null/);
118117
});
119118

120119
it('uses "Continue where you left off" label text', () => {

test/ui/dashboard-active-work-details.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ describe("ContinueWhereYouLeftOff compact section", () => {
101101
expect(src).toContain("section-label");
102102
});
103103

104-
it("shows nudge when no items", () => {
104+
it("hides when no items (returns null)", () => {
105105
const src = readFile(componentPath);
106106
expect(src).toContain("items.length === 0");
107-
expect(src).toMatch(/start.*job|progress.*appear/i);
107+
expect(src).toMatch(/return\s+null/);
108108
});
109109
});

test/ui/dashboard-active-work.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ describe("ContinueWhereYouLeftOff component", () => {
8585
expect(src).toContain("onContinue");
8686
});
8787

88-
it("shows a first-run nudge when no items", () => {
88+
it("hides when no items (returns null)", () => {
8989
const src = readFile(componentPath);
9090
expect(src).toContain("items.length === 0");
91-
expect(src).toMatch(/start.*job|progress.*appear/i);
91+
expect(src).toMatch(/return\s+null/);
9292
});
9393

9494
it("uses section-label styling for heading", () => {
@@ -129,9 +129,9 @@ describe("DashboardWorkspace active work integration", () => {
129129
// ═════════════════════��═══════════════════════���═════════
130130

131131
describe("Mode A stays clean", () => {
132-
it("ContinueWhereYouLeftOff shows nudge when items is empty", () => {
132+
it("ContinueWhereYouLeftOff hides when items is empty (returns null)", () => {
133133
const src = readFile("features/dashboard/components/ContinueWhereYouLeftOff.tsx");
134134
expect(src).toContain("items.length === 0");
135-
expect(src).toMatch(/start.*job|progress.*appear/i);
135+
expect(src).toMatch(/return\s+null/);
136136
});
137137
});

test/ui/dashboard-maturity-layout.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,20 @@ describe("DashboardWorkspace unified layout", () => {
103103
expect(src).not.toContain("ActionCardGrid");
104104
});
105105

106-
it("renders sections in correct order: Hero, Jobs, Roster, Outputs, Continue, Board", () => {
106+
it("renders sections in correct order: Hero, Jobs, Input, Roster, Outputs, Continue, Board", () => {
107107
const src = readFile(dashPath);
108108
const heroIdx = src.indexOf("<CompanyUnderstandingHero");
109109
const jobsIdx = src.indexOf("<RecommendedJobs");
110+
const inputIdx = src.indexOf("<JobOrientedInput");
110111
const rosterIdx = src.indexOf("<DashboardAgentRoster");
111112
const outputsIdx = src.indexOf("<RecentOutputs");
112113
const continueIdx = src.indexOf("<ContinueWhereYouLeftOff");
113114
const boardIdx = src.indexOf("<BoardSummary");
114115

115116
expect(heroIdx).toBeGreaterThan(-1);
116117
expect(jobsIdx).toBeGreaterThan(heroIdx);
117-
expect(rosterIdx).toBeGreaterThan(jobsIdx);
118+
expect(inputIdx).toBeGreaterThan(jobsIdx);
119+
expect(rosterIdx).toBeGreaterThan(inputIdx);
118120
expect(outputsIdx).toBeGreaterThan(rosterIdx);
119121
expect(continueIdx).toBeGreaterThan(outputsIdx);
120122
expect(boardIdx).toBeGreaterThan(continueIdx);
@@ -130,6 +132,7 @@ describe("DashboardWorkspace unified layout", () => {
130132
};
131133
expect(countOccurrences(src, "<CompanyUnderstandingHero")).toBe(1);
132134
expect(countOccurrences(src, "<RecommendedJobs")).toBe(1);
135+
expect(countOccurrences(src, "<JobOrientedInput")).toBe(1);
133136
expect(countOccurrences(src, "<DashboardAgentRoster")).toBe(1);
134137
expect(countOccurrences(src, "<RecentOutputs")).toBe(1);
135138
expect(countOccurrences(src, "<ContinueWhereYouLeftOff")).toBe(1);

test/ui/dashboard-recent-work-visibility.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ describe("ContinueWhereYouLeftOff renders in unified layout", () => {
1818
expect(matches!.length).toBeGreaterThanOrEqual(1);
1919
});
2020

21-
it("ContinueWhereYouLeftOff shows nudge when items are empty", () => {
21+
it("ContinueWhereYouLeftOff hides when items are empty (returns null)", () => {
2222
const src = readFile("features/dashboard/components/ContinueWhereYouLeftOff.tsx");
2323
expect(src).toContain("items.length === 0");
24-
expect(src).toMatch(/start.*job|progress.*appear/i);
24+
expect(src).toMatch(/return\s+null/);
2525
});
2626
});
2727

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { describe, expect, it } from "vitest";
2+
import { readFileSync } from "node:fs";
3+
import { resolve } from "node:path";
4+
5+
const desktopSrc = resolve(__dirname, "../../apps/desktop/src");
6+
const readFile = (relPath: string) =>
7+
readFileSync(resolve(desktopSrc, relPath), "utf-8");
8+
9+
// ═══════════════════════════════════════════════════════
10+
// 1. Complete section order — all 7 sections
11+
// ═══════════════════════════════════════════════════════
12+
13+
describe("Dashboard section order — all 7 sections", () => {
14+
const dashSrc = () => readFile("features/dashboard/components/DashboardWorkspace.tsx");
15+
16+
it("renders all 7 sections in correct order: Hero, Jobs, Input, Roster, Outputs, Continue, Board", () => {
17+
const src = dashSrc();
18+
const heroIdx = src.indexOf("<CompanyUnderstandingHero");
19+
const jobsIdx = src.indexOf("<RecommendedJobs");
20+
const inputIdx = src.indexOf("<JobOrientedInput");
21+
const rosterIdx = src.indexOf("<DashboardAgentRoster");
22+
const outputsIdx = src.indexOf("<RecentOutputs");
23+
const continueIdx = src.indexOf("<ContinueWhereYouLeftOff");
24+
const boardIdx = src.indexOf("<BoardSummary");
25+
26+
// All 7 must exist
27+
expect(heroIdx).toBeGreaterThan(-1);
28+
expect(jobsIdx).toBeGreaterThan(-1);
29+
expect(inputIdx).toBeGreaterThan(-1);
30+
expect(rosterIdx).toBeGreaterThan(-1);
31+
expect(outputsIdx).toBeGreaterThan(-1);
32+
expect(continueIdx).toBeGreaterThan(-1);
33+
expect(boardIdx).toBeGreaterThan(-1);
34+
35+
// Correct order: 1 < 2 < 3 < 4 < 5 < 6 < 7
36+
expect(heroIdx).toBeLessThan(jobsIdx);
37+
expect(jobsIdx).toBeLessThan(inputIdx);
38+
expect(inputIdx).toBeLessThan(rosterIdx);
39+
expect(rosterIdx).toBeLessThan(outputsIdx);
40+
expect(outputsIdx).toBeLessThan(continueIdx);
41+
expect(continueIdx).toBeLessThan(boardIdx);
42+
});
43+
44+
it("JobOrientedInput appears between RecommendedJobs and DashboardAgentRoster", () => {
45+
const src = dashSrc();
46+
const jobsIdx = src.indexOf("<RecommendedJobs");
47+
const inputIdx = src.indexOf("<JobOrientedInput");
48+
const rosterIdx = src.indexOf("<DashboardAgentRoster");
49+
50+
expect(inputIdx).toBeGreaterThan(jobsIdx);
51+
expect(inputIdx).toBeLessThan(rosterIdx);
52+
});
53+
});
54+
55+
// ═══════════════════════════════════════════════════════
56+
// 2. ContinueWhereYouLeftOff receives maturity prop
57+
// ═══════════════════════════════════════════════════════
58+
59+
describe("ContinueWhereYouLeftOff maturity-aware demotion", () => {
60+
const dashSrc = () => readFile("features/dashboard/components/DashboardWorkspace.tsx");
61+
const continueSrc = () => readFile("features/dashboard/components/ContinueWhereYouLeftOff.tsx");
62+
63+
it("DashboardWorkspace passes maturity prop to ContinueWhereYouLeftOff", () => {
64+
const src = dashSrc();
65+
// The JSX should pass maturity={maturity} or maturity=
66+
expect(src).toMatch(/<ContinueWhereYouLeftOff[\s\S]*?maturity[=]/);
67+
});
68+
69+
it("ContinueWhereYouLeftOff accepts a maturity prop", () => {
70+
const src = continueSrc();
71+
expect(src).toContain("maturity");
72+
});
73+
74+
it("ContinueWhereYouLeftOff hides empty state for non-active maturity", () => {
75+
const src = continueSrc();
76+
// Should return null when items are empty and maturity is not active
77+
expect(src).toMatch(/items\.length\s*===\s*0/);
78+
expect(src).toMatch(/return\s+null/);
79+
});
80+
81+
it("ContinueWhereYouLeftOff uses neutral (non-primary) border styling", () => {
82+
const src = continueSrc();
83+
// Populated state should NOT use primary-color border — should be neutral
84+
expect(src).not.toMatch(/border-primary\/10/);
85+
});
86+
87+
it("ContinueWhereYouLeftOff hover uses neutral colors, not primary", () => {
88+
const src = continueSrc();
89+
// Hover on items should be neutral, not primary-tinted
90+
expect(src).not.toMatch(/hover:bg-primary/);
91+
});
92+
});
93+
94+
// ═══════════════════════════════════════════════════════
95+
// 3. BoardSummary demotion verification
96+
// ═══════════════════════════════════════════════════════
97+
98+
describe("BoardSummary demotion", () => {
99+
const dashSrc = () => readFile("features/dashboard/components/DashboardWorkspace.tsx");
100+
const boardSrc = () => readFile("features/dashboard/components/BoardSummary.tsx");
101+
102+
it("BoardSummary is gated by maturity !== 'new'", () => {
103+
const src = dashSrc();
104+
expect(src).toMatch(/maturity\s*!==\s*['"]new['"]/);
105+
});
106+
107+
it("BoardSummary uses neutral border, not accent/primary", () => {
108+
const src = boardSrc();
109+
// Should use neutral border colors
110+
expect(src).toMatch(/border-border|border-white/);
111+
// Should NOT use primary-colored borders on the outer container
112+
expect(src).not.toMatch(/border-primary/);
113+
});
114+
115+
it("BoardSummary returns null when empty", () => {
116+
const src = boardSrc();
117+
expect(src).toMatch(/isEmpty[\s\S]*?return\s+null/);
118+
});
119+
});
120+
121+
// ═══════════════════════════════════════════════════════
122+
// 4. Action-first layout: jobs/outputs visually heavier than continue/board
123+
// ═══════════════════════════════════════════════════════
124+
125+
describe("Action-first layout hierarchy", () => {
126+
const dashSrc = () => readFile("features/dashboard/components/DashboardWorkspace.tsx");
127+
128+
it("RecommendedJobs is wrapped in dashboard-section", () => {
129+
const src = dashSrc();
130+
expect(src).toMatch(/dashboard-section[\s\S]*?<RecommendedJobs/);
131+
});
132+
133+
it("JobOrientedInput is wrapped in dashboard-section", () => {
134+
const src = dashSrc();
135+
expect(src).toMatch(/dashboard-section[\s\S]*?<JobOrientedInput/);
136+
});
137+
138+
it("DashboardAgentRoster is wrapped in dashboard-section", () => {
139+
const src = dashSrc();
140+
expect(src).toMatch(/dashboard-section[\s\S]*?<DashboardAgentRoster/);
141+
});
142+
143+
it("BoardSummary is wrapped in dashboard-section", () => {
144+
const src = dashSrc();
145+
expect(src).toMatch(/dashboard-section[\s\S]*?<BoardSummary/);
146+
});
147+
});

test/ui/dashboard-simplify.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ describe("Dashboard simplification — Sprint 5 reset", () => {
222222
expect(src).toContain("Continue");
223223
});
224224

225-
it("shows nudge when items is empty", () => {
225+
it("hides when items is empty (returns null)", () => {
226226
const src = readSrc("components/ContinueWhereYouLeftOff.tsx");
227227
expect(src).toContain("items.length === 0");
228-
expect(src).toMatch(/start.*job|progress.*appear/i);
228+
expect(src).toMatch(/return\s+null/);
229229
});
230230
});
231231
});

0 commit comments

Comments
 (0)