From 7d7868df69ec352cd8c16d749a1f8dbb483197fe Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:36:21 +0200 Subject: [PATCH] feat(control): the project screen asks its question with fewer things MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-on to #483. That gave the phone one PANE per screen; this reduces what the remaining pane puts in front of you. Measured on the live detail screen at 390px before touching it: 1088px tall, 114 words, 22 controls — and SIX different ways to start the same action (type + Send, tap Suggested next, tap Next best, tap a history chip, open Prompt library, open More). Six entry points to one job, with nothing saying which is the normal one. Two changes, both removing duplication rather than hiding function: 1. THE "SEND TO AGENT" HEADING IS GONE. The composer directly above already asks the question — its placeholder is literally "What should the agent work on?" — so the kicker restated it, and worse put a section break between the two halves of ONE decision. Measured: the composer ended at y=779 and Next best began at y=924, reading as a separate feature rather than the other way to answer the same prompt. The honesty chip stays; it says something the button cannot ("Builder online"). 2. ONE REUSE AFFORDANCE INSTEAD OF TWO. RecentPromptChips and ProjectPromptLibrary do the same job with the same handler — `onPick={onCustomChange}` and `onSelect={onCustomChange}`, both "put text in the composer" — yet rendered as two blocks with two headings, 4 of the 22 controls and ~150px, below the fold, competing with the composer they feed. They now share one labelled disclosure, closed by default: a convenience does not get to open itself on the screen where space is scarcest. CSS-only (`max-lg:hidden` + a `lg:hidden` toggle), so there is no hydration branch and desktop is untouched — that card has the room and never had the problem. Result, same environment, same project, before → after: 1088px → 888px (-18%) 114 words → 91 (-20%) 22 controls → 19 NOT DONE, and worth naming rather than implying otherwise. Of the 19 that remain, six are header context/navigation (streak, Focus terminal, Open here, branch, project details, agent setup) that you pass before reaching the question — that is the next lever. And "Autopilot on" and "Pause automatic continuation" are two adjacent controls about whether it keeps going by itself; they are genuinely distinct (automationMode vs per-project auto-continue), so merging them is a semantics change I am not making blind. pnpm run verify passes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P --- .../control/project-intent-panel.tsx | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/components/control/project-intent-panel.tsx b/src/components/control/project-intent-panel.tsx index 000d21d6..aaf3d13e 100644 --- a/src/components/control/project-intent-panel.tsx +++ b/src/components/control/project-intent-panel.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useCallback } from "react"; -import { Eraser, Loader2 } from "lucide-react"; +import { ChevronDown, ChevronUp, Eraser, Loader2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { useMicComposer } from "@/hooks/use-mic-composer"; import { postJson } from "@/lib/api/fetch"; @@ -154,6 +154,10 @@ export function IntentButtonPanel({ }) { const [showMore, setShowMore] = useState(false); const [showLibraryPrompts, setShowLibraryPrompts] = useState(false); + // Phone-only: the two reuse surfaces at the foot of this panel share one + // disclosure. Closed by default — a convenience does not get to open itself + // on the screen where space is scarcest. + const [reuseOpen, setReuseOpen] = useState(false); const [clearingContext, setClearingContext] = useState(false); const builderPresence = useBuilderPresence(); const dispatchHonesty = deriveExecutorHonestyLabel({ @@ -317,8 +321,16 @@ export function IntentButtonPanel({ {/* Action area — hidden when banner is active (banner owns the primary CTA) */} {!bannerActive && primary && (
-
-

Send to agent

+ {/* No "Send to agent" heading here any more. + The composer sitting directly above already asks the question — + its placeholder is "What should the agent work on?" — so the + kicker restated it and, worse, put a section break between the + two halves of ONE decision. Measured on a phone: the composer + ended at y=779 and this button started at y=924, reading as a + separate feature rather than the other way to answer the same + prompt. The honesty chip stays; it says something the button + cannot ("Builder online"). */} +
{/* Primary CTA: Next best — full width, visually elevated. @@ -427,19 +439,41 @@ export function IntentButtonPanel({
)} - + {/* ONE reuse affordance on a phone, not two. + These two components do the same job with the same handler — + `onPick={onCustomChange}` and `onSelect={onCustomChange}`, both + "put text in the composer" — yet they rendered as two separate + blocks with two headings. Counted on the live detail screen at + 390px, they accounted for 4 of 22 controls and ~150px, below the + fold, competing with the composer they feed. + They stay one tap away, together, under a single label. From `lg` + the toggle is hidden and both are simply visible, as before: the + desktop card has the room and never had the problem. */} + - +
+ + + +
); }