diff --git a/components/Chat/ChatGreeting.tsx b/components/Chat/ChatGreeting.tsx
index e95c002f4..b19457e0e 100644
--- a/components/Chat/ChatGreeting.tsx
+++ b/components/Chat/ChatGreeting.tsx
@@ -3,6 +3,7 @@ import ImageWithFallback from "@/components/ImageWithFallback";
import ValuationHero from "@/components/Home/ValuationHero";
import useHomeValuation from "@/hooks/useHomeValuation";
import TasksModule from "@/components/Home/TasksModule";
+import HomeSuggestedPrompts from "@/components/Home/HomeSuggestedPrompts";
import { VALUATION_URL } from "@/lib/consts";
/**
@@ -11,8 +12,8 @@ import { VALUATION_URL } from "@/lib/consts";
* funnel: the artist-scoped catalog valuation hero when the account has
* measured data for the current scope, otherwise the "Ask me about"
* greeting plus a CTA into the valuation funnel; the "label at work"
- * tasks module renders beneath it (self-hiding when it has nothing to
- * show). All context arrives via provider-backed hooks — no props beyond
+ * tasks module and the suggested prompt chips render beneath it (each
+ * self-hiding when it has nothing to show). All context arrives via provider-backed hooks — no props beyond
* the fade flag the chat empty state already owned, so the chat component
* needs no knowledge of what this header shows.
*/
@@ -36,6 +37,7 @@ export function ChatGreeting({ isVisible }: { isVisible: boolean }) {
measuredTrackCount={valuation.measuredTrackCount}
/>
+
);
}
@@ -78,6 +80,9 @@ export function ChatGreeting({ isVisible }: { isVisible: boolean }) {
+
+
+
);
}
diff --git a/components/Home/HomeSuggestedPrompts.tsx b/components/Home/HomeSuggestedPrompts.tsx
new file mode 100644
index 000000000..793fedadf
--- /dev/null
+++ b/components/Home/HomeSuggestedPrompts.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { useVercelChatContext } from "@/providers/VercelChatProvider";
+import { useArtistProvider } from "@/providers/ArtistProvider";
+import useHomeValuation from "@/hooks/useHomeValuation";
+import useHomeTasksModuleState from "@/hooks/useHomeTasksModuleState";
+import { getHomeSuggestedPrompts } from "@/lib/home/getHomeSuggestedPrompts";
+
+/**
+ * Suggested prompt chips for the homepage command bar (recoupable/chat#1850).
+ * Fully self-contained per the KISS review: module relevance comes from the
+ * same provider-backed hooks the header uses (react-query dedupes by key),
+ * and clicking a chip prefills the existing chat input via the provider's
+ * `setInput` — the send itself goes through the untouched send path. Renders
+ * nothing when no module has data, so no mount needs to know it exists.
+ */
+const HomeSuggestedPrompts = () => {
+ const { setInput } = useVercelChatContext();
+ const { selectedArtist } = useArtistProvider();
+ const valuation = useHomeValuation();
+ const tasksState = useHomeTasksModuleState();
+
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: valuation.show,
+ hasRuns: tasksState.view === "runs",
+ artistName: selectedArtist?.name || "",
+ });
+
+ if (prompts.length === 0) return null;
+
+ return (
+
+ {prompts.map((p) => (
+
+ ))}
+
+ );
+};
+
+export default HomeSuggestedPrompts;
diff --git a/components/Home/TasksModule.tsx b/components/Home/TasksModule.tsx
index f06a7eeec..0f46d4735 100644
--- a/components/Home/TasksModule.tsx
+++ b/components/Home/TasksModule.tsx
@@ -1,9 +1,7 @@
"use client";
import Link from "next/link";
-import { useArtistProvider } from "@/providers/ArtistProvider";
-import { useTaskRuns } from "@/hooks/useTaskRuns";
-import { getHomeTasksModuleState } from "@/lib/home/getHomeTasksModuleState";
+import useHomeTasksModuleState from "@/hooks/useHomeTasksModuleState";
import HomeRunRow from "./HomeRunRow";
import StarterTaskCard from "./StarterTaskCard";
@@ -14,15 +12,7 @@ import StarterTaskCard from "./StarterTaskCard";
* failure so the homepage never blocks on it.
*/
const TasksModule = () => {
- const { data: runs, isLoading, isError } = useTaskRuns();
- const { selectedArtist } = useArtistProvider();
-
- const state = getHomeTasksModuleState({
- runs,
- runsFailed: isError,
- isLoading,
- hasArtist: !!selectedArtist?.account_id,
- });
+ const state = useHomeTasksModuleState();
if (state.view === "hidden") return null;
diff --git a/hooks/useHomeTasksModuleState.ts b/hooks/useHomeTasksModuleState.ts
new file mode 100644
index 000000000..46f2cd431
--- /dev/null
+++ b/hooks/useHomeTasksModuleState.ts
@@ -0,0 +1,25 @@
+import { useTaskRuns } from "@/hooks/useTaskRuns";
+import { useArtistProvider } from "@/providers/ArtistProvider";
+import {
+ getHomeTasksModuleState,
+ HomeTasksModuleState,
+} from "@/lib/home/getHomeTasksModuleState";
+
+/**
+ * Composed hook behind the homepage tasks module. Shared by `TasksModule`
+ * and `HomePage` (which needs the visibility to dock the command bar);
+ * react-query dedupes the underlying `GET /api/tasks/runs` by key.
+ */
+const useHomeTasksModuleState = (): HomeTasksModuleState => {
+ const { data: runs, isLoading, isError } = useTaskRuns();
+ const { selectedArtist } = useArtistProvider();
+
+ return getHomeTasksModuleState({
+ runs,
+ runsFailed: isError,
+ isLoading,
+ hasArtist: !!selectedArtist?.account_id,
+ });
+};
+
+export default useHomeTasksModuleState;
diff --git a/lib/home/__tests__/getHomeSuggestedPrompts.test.ts b/lib/home/__tests__/getHomeSuggestedPrompts.test.ts
new file mode 100644
index 000000000..762b99894
--- /dev/null
+++ b/lib/home/__tests__/getHomeSuggestedPrompts.test.ts
@@ -0,0 +1,63 @@
+import { describe, expect, it } from "vitest";
+import { getHomeSuggestedPrompts } from "@/lib/home/getHomeSuggestedPrompts";
+
+describe("getHomeSuggestedPrompts", () => {
+ it("wires chips to the visible modules", () => {
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: true,
+ hasRuns: true,
+ artistName: "Del Water Gap",
+ });
+
+ expect(prompts.map((p) => p.label)).toEqual([
+ "Why did my valuation change?",
+ "What did my last task run do?",
+ "What should Del Water Gap do this week?",
+ ]);
+ });
+
+ it("caps at three chips", () => {
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: true,
+ hasRuns: true,
+ artistName: "Del Water Gap",
+ });
+ expect(prompts.length).toBeLessThanOrEqual(3);
+ });
+
+ it("falls back to a catalog-value chip when no valuation is shown", () => {
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: false,
+ hasRuns: false,
+ artistName: "Del Water Gap",
+ });
+
+ expect(prompts.map((p) => p.label)).toEqual([
+ "What's my catalog worth?",
+ "What should Del Water Gap do this week?",
+ ]);
+ });
+
+ it("omits the artist chip when no artist is selected", () => {
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: true,
+ hasRuns: false,
+ artistName: "",
+ });
+
+ expect(prompts.map((p) => p.label)).toEqual([
+ "Why did my valuation change?",
+ ]);
+ });
+
+ it("provides a sendable prompt for every chip", () => {
+ const prompts = getHomeSuggestedPrompts({
+ hasValuation: true,
+ hasRuns: true,
+ artistName: "Del Water Gap",
+ });
+ for (const p of prompts) {
+ expect(p.prompt.length).toBeGreaterThan(10);
+ }
+ });
+});
diff --git a/lib/home/getHomeSuggestedPrompts.ts b/lib/home/getHomeSuggestedPrompts.ts
new file mode 100644
index 000000000..45c9f17ee
--- /dev/null
+++ b/lib/home/getHomeSuggestedPrompts.ts
@@ -0,0 +1,60 @@
+export interface HomeSuggestedPrompt {
+ label: string;
+ prompt: string;
+}
+
+interface GetHomeSuggestedPromptsParams {
+ hasValuation: boolean;
+ hasRuns: boolean;
+ artistName: string;
+}
+
+const MAX_PROMPTS = 3;
+
+/**
+ * Suggested prompt chips for the homepage command bar, wired to whichever
+ * modules are visible (valuation hero, tasks module) so the chat's job is
+ * interrogating the dashboard data (recoupable/chat#1850).
+ */
+export function getHomeSuggestedPrompts({
+ hasValuation,
+ hasRuns,
+ artistName,
+}: GetHomeSuggestedPromptsParams): HomeSuggestedPrompt[] {
+ const prompts: HomeSuggestedPrompt[] = [];
+
+ if (hasValuation) {
+ prompts.push({
+ label: "Why did my valuation change?",
+ prompt:
+ "Why did my catalog valuation change? Walk through the latest " +
+ "measurements and explain what moved the estimate.",
+ });
+ } else {
+ prompts.push({
+ label: "What's my catalog worth?",
+ prompt: `Estimate the catalog value for ${
+ artistName || "my roster"
+ } from public streaming data.`,
+ });
+ }
+
+ if (hasRuns) {
+ prompts.push({
+ label: "What did my last task run do?",
+ prompt:
+ "What did my last task run do? Summarize its output and anything it sent.",
+ });
+ }
+
+ if (artistName) {
+ prompts.push({
+ label: `What should ${artistName} do this week?`,
+ prompt:
+ `Given ${artistName}'s latest streams and catalog value, ` +
+ "what should we focus on this week?",
+ });
+ }
+
+ return prompts.slice(0, MAX_PROMPTS);
+}