From e05341d26d55c6cd82339b93643e29784ec9f71f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 11 Aug 2026 12:04:15 +0000 Subject: [PATCH] Clarify primary CTAs across Cycles and other pages Keep a single solid primary action per screen (usually NextStep). Demote duplicate PageHeader/Section/table-row buttons to secondary or ghost so Cycles no longer shows Create cycle three times in neon green, and apply the same hierarchy on Team, Roles, Home, Settings, Writing Desk, and related pages. Co-authored-by: Damon --- CHANGELOG.md | 4 + apps/ui/src/pages/BackfillPage.tsx | 2 +- apps/ui/src/pages/ConsistencyPage.tsx | 12 +- apps/ui/src/pages/CyclesPage.tsx | 601 ++++++++++++---------- apps/ui/src/pages/HomePage.tsx | 14 +- apps/ui/src/pages/PersonPage.tsx | 9 - apps/ui/src/pages/PromotionDetailPage.tsx | 20 +- apps/ui/src/pages/PromotionsPage.tsx | 16 +- apps/ui/src/pages/RolesPage.tsx | 21 +- apps/ui/src/pages/SettingsPage.tsx | 12 +- apps/ui/src/pages/TeamPage.tsx | 7 +- apps/ui/src/pages/TemplatesPage.tsx | 2 +- apps/ui/src/pages/WritingDeskPage.tsx | 26 +- apps/ui/src/styles.css | 9 + 14 files changed, 404 insertions(+), 351 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2157fd..c9d946f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,11 @@ Versions follow [SemVer](https://semver.org/) (`0.1.0-alpha.x` while the public - MCP / AGENTS docs no longer imply a silent `PRM_PASSWORD=workbench` default - Settings → AI: OpenAI-compatible gateways are a separate provider from Cursor Cloud Agents - Empty workspaces land in setup until finished or skipped; demo seed marks onboarding complete +<<<<<<< HEAD - **Cursor Cloud Agents** chat: stream status and assistant text live via SSE; empty-result errors include agent/run ids and a dashboard link +======= +- Primary CTA hierarchy: NextStep owns the solid primary action; PageHeader/Section duplicates and table-row moves use secondary/ghost so pages like Cycles no longer show the same green button 3–4 times +>>>>>>> bd04ca1 (Clarify primary CTAs across Cycles and other pages) ### Fixed diff --git a/apps/ui/src/pages/BackfillPage.tsx b/apps/ui/src/pages/BackfillPage.tsx index 781a698..eb2a10b 100644 --- a/apps/ui/src/pages/BackfillPage.tsx +++ b/apps/ui/src/pages/BackfillPage.tsx @@ -89,7 +89,7 @@ export function BackfillPage() { tone="ok" label="Dossiers look populated" detail="Every direct has at least two evidence items. Keep capturing year-round." - action={Back to command center} + action={Back to command center} /> )} diff --git a/apps/ui/src/pages/ConsistencyPage.tsx b/apps/ui/src/pages/ConsistencyPage.tsx index f64e1e5..4b872d1 100644 --- a/apps/ui/src/pages/ConsistencyPage.tsx +++ b/apps/ui/src/pages/ConsistencyPage.tsx @@ -56,9 +56,9 @@ export function ConsistencyPage() { + Open first writing desk ) : undefined @@ -114,7 +114,7 @@ export function ConsistencyPage() { action={ - ) : ( - - ) + secondary={ + empty ? ( + + Jump to create form + + ) : selectedCycle && subjectId ? ( + + Open writing desk + + ) : undefined } - secondary={selectedCycle && subjectId ? ( - Open writing desk - ) : undefined} /> - {cycles.length === 0 ? ( + {empty ? ( Create cycle with directs} + action={ + + } /> ) : ( - - Jump to import + + + Jump to import + } /> @@ -90,286 +100,339 @@ export function CyclesPage() { { id: "import-bundle", label: "Import" }, ]} > -
- Create cycle with directs - - } - > - {cycles.length === 0 ? ( -

No cycles yet.

- ) : ( - - - - {cycles.map((c) => ( - setSelectedCycle(c.id)} - > - - - - - - ))} - -
NameWindowStatusPeople
{c.name}{c.windowStart} → {c.windowEnd}{c.status}{c.participantCount}
- )} -
{ - e.preventDefault(); - await api("/api/cycles", { - method: "POST", - body: JSON.stringify({ - name, - windowStart, - windowEnd, - includeDirects: true, - ...(selfDue ? { selfDue } : {}), - ...(peerDue ? { peerDue } : {}), - ...(managerDue ? { managerDue } : {}), - }), - }); - setMessage("Cycle created — export self-review bundles next."); - await load(); - }} - > -
- setName(e.target.value)} aria-label="Cycle name" placeholder="Cycle name" /> -
- - setWindowStart(e.target.value)} required /> -
-
- - setWindowEnd(e.target.value)} required /> -
-
-
-
- - setSelfDue(e.target.value)} /> -
-
- - setPeerDue(e.target.value)} /> -
-
- - setManagerDue(e.target.value)} /> -
- -
-
-
- - {selected && (
- Update cycle window - - } + id="cycles-list" + title="Cycles" + when="Create a cycle when a review window starts; select a row anytime to export, import, or write for that cycle." + how="Fill name, window, and optional due dates, then create with your current directs — or click a cycle to select it." > -
- {PHASE_STATUSES.map((phase) => ( - - ))} -
+ {empty ? ( +

No cycles yet.

+ ) : ( + + + + + + + + + + + {cycles.map((c) => ( + setSelectedCycle(c.id)} + > + + + + + + ))} + +
NameWindowStatusPeople
{c.name} + {c.windowStart} → {c.windowEnd} + {c.status}{c.participantCount}
+ )}
{ e.preventDefault(); - const fd = new FormData(e.currentTarget); - await api(`/api/cycles/${selected.id}`, { - method: "PATCH", + await api("/api/cycles", { + method: "POST", body: JSON.stringify({ - windowStart: String(fd.get("windowStart") || selected.windowStart), - windowEnd: String(fd.get("windowEnd") || selected.windowEnd), + name, + windowStart, + windowEnd, + includeDirects: true, + ...(selfDue ? { selfDue } : {}), + ...(peerDue ? { peerDue } : {}), + ...(managerDue ? { managerDue } : {}), }), }); - setMessage("Cycle window updated."); + setMessage("Cycle created — export self-review bundles next."); await load(); }} > -
- - +
+ setName(e.target.value)} + aria-label="Cycle name" + placeholder="Cycle name" + /> +
+ + setWindowStart(e.target.value)} + required + /> +
+
+ + setWindowEnd(e.target.value)} + required + /> +
-
- - +
+
+ + setSelfDue(e.target.value)} /> +
+
+ + setPeerDue(e.target.value)} /> +
+
+ + setManagerDue(e.target.value)} + /> +
+
-
- )} -
-
- Download JSON bundle - - } - > -
{ - e.preventDefault(); - if (!selectedCycle || !subjectId) return; - const bundle = await api>("/api/bundles/export", { - method: "POST", - body: JSON.stringify({ - kind: bundleKind, - cycleId: selectedCycle, - subjectPersonId: subjectId, - peerAuthorName: peerName || undefined, - }), - }); - const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: "application/json" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `${bundleKind}-${(bundle.subjectName as string)?.replace(/\s+/g, "-") ?? "bundle"}.json`; - a.click(); - setMessage( - `Bundle downloaded — ${bundleKind} pipeline marked exported. Send outside the app, then import the filled JSON below.`, - ); - }} + {selected && ( +
-
- - -
-
- - +
+ {PHASE_STATUSES.map((phase) => { + const active = selected.status === phase.value; + return ( + + ); + })}
-
- - -
- {bundleKind === "peer" && ( -
- - setPeerName(e.target.value)} placeholder="Jordan Lee" /> + { + e.preventDefault(); + const fd = new FormData(e.currentTarget); + await api(`/api/cycles/${selected.id}`, { + method: "PATCH", + body: JSON.stringify({ + windowStart: String(fd.get("windowStart") || selected.windowStart), + windowEnd: String(fd.get("windowEnd") || selected.windowEnd), + }), + }); + setMessage("Cycle window updated."); + await load(); + }} + > +
+ +
- )} - -
+
+ + +
+ + +
+ )} -
{ - const parsed = JSON.parse(importText) as Record; - const fields = (parsed.fields ?? {}) as Record; - if (parsed.kind === "self" && !fields.top_outcomes) { - fields.top_outcomes = "Shipped reliability work; mentored a new joiner; unblocked checkout migration."; - fields.growth_areas = "Take more cross-team design leadership."; - fields.proposed_goals = "Own Q3 platform migration milestones."; - parsed.fields = fields; - } - if (parsed.kind === "peer" && !fields.strengths) { - fields.context = "Paired on API design for 6 weeks."; - fields.strengths = "Clear written RFCs; calm under incident pressure."; - fields.growth = "Could socialize designs earlier with partner teams."; - parsed.fields = fields; - parsed.peerAuthorName = parsed.peerAuthorName || "Peer Reviewer"; - } - await api("/api/bundles/import", { +
+
+
{ + e.preventDefault(); + if (!selectedCycle || !subjectId) return; + const bundle = await api>("/api/bundles/export", { method: "POST", - body: JSON.stringify({ bundle: parsed, sourceFilename: "paste.json" }), + body: JSON.stringify({ + kind: bundleKind, + cycleId: selectedCycle, + subjectPersonId: subjectId, + peerAuthorName: peerName || undefined, + }), }); - const sid = String(parsed.subjectPersonId ?? subjectId); - const cid = String(parsed.cycleId ?? selectedCycle); - setMessage("Imported. Next: open the writing desk and draft the manager review."); - setImportText(""); - setSubjectId(sid); - setSelectedCycle(cid); + const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${bundleKind}-${(bundle.subjectName as string)?.replace(/\s+/g, "-") ?? "bundle"}.json`; + a.click(); + setMessage( + `Bundle downloaded — ${bundleKind} pipeline marked exported. Send outside the app, then import the filled JSON below.`, + ); }} > - Import completed JSON - - } - > -
- -