Skip to content

Commit a5d3648

Browse files
committed
feat(code): polish Product Creation Dialog
Bug fix: the rounds banner was nested inside the PostHog project section, visually attaching it to the project picker. Now it lives at the top as the framing copy. Polish: - Title size 4 + small rocket icon — this is a beginning, not a row in a settings list - Banner moved directly below the title and tinted with accent colors so it reads as a system-level promise rather than body chrome - Trim banner copy: "rounds of clarifying questions to shape your product." → "rounds of questions before scaffolding." - Indent the project picker / helper text under their respective radios so the relationship is unambiguous - Trim the "later" helper to one line - Submit button: "Create product" → "Start building" with a rocket icon - Voice fix: "What's the name of your new thing?" → "What are we calling it?" (was the only label that didn't use first-person agent voice) Generated-By: PostHog Code Task-Id: 142b4c29-fa5f-4e02-9bc9-de8b22313475
1 parent 3562949 commit a5d3648

2 files changed

Lines changed: 69 additions & 61 deletions

File tree

apps/code/src/renderer/features/scratchpads/components/ProductCreationDialog.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ describe("ProductCreationDialog", () => {
118118
renderDialog();
119119
expect(screen.getByText(/I'll ask up to/)).toBeInTheDocument();
120120
expect(
121-
screen.getByText(/of clarifying questions to shape your product\./),
121+
screen.getByText(/of questions before scaffolding\./),
122122
).toBeInTheDocument();
123123

124124
expect(screen.getByRole("radio", { name: "3" })).toBeChecked();
@@ -138,7 +138,7 @@ describe("ProductCreationDialog", () => {
138138
renderDialog();
139139
fillRequiredFields();
140140

141-
fireEvent.click(screen.getByRole("button", { name: /create product/i }));
141+
fireEvent.click(screen.getByRole("button", { name: /start building/i }));
142142

143143
await waitFor(() => {
144144
expect(mockSagaRun).toHaveBeenCalledTimes(1);
@@ -164,7 +164,7 @@ describe("ProductCreationDialog", () => {
164164
// ProjectPicker mock click sets value to 42
165165
fireEvent.click(screen.getByTestId("project-picker"));
166166

167-
fireEvent.click(screen.getByRole("button", { name: /create product/i }));
167+
fireEvent.click(screen.getByRole("button", { name: /start building/i }));
168168

169169
await waitFor(() => {
170170
expect(mockSagaRun).toHaveBeenCalledTimes(1);
@@ -193,7 +193,7 @@ describe("ProductCreationDialog", () => {
193193
renderDialog();
194194
fillRequiredFields();
195195

196-
const submit = screen.getByRole("button", { name: /create product/i });
196+
const submit = screen.getByRole("button", { name: /start building/i });
197197
fireEvent.click(submit);
198198

199199
await waitFor(() => {
@@ -229,7 +229,7 @@ describe("ProductCreationDialog", () => {
229229
renderDialog();
230230
fillRequiredFields();
231231

232-
fireEvent.click(screen.getByRole("button", { name: /create product/i }));
232+
fireEvent.click(screen.getByRole("button", { name: /start building/i }));
233233

234234
await waitFor(() => {
235235
expect(useScratchpadCreationStore.getState().lastError).toBe(
@@ -239,15 +239,15 @@ describe("ProductCreationDialog", () => {
239239

240240
expect(useScratchpadCreationStore.getState().step).toBe("idle");
241241
expect(
242-
screen.getByRole("button", { name: /create product/i }),
242+
screen.getByRole("button", { name: /start building/i }),
243243
).not.toBeDisabled();
244244
expect(screen.getByRole("alert")).toHaveTextContent("things broke");
245245
});
246246

247247
it("submit is disabled when required fields are empty", () => {
248248
renderDialog();
249249
expect(
250-
screen.getByRole("button", { name: /create product/i }),
250+
screen.getByRole("button", { name: /start building/i }),
251251
).toBeDisabled();
252252
});
253253
});

apps/code/src/renderer/features/scratchpads/components/ProductCreationDialog.tsx

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ProjectPicker } from "@features/scratchpads/components/ProjectPicker";
22
import { useScratchpadCreationStore } from "@features/scratchpads/stores/scratchpadCreationStore";
33
import { useAuthenticatedClient } from "@hooks/useAuthenticatedClient";
4+
import { RocketIcon } from "@radix-ui/react-icons";
45
import {
56
Button,
67
Dialog,
@@ -140,13 +141,38 @@ export function ProductCreationDialog() {
140141
gap="3"
141142
className="transition-all duration-200"
142143
>
143-
<Dialog.Title size="3" className="m-0">
144-
Create a new product
144+
<Dialog.Title size="4" className="m-0">
145+
<Flex align="center" gap="2">
146+
<RocketIcon className="text-(--accent-11)" />
147+
Create a new product
148+
</Flex>
145149
</Dialog.Title>
146150

151+
<Text
152+
as="div"
153+
className="rounded-(--radius-2) border border-(--accent-5) bg-(--accent-2) px-3 py-2 text-(--accent-12) text-[13px] leading-6"
154+
>
155+
I'll ask up to{" "}
156+
<SegmentedControl.Root
157+
size="1"
158+
value={String(rounds)}
159+
onValueChange={(v) => setRounds(clampRounds(Number(v)))}
160+
disabled={isSubmitting}
161+
aria-label="Clarification rounds"
162+
className="!h-[20px] mx-1 inline-flex align-middle text-[12px]"
163+
>
164+
{ROUND_OPTIONS.map((n) => (
165+
<SegmentedControl.Item key={n} value={String(n)}>
166+
{n}
167+
</SegmentedControl.Item>
168+
))}
169+
</SegmentedControl.Root>{" "}
170+
{rounds === 1 ? "round" : "rounds"} of questions before scaffolding.
171+
</Text>
172+
147173
<Flex direction="column" gap="1">
148174
<Text color="gray" className="text-[13px]">
149-
What's the name of your new thing?
175+
What are we calling it?
150176
</Text>
151177
<TextField.Root
152178
value={productName}
@@ -183,58 +209,39 @@ export function ProductCreationDialog() {
183209
disabled={isSubmitting}
184210
>
185211
<Flex direction="column" gap="2">
186-
<Text as="label" className="text-[13px]">
187-
<Flex gap="2" align="center">
188-
<RadioGroup.Item value="later" />
189-
Set up on publish later
190-
</Flex>
191-
</Text>
192-
<Text as="label" className="text-[13px]">
193-
<Flex gap="2" align="center">
194-
<RadioGroup.Item value="existing" />
195-
Use existing project
196-
</Flex>
197-
</Text>
212+
<Flex direction="column" gap="1">
213+
<Text as="label" className="text-[13px]">
214+
<Flex gap="2" align="center">
215+
<RadioGroup.Item value="later" />
216+
Set up on publish later
217+
</Flex>
218+
</Text>
219+
{projectMode === "later" && (
220+
<Text color="gray" className="pl-[26px] text-[13px]">
221+
I'll add the PostHog SDK with placeholders. You'll pick or
222+
create a PostHog project at publish time.
223+
</Text>
224+
)}
225+
</Flex>
226+
<Flex direction="column" gap="1">
227+
<Text as="label" className="text-[13px]">
228+
<Flex gap="2" align="center">
229+
<RadioGroup.Item value="existing" />
230+
Use existing project
231+
</Flex>
232+
</Text>
233+
{projectMode === "existing" && (
234+
<div className="pl-[26px]">
235+
<ProjectPicker
236+
value={selectedProjectId}
237+
onChange={setSelectedProjectId}
238+
disabled={isSubmitting}
239+
/>
240+
</div>
241+
)}
242+
</Flex>
198243
</Flex>
199244
</RadioGroup.Root>
200-
201-
{projectMode === "later" && (
202-
<Text color="gray" className="text-[13px]">
203-
I'll wire up PostHog (analytics, replay, error tracking) with
204-
placeholder credentials so the SDK is in place. You'll pick or
205-
create a real project at publish time.
206-
</Text>
207-
)}
208-
{projectMode === "existing" && (
209-
<ProjectPicker
210-
value={selectedProjectId}
211-
onChange={setSelectedProjectId}
212-
disabled={isSubmitting}
213-
/>
214-
)}
215-
216-
<Text
217-
as="div"
218-
className="rounded-(--radius-2) border border-(--gray-5) bg-(--gray-2) px-3 py-2 text-(--gray-12) text-[13px] leading-6"
219-
>
220-
I'll ask up to{" "}
221-
<SegmentedControl.Root
222-
size="1"
223-
value={String(rounds)}
224-
onValueChange={(v) => setRounds(clampRounds(Number(v)))}
225-
disabled={isSubmitting}
226-
aria-label="Clarification rounds"
227-
className="!h-[20px] mx-1 inline-flex align-middle text-[12px]"
228-
>
229-
{ROUND_OPTIONS.map((n) => (
230-
<SegmentedControl.Item key={n} value={String(n)}>
231-
{n}
232-
</SegmentedControl.Item>
233-
))}
234-
</SegmentedControl.Root>{" "}
235-
{rounds === 1 ? "round" : "rounds"} of clarifying questions to
236-
shape your product.
237-
</Text>
238245
</Flex>
239246

240247
{lastError && (
@@ -263,7 +270,8 @@ export function ProductCreationDialog() {
263270
disabled={!canSubmit}
264271
loading={isSubmitting}
265272
>
266-
Create product
273+
<RocketIcon />
274+
Start building
267275
</Button>
268276
</Flex>
269277
</Flex>

0 commit comments

Comments
 (0)