Skip to content

Commit 3562949

Browse files
committed
refactor(code): drop [UNPUBLISHED] prefix logic — never shipped
Auto-create at scaffold time was the only thing that produced the prefix; now that's gone. Cleaning up the dead code: - usePublishScratchpad: drops the rename step (no prefix to strip) and the productName input. Hook just publishes. - useDeleteScratchpad: drops the project deletion path entirely. Drafts only ever reference user-picked projects, which we never delete. - posthogClient.deleteProject + useDeleteProject hook: deleted; no consumers remain. - DraftTaskHeaderActions: takes taskTitle directly instead of fetching the linked project to derive a name. - PublishDialog: stops threading productName through to the publish hook; only uses it as a pre-fill for the create-new-project name. Tests trimmed accordingly. Generated-By: PostHog Code Task-Id: 142b4c29-fa5f-4e02-9bc9-de8b22313475
1 parent 7c3e16d commit 3562949

12 files changed

Lines changed: 84 additions & 394 deletions

File tree

apps/code/src/renderer/api/posthogClient.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -603,16 +603,6 @@ export class PostHogAPIClient {
603603
return data as Schemas.Team;
604604
}
605605

606-
async deleteProject(projectId: number): Promise<void> {
607-
await this.api.delete(
608-
// @ts-expect-error this endpoint is not in the generated client
609-
"/api/projects/{project_id}/",
610-
{
611-
path: { project_id: projectId.toString() },
612-
},
613-
);
614-
}
615-
616606
async listSignalSourceConfigs(
617607
projectId: number,
618608
): Promise<SignalSourceConfig[]> {

apps/code/src/renderer/features/posthog-projects/hooks/useCreateProject.test.ts

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
88
const mockClient = vi.hoisted(() => ({
99
createProject: vi.fn(),
1010
updateProject: vi.fn(),
11-
deleteProject: vi.fn(),
1211
getCurrentUser: vi.fn(),
1312
}));
1413

@@ -29,7 +28,6 @@ vi.mock("@utils/logger", () => ({
2928

3029
// Imports after mocks so the modules pick up the mocked dependencies.
3130
import { useCreateProject } from "./useCreateProject";
32-
import { useDeleteProject } from "./useDeleteProject";
3331
import { useUpdateProject } from "./useUpdateProject";
3432

3533
// --- Helpers ------------------------------------------------------------
@@ -55,7 +53,7 @@ function newClient() {
5553

5654
// --- Tests --------------------------------------------------------------
5755

58-
describe("useCreateProject / useUpdateProject / useDeleteProject", () => {
56+
describe("useCreateProject / useUpdateProject", () => {
5957
beforeEach(() => {
6058
vi.clearAllMocks();
6159
});
@@ -131,52 +129,6 @@ describe("useCreateProject / useUpdateProject / useDeleteProject", () => {
131129
expect(mockClient.updateProject).toHaveBeenCalledWith(5, { name: "new" });
132130
});
133131

134-
it("deleteProject: happy path calls client.deleteProject with the id", async () => {
135-
mockClient.deleteProject.mockResolvedValueOnce(undefined);
136-
137-
const queryClient = newClient();
138-
const { result } = renderHook(() => useDeleteProject(), {
139-
wrapper: makeWrapper(queryClient),
140-
});
141-
142-
await waitFor(async () => {
143-
await result.current.mutateAsync({ projectId: 9 });
144-
});
145-
146-
expect(mockClient.deleteProject).toHaveBeenCalledTimes(1);
147-
expect(mockClient.deleteProject).toHaveBeenCalledWith(9);
148-
});
149-
150-
it("deleteProject: 404 from API resolves successfully (already gone)", async () => {
151-
mockClient.deleteProject.mockRejectedValueOnce(
152-
new Error('Failed request: [404] {"detail":"Not found."}'),
153-
);
154-
155-
const queryClient = newClient();
156-
const { result } = renderHook(() => useDeleteProject(), {
157-
wrapper: makeWrapper(queryClient),
158-
});
159-
160-
await expect(
161-
result.current.mutateAsync({ projectId: 123 }),
162-
).resolves.toBeUndefined();
163-
});
164-
165-
it("deleteProject: 403 surfaces a permissions error", async () => {
166-
mockClient.deleteProject.mockRejectedValueOnce(
167-
new Error('Failed request: [403] {"detail":"forbidden"}'),
168-
);
169-
170-
const queryClient = newClient();
171-
const { result } = renderHook(() => useDeleteProject(), {
172-
wrapper: makeWrapper(queryClient),
173-
});
174-
175-
await expect(
176-
result.current.mutateAsync({ projectId: 123 }),
177-
).rejects.toThrow(/insufficient permissions/i);
178-
});
179-
180132
it("createProject: invalidates the projects-list cache on success", async () => {
181133
mockClient.createProject.mockResolvedValueOnce({ id: 1, name: "x" });
182134

apps/code/src/renderer/features/posthog-projects/hooks/useDeleteProject.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

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

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { PublishDialog } from "@features/scratchpads/components/PublishDialog";
22
import { useDraftTaskIds } from "@features/scratchpads/hooks/useDraftTaskInfo";
3-
import { useAuthenticatedClient } from "@hooks/useAuthenticatedClient";
43
import { UploadSimpleIcon } from "@phosphor-icons/react";
54
import { Button } from "@radix-ui/themes";
6-
import { trpc } from "@renderer/trpc";
7-
import { useQuery } from "@tanstack/react-query";
85
import { useState } from "react";
96

107
interface DraftTaskHeaderActionsProps {
118
taskId: string;
9+
taskTitle: string;
1210
}
1311

1412
/**
@@ -17,56 +15,22 @@ interface DraftTaskHeaderActionsProps {
1715
*/
1816
export function DraftTaskHeaderActions({
1917
taskId,
18+
taskTitle,
2019
}: DraftTaskHeaderActionsProps) {
2120
const draftTaskIds = useDraftTaskIds();
2221
const isDraft = draftTaskIds.has(taskId);
2322

24-
const posthogClient = useAuthenticatedClient();
25-
26-
const manifestQuery = useQuery(
27-
trpc.scratchpad.readManifest.queryOptions(
28-
{ taskId },
29-
{
30-
enabled: isDraft,
31-
staleTime: 30_000,
32-
},
33-
),
34-
);
35-
36-
const projectId = manifestQuery.data?.projectId;
37-
const hasLinkedProject = projectId != null;
38-
const projectQuery = useQuery({
39-
queryKey: ["project", projectId],
40-
queryFn: () => {
41-
if (!hasLinkedProject) return null;
42-
return posthogClient.getProject(projectId).catch(() => null);
43-
},
44-
enabled: isDraft && hasLinkedProject,
45-
staleTime: 60_000,
46-
});
47-
4823
const [dialogOpen, setDialogOpen] = useState(false);
4924

5025
if (!isDraft) return null;
5126

52-
// When no project is linked we still render the Publish button so the user
53-
// can see the path forward — the dialog/hook will explain that linking a
54-
// project is a prerequisite.
55-
const productName = hasLinkedProject
56-
? (projectQuery.data?.name ?? "")
57-
: manifestQuery.data
58-
? ""
59-
: "";
60-
const repoNameDefault = productName;
61-
6227
return (
6328
<>
6429
<Button
6530
size="1"
6631
variant="soft"
6732
color="green"
6833
onClick={() => setDialogOpen(true)}
69-
disabled={hasLinkedProject && !productName}
7034
>
7135
<UploadSimpleIcon size={12} weight="bold" />
7236
Publish
@@ -76,8 +40,8 @@ export function DraftTaskHeaderActions({
7640
open={dialogOpen}
7741
onOpenChange={setDialogOpen}
7842
taskId={taskId}
79-
defaultRepoName={repoNameDefault}
80-
productName={productName}
43+
defaultRepoName={taskTitle}
44+
productName={taskTitle}
8145
/>
8246
)}
8347
</>

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ function fillRequiredFields() {
8484
fireEvent.change(screen.getByPlaceholderText("Uber for dogs"), {
8585
target: { value: "My Product" },
8686
});
87-
fireEvent.change(screen.getByPlaceholderText(/On-demand dog walks/), {
88-
target: { value: "An idea" },
89-
});
87+
fireEvent.change(
88+
screen.getByPlaceholderText(/Web app to get a dog delivered on demand/),
89+
{ target: { value: "An idea" } },
90+
);
9091
}
9192

9293
describe("ProductCreationDialog", () => {
@@ -113,24 +114,24 @@ describe("ProductCreationDialog", () => {
113114
useScratchpadCreationStore.getState().reset();
114115
});
115116

116-
it("defaults rounds to 3 and exposes 1..5 in the segmented control", () => {
117+
it("defaults rounds to 3 and exposes 1..4 in the segmented control", () => {
117118
renderDialog();
118-
expect(screen.getByText(/We'll ask up to/)).toBeInTheDocument();
119+
expect(screen.getByText(/I'll ask up to/)).toBeInTheDocument();
119120
expect(
120121
screen.getByText(/of clarifying questions to shape your product\./),
121122
).toBeInTheDocument();
122123

123124
expect(screen.getByRole("radio", { name: "3" })).toBeChecked();
124-
for (const n of ["1", "2", "3", "4", "5"]) {
125+
for (const n of ["1", "2", "3", "4"]) {
125126
expect(screen.getByRole("radio", { name: n })).toBeInTheDocument();
126127
}
127128

128-
// Switch to 5
129-
fireEvent.click(screen.getByRole("radio", { name: "5" }));
130-
expect(screen.getByRole("radio", { name: "5" })).toBeChecked();
129+
// Switch to 4
130+
fireEvent.click(screen.getByRole("radio", { name: "4" }));
131+
expect(screen.getByRole("radio", { name: "4" })).toBeChecked();
131132

132-
// The selector caps at 5 — there is no "6" option rendered.
133-
expect(screen.queryByRole("radio", { name: "6" })).toBeNull();
133+
// The selector caps at 4 — there is no "5" option rendered.
134+
expect(screen.queryByRole("radio", { name: "5" })).toBeNull();
134135
});
135136

136137
it("submit with default 'later' mode passes no projectId (link decided at publish time)", async () => {

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

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import { useState } from "react";
1818

1919
const log = logger.scope("product-creation-dialog");
2020

21-
const ROUND_OPTIONS = [1, 2, 3, 4, 5] as const;
2221
const MIN_ROUNDS = 1;
23-
const MAX_ROUNDS = 5;
22+
const MAX_ROUNDS = 4;
23+
const ROUND_OPTIONS: number[] = Array.from(
24+
{ length: MAX_ROUNDS },
25+
(_, i) => i + 1,
26+
);
2427
const DEFAULT_ROUNDS = 3;
2528

2629
type ProjectMode = "later" | "existing";
@@ -141,32 +144,9 @@ export function ProductCreationDialog() {
141144
Create a new product
142145
</Dialog.Title>
143146

144-
<Text
145-
as="div"
146-
className="rounded-(--radius-2) border border-(--gray-5) bg-(--gray-2) px-3 py-2 text-(--gray-12) text-[13px] leading-6"
147-
>
148-
We'll ask up to{" "}
149-
<SegmentedControl.Root
150-
size="1"
151-
value={String(rounds)}
152-
onValueChange={(v) => setRounds(clampRounds(Number(v)))}
153-
disabled={isSubmitting}
154-
aria-label="Clarification rounds"
155-
className="!h-[20px] mx-1 inline-flex align-middle text-[12px]"
156-
>
157-
{ROUND_OPTIONS.map((n) => (
158-
<SegmentedControl.Item key={n} value={String(n)}>
159-
{n}
160-
</SegmentedControl.Item>
161-
))}
162-
</SegmentedControl.Root>{" "}
163-
{rounds === 1 ? "round" : "rounds"} of clarifying questions to shape
164-
your product.
165-
</Text>
166-
167147
<Flex direction="column" gap="1">
168148
<Text color="gray" className="text-[13px]">
169-
Product name
149+
What's the name of your new thing?
170150
</Text>
171151
<TextField.Root
172152
value={productName}
@@ -180,12 +160,12 @@ export function ProductCreationDialog() {
180160

181161
<Flex direction="column" gap="1">
182162
<Text color="gray" className="text-[13px]">
183-
Initial idea
163+
What would you like me to build?
184164
</Text>
185165
<TextArea
186166
value={initialIdea}
187167
onChange={(e) => setInitialIdea(e.target.value)}
188-
placeholder="On-demand dog walks and rides to the vet. Owners book through a mobile app, walkers/drivers accept gigs nearby, payments and tips are handled in-app..."
168+
placeholder="Web app to get a dog delivered on demand, or something."
189169
size="2"
190170
rows={5}
191171
disabled={isSubmitting}
@@ -206,7 +186,7 @@ export function ProductCreationDialog() {
206186
<Text as="label" className="text-[13px]">
207187
<Flex gap="2" align="center">
208188
<RadioGroup.Item value="later" />
209-
Let's do this later
189+
Set up on publish later
210190
</Flex>
211191
</Text>
212192
<Text as="label" className="text-[13px]">
@@ -220,7 +200,7 @@ export function ProductCreationDialog() {
220200

221201
{projectMode === "later" && (
222202
<Text color="gray" className="text-[13px]">
223-
We'll wire up PostHog (analytics, replay, error tracking) with
203+
I'll wire up PostHog (analytics, replay, error tracking) with
224204
placeholder credentials so the SDK is in place. You'll pick or
225205
create a real project at publish time.
226206
</Text>
@@ -232,6 +212,29 @@ export function ProductCreationDialog() {
232212
disabled={isSubmitting}
233213
/>
234214
)}
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>
235238
</Flex>
236239

237240
{lastError && (

0 commit comments

Comments
 (0)