Skip to content

Commit 8ad05de

Browse files
committed
fix(web): move home quick-action pills back under the greeting
After unifying the home onto the shared workspace shell, the composer became an in-flow element pinned at the bottom — and HomeQuickActions lives inside the composer, so the pills dropped down there too (owner flagged: they belong under 'cheatcode ready to build'). HomeContentPane now exposes a quickActionsSlot (a div after HomeHeadline, captured by callback ref) threaded through HomeComposerFromSearchParams to HomeComposer, which renders <HomeQuickActions/> into it via createPortal. The pills stay in the composer's React subtree — so intentId, the active-pill highlight, and every click side effect (placeholder/surface/skill/repo/project + textarea focus) are unchanged; only the DOM position moved. Verified live: pills sit 29px under the greeting, and clicking 'Mobile app' still switches the composer placeholder. Composer stays pinned at the bottom so the computer-open layout still matches the chat. Gate green (15/15).
1 parent 8ff9eaf commit 8ad05de

3 files changed

Lines changed: 29 additions & 4 deletions

File tree

apps/web/src/components/home/home-composer-from-search-params.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ type InitialComposerParams = {
1515
tool?: IntegrationName | undefined;
1616
};
1717

18-
export function HomeComposerFromSearchParams() {
18+
export function HomeComposerFromSearchParams({
19+
quickActionsSlot,
20+
}: {
21+
quickActionsSlot?: HTMLElement | null | undefined;
22+
}) {
1923
const [params, setParams] = useState<InitialComposerParams | null>(null);
2024

2125
useEffect(() => {
@@ -30,7 +34,7 @@ export function HomeComposerFromSearchParams() {
3034
}, []);
3135

3236
if (!params) {
33-
return <HomeComposer />;
37+
return <HomeComposer quickActionsSlot={quickActionsSlot} />;
3438
}
3539

3640
return (
@@ -40,6 +44,7 @@ export function HomeComposerFromSearchParams() {
4044
initialSkill={params.skill}
4145
initialTool={params.tool}
4246
key={`${params.promptKey ?? ""}:${params.skill ?? ""}:${params.tool ?? ""}:${params.skillCreator ? "sc" : ""}`}
47+
quickActionsSlot={quickActionsSlot}
4348
skillCreator={params.skillCreator}
4449
/>
4550
);

apps/web/src/components/home/home-composer.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
useRef,
2222
useState,
2323
} from "react";
24+
import { createPortal } from "react-dom";
2425
import { toast } from "sonner";
2526
import { AuthModal } from "@/components/auth/auth-modal";
2627
import { UpgradeDialog } from "@/components/billing/upgrade-dialog";
@@ -161,12 +162,14 @@ export function HomeComposer({
161162
initialPromptKey,
162163
initialSkill,
163164
initialTool,
165+
quickActionsSlot,
164166
skillCreator = false,
165167
}: {
166168
initialPrompt?: string | undefined;
167169
initialPromptKey?: string | undefined;
168170
initialSkill?: string | undefined;
169171
initialTool?: IntegrationName | undefined;
172+
quickActionsSlot?: HTMLElement | null | undefined;
170173
skillCreator?: boolean | undefined;
171174
}) {
172175
const initial = useMemo(() => resolveInitialSkill(initialSkill ?? null), [initialSkill]);
@@ -384,7 +387,15 @@ export function HomeComposer({
384387

385388
return (
386389
<div className="mt-6 w-full">
387-
<HomeQuickActions activeIntentId={intentId} onIntentClick={selectQuickIntent} />
390+
{/* The pills live in the greeting cluster (above), but their intent state is
391+
owned here — a portal keeps them in this React subtree so `intentId` /
392+
`selectQuickIntent` stay wired while rendering into the greeting slot. */}
393+
{quickActionsSlot
394+
? createPortal(
395+
<HomeQuickActions activeIntentId={intentId} onIntentClick={selectQuickIntent} />,
396+
quickActionsSlot,
397+
)
398+
: null}
388399
{skillCreatorMode ? <SkillCreatorSuggestions onPick={(text) => setValue(text)} /> : null}
389400
<form className="mt-8 w-full" onSubmit={submit}>
390401
<div className="relative z-10 mx-auto flex w-full max-w-[708px] flex-col gap-0">

apps/web/src/components/home/home-workspace.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ export function HomeWorkspace() {
6161
}
6262

6363
function HomeContentPane() {
64+
// The quick-action pills belong in the greeting cluster (below the headline),
65+
// but their intent state is owned by the composer at the bottom of the pane and
66+
// is entangled with the composer's skill/repo/project state, textarea focus, and
67+
// search-param seeding. The composer portals the pills into this slot so they
68+
// render in the greeting cluster while staying in the composer's React tree —
69+
// the intent wiring flows through the React tree, not the DOM, so it is
70+
// unchanged. See `HomeComposer`.
71+
const [quickActionsSlot, setQuickActionsSlot] = useState<HTMLElement | null>(null);
6472
return (
6573
<div className="relative flex min-h-0 flex-1 flex-col bg-white">
6674
<div className="chat-scrollbar flex min-h-0 flex-1 flex-col items-center justify-center overflow-y-auto px-6 py-10">
@@ -70,10 +78,11 @@ function HomeContentPane() {
7078
<HomeGreeting />
7179
</Suspense>
7280
<HomeHeadline />
81+
<div className="mt-6 w-full" ref={setQuickActionsSlot} />
7382
</div>
7483
</div>
7584
<div className="shrink-0 px-6 pb-8">
76-
<HomeComposerFromSearchParams />
85+
<HomeComposerFromSearchParams quickActionsSlot={quickActionsSlot} />
7786
</div>
7887
</div>
7988
);

0 commit comments

Comments
 (0)