From 385059f5ca416ba3a57dcb2613974ae9ec8eb053 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 16 Aug 2026 08:05:11 -0700 Subject: [PATCH] FN-9124: Move mission planning CTA to top of list Place the primary mission-planning action consistently above mission content across desktop and mobile layouts. - move the Plan New Mission and Create actions above the mission list - increase the mission CTA height with shared design tokens and remove duplicate empty-state controls - document and test placement, sizing, create-mode, empty-state, and responsive behavior Files changed: .changeset/fn-9124-mission-cta-top.md | 7 ++ docs/missions.md | 2 +- .../dashboard/app/components/MissionManager.css | 42 +++---- .../dashboard/app/components/MissionManager.tsx | 89 +++++++------- .../dashboard/app/components/PlanningModeModal.css | 14 +-- .../dashboard/app/components/PlanningModeModal.tsx | 12 +- .../__tests__/MissionManager.mobile-css.test.ts | 33 +++-- .../MissionManager.plan-cta-placement.test.tsx | 134 +++++++++++++++++++++ 8 files changed, 238 insertions(+), 95 deletions(-) Fusion-Task-Id: FN-9124 Fusion-Task-Lineage: 09ad7695-80ed-46af-a1c1-f334952c37ec Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-9124-mission-cta-top.md | 7 + docs/missions.md | 2 +- .../app/components/MissionManager.css | 42 +++--- .../app/components/MissionManager.tsx | 89 ++++++------ .../app/components/PlanningModeModal.css | 14 +- .../app/components/PlanningModeModal.tsx | 12 +- .../MissionManager.mobile-css.test.ts | 37 +++-- ...MissionManager.plan-cta-placement.test.tsx | 134 ++++++++++++++++++ 8 files changed, 240 insertions(+), 97 deletions(-) create mode 100644 .changeset/fn-9124-mission-cta-top.md create mode 100644 packages/dashboard/app/components/__tests__/MissionManager.plan-cta-placement.test.tsx diff --git a/.changeset/fn-9124-mission-cta-top.md b/.changeset/fn-9124-mission-cta-top.md new file mode 100644 index 0000000000..28ddcde263 --- /dev/null +++ b/.changeset/fn-9124-mission-cta-top.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Plan New Mission now sits at the top of the mission list and is slightly taller. +category: fix +dev: Replaced footer CTA wrappers with top mission-manager__sidebar-cta-bar and mission-list__header-actions containers using calc(var(--space-lg) * 2 + var(--space-sm)); removed the duplicate empty-state CTA. diff --git a/docs/missions.md b/docs/missions.md index 14dffee1a5..b02c1d48af 100644 --- a/docs/missions.md +++ b/docs/missions.md @@ -144,7 +144,7 @@ This keeps member execution isolated per task while still routing member landing Use the Mission Manager UI to create missions and build hierarchy interactively. -On mobile, Mission Manager surfaces the primary **Plan New Mission** CTA at the top of the mission list for faster access, while desktop keeps the split-layout sidebar CTA anchored in the bottom action region as the primary entry point. +Mission Manager anchors the primary **Plan New Mission** CTA at the top of the mission list on both mobile stacked and desktop split layouts. The secondary **Create** manual-create link sits beneath it, and both primary controls use the same slightly taller tokenized height. While the inline create form is open, the CTA is suppressed cleanly without leaving an empty container; each surface retains only one primary planning control. Mission detail refreshes now preserve expanded milestone/slice state and keep the selected milestone expanded, so persisted milestone acceptance criteria remain visible across live updates. diff --git a/packages/dashboard/app/components/MissionManager.css b/packages/dashboard/app/components/MissionManager.css index a9ec6e4cce..c94f0bb69e 100644 --- a/packages/dashboard/app/components/MissionManager.css +++ b/packages/dashboard/app/components/MissionManager.css @@ -225,19 +225,23 @@ Title metric matches the shared ViewHeader (1.125rem) so the Missions header rea box-shadow: var(--focus-ring-strong); } -/* Desktop split-only sidebar footer action bar for mission creation CTA. */ -.mission-manager__sidebar-footer { +/* +FNXC:MissionsUI 2026-08-16-14:48: +The desktop CTA bar stays above the scrolling mission list so planning is immediately reachable. +*/ +.mission-manager__sidebar-cta-bar { display: flex; + flex-direction: column; align-items: center; justify-content: center; gap: var(--space-sm); padding: var(--space-md); - border-top: var(--btn-border-width) solid var(--border); + border-bottom: var(--btn-border-width) solid var(--border); } .mission-manager__sidebar-cta { width: 100%; - min-height: calc(var(--space-lg) * 2 + var(--space-xs)); + min-height: calc(var(--space-lg) * 2 + var(--space-sm)); justify-content: center; gap: var(--space-sm); font-size: inherit; @@ -388,11 +392,6 @@ primary CTA while every production presentation can open the merge-aware form. color: var(--text-muted); } -.mission-manager__empty-cta { - margin-top: var(--space-sm); - gap: var(--space-sm); -} - /* ── Status Badge ── */ .mission-status-badge { display: inline-flex; @@ -556,11 +555,12 @@ primary CTA while every production presentation can open the merge-aware form. } /* -FNXC:MissionsMobile 2026-06-22-18:00: -Narrow/mobile Missions puts Plan New Mission at the bottom of the list, using the same compact primary button proportions as Chat's New Chat action instead of a large top CTA. +FNXC:MissionsUI 2026-08-16-14:48: +Mobile Missions keeps Plan New Mission above the list for immediate reachability. The shared tokenized height matches the desktop sidebar CTA while preserving the compact button treatment. */ .mission-list__primary-cta { width: 100%; + min-height: calc(var(--space-lg) * 2 + var(--space-sm)); justify-content: center; gap: var(--space-sm); } @@ -580,12 +580,6 @@ Narrow/mobile Missions puts Plan New Mission at the bottom of the list, using th font-weight: 600; } -.mission-list__footer { - display: flex; - flex-direction: column; - gap: var(--space-sm); -} - .mission-list__resume-actions { display: flex; flex-wrap: wrap; @@ -594,20 +588,20 @@ Narrow/mobile Missions puts Plan New Mission at the bottom of the list, using th margin-inline-start: auto; } -.mission-list__footer-actions { +.mission-list__header-actions { display: flex; + flex-direction: column; gap: var(--space-sm); } -/* Desktop split-only: list footer actions live in sidebar header, so hide duplicated footer buttons. */ -.mission-manager__sidebar .mission-list__footer-actions { +/* +FNXC:MissionsUI 2026-08-16-14:48: +Desktop owns the CTA in its sidebar bar, so the shared list header cannot duplicate it. +*/ +.mission-manager__sidebar .mission-list__header-actions { display: none; } -.mission-list__footer-actions .mission-list__manual-create-link { - align-self: center; -} - /* ── Inline Form Card ── */ .mission-form-card { display: flex; diff --git a/packages/dashboard/app/components/MissionManager.tsx b/packages/dashboard/app/components/MissionManager.tsx index 901c236e7a..f4a7f68da0 100644 --- a/packages/dashboard/app/components/MissionManager.tsx +++ b/packages/dashboard/app/components/MissionManager.tsx @@ -5293,14 +5293,30 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr ); }); - const renderMissionListContent = ({ hideBottomButtons = false }: { hideBottomButtons?: boolean } = {}) => { + const renderMissionListContent = ({ hideListCta = false }: { hideListCta?: boolean } = {}) => { const persistedInterviewMissions = missions.filter((mission) => mission.interviewState === "in_progress"); const standardMissions = missions.filter((mission) => mission.interviewState !== "in_progress"); - const showBottomPlanButton = !hideBottomButtons; + const showListCta = !hideListCta; return (
- {/* Create mission form */} + {/* + FNXC:MissionsUI 2026-08-16-14:48: + Mobile Missions anchors its single Plan New Mission CTA above every list state so it is immediately reachable. The inline create form suppresses this whole container rather than leaving an empty header shell. + */} + {!isCreatingMission && showListCta && ( +
+ + + {t("missions.createButton", "Create")} + +
+ )} + + {/* Create mission form */} {isCreatingMission && (
{t("missions.noMissionsYetBody", "Missions are large initiatives that bundle milestones, slices, and features into a single plan. Plan a mission to break down a goal end-to-end and let agents work through it autopilot-style.")}

- - - {t("missions.createButton", "Create")} -
)} - {!isCreatingMission && ( -
- {showBottomPlanButton && ( -
- - - {t("missions.createButton", "Create")} - -
- )} -
- )}
); }; @@ -5707,6 +5698,31 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr aria-label={t("missions.missionList", "Mission list")} style={isMobile ? undefined : { width: `${sidebarWidth}px` }} > + {/* + FNXC:MissionsUI 2026-08-16-14:48: + Desktop Missions anchors its single Plan New Mission CTA before the scrolling list for immediate reachability. Empty and create-form states never duplicate the CTA, and create mode omits the bar unless the delete confirmation still needs it. + */} + {(shouldRenderSidebarDeleteConfirm || !isCreatingMission) && ( +
+ {shouldRenderSidebarDeleteConfirm && renderDeleteConfirmPanel()} + {!isCreatingMission && ( + <> + + + {t("missions.createButton", "Create")} + + + )} +
+ )}
{loading ? (
@@ -5714,24 +5730,9 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr {t("missions.loadingMissions", "Loading missions...")}
) : ( - renderMissionListContent({ hideBottomButtons: true }) + renderMissionListContent({ hideListCta: true }) )}
-
- {shouldRenderSidebarDeleteConfirm && renderDeleteConfirmPanel()} - - - {t("missions.createButton", "Create")} - -
{!isMobile && ( diff --git a/packages/dashboard/app/components/PlanningModeModal.css b/packages/dashboard/app/components/PlanningModeModal.css index 4a3e107ebb..9b4aee058e 100644 --- a/packages/dashboard/app/components/PlanningModeModal.css +++ b/packages/dashboard/app/components/PlanningModeModal.css @@ -53,7 +53,7 @@ FNXC:PlanningMode 2026-06-21-00:00: FN-6886 promotes Planning Mode into the main app content area. The embedded shell fills the available content pane and intentionally disables modal-only sizing/resizing so the left sidebar owns navigation and no backdrop shell remains. */ /* -FNXC:Planning 2026-06-23-02:00: +FNXC:Planning 2026-08-16-14:48: The embedded Planning wrapper matches Missions' shell: NO outer padding. Missions' inline shell (mission-manager--inline) has no wrapper inset — its header supplies the top/side padding (var(--space-lg) var(--space-xl)) and the split body fills edge-to-edge below. Mirroring that here removes the previous extra var(--space-lg) inset so Planning's header and two-pane body align with Missions. */ .planning-view { @@ -293,7 +293,7 @@ The embedded planning header must be a visual SIBLING of MissionManager's inline } /* -FNXC:Planning 2026-06-23-01:15: +FNXC:Planning 2026-08-16-14:48: Embedded Planning is a real two-pane view mirroring Missions (mission-manager__split): the left sidebar is a full-height flex column (list scrolls, footer pinned to bottom) and the right detail pane fills the remaining width AND height. The sidebar surface/border/width treatment matches mission-manager__sidebar — var(--surface) background, a single border-right divider — so the two views read consistently. The flex chain planning-view -> planning-modal--embedded -> planning-modal-body keeps the whole layout filling the main-content pane to the bottom. */ .planning-sidebar { @@ -308,7 +308,7 @@ Embedded Planning is a real two-pane view mirroring Missions (mission-manager__s } /* -FNXC:Planning 2026-06-23-02:00: +FNXC:Planning 2026-08-16-14:48: Sidebar resize handle — identical treatment to MissionManager's mission-manager__sidebar-resize-handle: a thin (var(--space-sm)) col-resize strip whose ::before center bar tints with var(--todo) on hover/active, plus a focus ring for keyboard resize. touch-action:none keeps the pointer drag from scrolling on trackpads. */ .planning-sidebar-resize-handle { @@ -342,8 +342,8 @@ Sidebar resize handle — identical treatment to MissionManager's mission-manage } /* -FNXC:Planning 2026-06-23-02:00: -Footer matches MissionManager's mission-manager__sidebar-footer EXACTLY: padding var(--space-md), a single border-top divider (var(--btn-border-width) solid var(--border)), gap var(--space-sm), centered. Missions has no separate footer band styling beyond the centered CTA, so we mirror that — no extra bottom padding/margin and no empty space. The column direction is the only deviation, required because Planning carries the show/hide-archived link beneath the CTA (Missions has a single child); align-items stays stretch so the full-width CTA fills the footer like Missions'. +FNXC:Planning 2026-08-16-14:48: +Planning intentionally retains this bottom-pinned session footer at its current metrics. Missions now uses a top-anchored sidebar CTA, so this footer preserves its own padding, border-top, gap, and column layout for the archived-session link. */ .planning-sidebar-footer { display: flex; @@ -377,8 +377,8 @@ Footer matches MissionManager's mission-manager__sidebar-footer EXACTLY: padding } /* -FNXC:Planning 2026-06-23-02:00: -The New session button must look EXACTLY like Missions' primary sidebar create button (mission-manager__sidebar-cta with btn btn-primary): same full-width fill, same min-height (calc(--space-lg * 2 + --space-xs)), centered icon+label gap, inherited font-size. The earlier .active "inset ring" outline is REMOVED — Missions' CTA has no ring in any state, so the no-session-selected (.active) state now relies solely on the empty/initial detail pane to signal the new-session view, keeping the two buttons visually identical. +FNXC:Planning 2026-08-16-14:48: +Planning intentionally retains this bottom-pinned New session button at its current metrics. Missions now anchors its slightly taller CTA at the top of its sidebar; both controls reuse the shared primary button treatment without requiring exact height parity. The earlier .active "inset ring" outline is REMOVED so the no-session-selected (.active) state relies solely on the empty/initial detail pane. */ .planning-sidebar-new { width: 100%; diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index c5634b8e4a..80c2f80711 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -74,7 +74,7 @@ import { MicButton } from "./MicButton"; const WARNING_ICON = "⚠️"; /* -FNXC:Planning 2026-06-23-02:00: +FNXC:Planning 2026-08-16-14:48: The embedded Planning sidebar is resizable exactly like Missions (MissionManager's MISSION_SIDEBAR_* constants). Default 300px matches Missions' default (calc(--space-lg 16px * 18.75)); min/max/storage mirror Missions so the two views resize identically and persist independently. */ const PLANNING_SIDEBAR_DEFAULT_WIDTH = 300; @@ -957,7 +957,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat }, [isRefineMenuOpen]); /* - FNXC:Planning 2026-06-23-02:00: + FNXC:Planning 2026-08-16-14:48: Resizable Planning sidebar — pointer-drag + arrow-key resize with localStorage persistence, mirroring MissionManager.handleSidebarResizeStart/handleSidebarResizeKeyDown. Width is clamped to PLANNING_SIDEBAR_MIN/MAX and applied as an inline width on the sidebar