Skip to content

Commit 1658a13

Browse files
authored
Refine draft empty state (#82)
1 parent 74b7054 commit 1658a13

5 files changed

Lines changed: 18 additions & 76 deletions

File tree

apps/web/src/components/ChatView.browser.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,7 +2456,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
24562456
}
24572457
});
24582458

2459-
it("keeps source control closed by default on wide draft thread routes before first send", async () => {
2459+
it("defaults source control open on wide draft thread routes before first send", async () => {
24602460
const draftId = DraftId.make("draft-source-control-before-start");
24612461
useComposerDraftStore.setState({
24622462
draftThreadsByThreadKey: {
@@ -2494,18 +2494,19 @@ describe("ChatView timeline estimator parity (full app)", () => {
24942494
"Unable to find source control toggle.",
24952495
);
24962496
expect(sourceControlToggle.disabled).toBe(false);
2497-
expect(sourceControlToggle.hasAttribute("data-pressed")).toBe(false);
2498-
await expect
2499-
.element(page.getByRole("heading", { name: "Source Control" }))
2500-
.not.toBeInTheDocument();
2497+
expect(sourceControlToggle.hasAttribute("data-pressed")).toBe(true);
2498+
expect(document.body.textContent).not.toContain("Explain this codebase");
2499+
expect(document.body.textContent).not.toContain("Review my uncommitted changes");
2500+
expect(document.body.textContent).not.toContain("Fix a bug");
2501+
await expect.element(page.getByRole("heading", { name: "Source Control" })).toBeVisible();
25012502

25022503
sourceControlToggle.click();
25032504

25042505
await vi.waitFor(
25052506
() => {
2506-
expect(mounted.router.state.location.search).toMatchObject({ sourceControl: "1" });
2507-
expect(sourceControlToggle.hasAttribute("data-pressed")).toBe(true);
2508-
expect(document.querySelector('h2[aria-label="Source Control"]')).not.toBeNull();
2507+
expect(mounted.router.state.location.search).toMatchObject({ sourceControl: "0" });
2508+
expect(sourceControlToggle.hasAttribute("data-pressed")).toBe(false);
2509+
expect(document.querySelector('h2[aria-label="Source Control"]')).toBeNull();
25092510
},
25102511
{ timeout: 8_000, interval: 16 },
25112512
);

apps/web/src/components/ChatView.tsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import { isElectron } from "../env";
4747
import { ensureLocalApi, readLocalApi } from "../localApi";
4848
import {
4949
closeRightPanelSearchParams,
50-
isDraftSourceControlPanelOpen,
5150
isSourceControlPanelOpen,
5251
parseDiffRouteSearch,
5352
preserveRightPanelSearchParamsForDraftNavigation,
@@ -1263,12 +1262,9 @@ export default function ChatView(props: ChatViewProps) {
12631262
composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
12641263
const isLocalDraftThread = !isServerThread && localDraftThread !== undefined;
12651264
const canCheckoutPullRequestIntoThread = isLocalDraftThread;
1266-
// Mirror the owning route's panel default: server threads open on wide
1267-
// viewports, drafts stay closed until explicitly opened.
1268-
const sourceControlOpen =
1269-
routeKind === "server"
1270-
? isSourceControlPanelOpen(rawSearch, { defaultOpen: !shouldUseRightPanelSheet })
1271-
: isDraftSourceControlPanelOpen(rawSearch);
1265+
const sourceControlOpen = isSourceControlPanelOpen(rawSearch, {
1266+
defaultOpen: !shouldUseRightPanelSheet,
1267+
});
12721268
// The diff panel is a drill-in of source control, so the header toggle
12731269
// treats the right panel as one unit: it stays pressed while a diff is
12741270
// open and pressing it closes the whole panel.
@@ -1323,30 +1319,19 @@ export default function ChatView(props: ChatViewProps) {
13231319
// General Chat threads run in a hidden scratch workspace: source-control,
13241320
// scripts, and open-in affordances stay hidden even though a project exists.
13251321
const isGeneralChatThread = activeProject?.kind === "general-chat";
1326-
const insertDraftStarterPrompt = useCallback(
1327-
(text: string) => {
1328-
setComposerDraftPrompt(composerDraftTarget, text);
1329-
window.requestAnimationFrame(() => {
1330-
composerRef.current?.focusAtEnd();
1331-
});
1332-
},
1333-
[composerDraftTarget, composerRef, setComposerDraftPrompt],
1334-
);
13351322
const draftTimelineEmptyState = useMemo(
13361323
() =>
13371324
isLocalDraftThread && draftThread ? (
13381325
<DraftEmptyState
13391326
currentProjectRef={scopeProjectRef(draftThread.environmentId, draftThread.projectId)}
13401327
currentProjectName={activeProject?.name ?? null}
13411328
isGeneralChat={isGeneralChatThread}
1342-
onInsertPrompt={insertDraftStarterPrompt}
13431329
/>
13441330
) : undefined,
13451331
[
13461332
activeProject?.name,
13471333
draftThread?.environmentId,
13481334
draftThread?.projectId,
1349-
insertDraftStarterPrompt,
13501335
isGeneralChatThread,
13511336
isLocalDraftThread,
13521337
],

apps/web/src/components/chat/DraftEmptyState.tsx

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { scopedProjectKey, scopeProjectRef } from "@threadlines/client-runtime";
22
import type { ScopedProjectRef } from "@threadlines/contracts";
3-
import {
4-
BugIcon,
5-
CheckIcon,
6-
CompassIcon,
7-
GitCompareArrowsIcon,
8-
MessagesSquareIcon,
9-
} from "lucide-react";
3+
import { CheckIcon, MessagesSquareIcon } from "lucide-react";
104
import { useMemo } from "react";
115

126
import { usePrimaryEnvironmentId } from "../../environments/primary";
@@ -16,7 +10,6 @@ import { resolveGeneralChatsProjectRef } from "../../lib/generalChats";
1610
import { selectGeneralChatsProjectAcrossEnvironments, useStore } from "../../store";
1711
import { ProjectFavicon } from "../ProjectFavicon";
1812
import { RecentThreadsList } from "../RecentThreadsList";
19-
import { Button } from "../ui/button";
2013
import { riseDelay, ThreadlinesFigure } from "../ThreadlinesFigure";
2114
import {
2215
Menu,
@@ -32,21 +25,12 @@ interface DraftEmptyStateProps {
3225
currentProjectRef: ScopedProjectRef | null;
3326
currentProjectName: string | null;
3427
isGeneralChat: boolean;
35-
onInsertPrompt?: ((text: string) => void) | undefined;
3628
}
3729

38-
/** Starter prompts inserted into the composer for editing, not sent. */
39-
const STARTER_PROMPTS = [
40-
{ icon: CompassIcon, prompt: "Explain this codebase" },
41-
{ icon: GitCompareArrowsIcon, prompt: "Review my uncommitted changes" },
42-
{ icon: BugIcon, prompt: "Fix a bug" },
43-
] as const;
44-
4530
export function DraftEmptyState({
4631
currentProjectRef,
4732
currentProjectName,
4833
isGeneralChat,
49-
onInsertPrompt,
5034
}: DraftEmptyStateProps) {
5135
const { handleNewThread, orderedProjects } = useHandleNewThread();
5236
const activeEnvironmentId = useStore((state) => state.activeEnvironmentId);
@@ -131,28 +115,8 @@ export function DraftEmptyState({
131115
?
132116
</h2>
133117

134-
{!isGeneralChat && onInsertPrompt ? (
135-
<div
136-
className="no-thread-rise mt-5 flex flex-wrap items-center justify-center gap-1"
137-
style={riseDelay("0.26s")}
138-
>
139-
{STARTER_PROMPTS.map(({ icon: Icon, prompt }) => (
140-
<Button
141-
className="text-muted-foreground hover:text-foreground"
142-
key={prompt}
143-
onClick={() => onInsertPrompt(prompt)}
144-
size="sm"
145-
variant="ghost"
146-
>
147-
<Icon />
148-
{prompt}
149-
</Button>
150-
))}
151-
</div>
152-
) : null}
153-
154118
<RecentThreadsList
155-
className="no-thread-rise mt-10 w-full [--no-thread-delay:0.4s]"
119+
className="no-thread-rise mt-10 w-full [--no-thread-delay:0.26s]"
156120
limit={5}
157121
testId="draft-empty-recent-thread"
158122
/>

apps/web/src/diffRouteSearch.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,6 @@ export function isSourceControlPanelOpen(
133133
return options.defaultOpen ?? true;
134134
}
135135

136-
/**
137-
* Drafts start with the source-control panel closed: a pristine thread has no
138-
* turn activity to review, and the home/new-thread surface should read as a
139-
* clean page. Only explicit `sourceControl=1` opens it; the wide-viewport
140-
* default that applies to server threads does not.
141-
*/
142-
export function isDraftSourceControlPanelOpen(search: DiffRouteSearch): boolean {
143-
return isSourceControlPanelOpen(search, { defaultOpen: false });
144-
}
145-
146136
export function parseDiffRouteSearch(search: Record<string, unknown>): DiffRouteSearch {
147137
const diff = isDiffOpenValue(search.diff) ? "1" : undefined;
148138
const diffMode = diff && search.diffMode === "workingTree" ? "workingTree" : undefined;

apps/web/src/routes/_chat.draft.$draftId.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ChatRightPanelInlineSidebar } from "../components/ChatRightPanelInlineS
99
import { useComposerDraftStore, DraftId } from "../composerDraftStore";
1010
import {
1111
closeRightPanelSearchParams,
12-
isDraftSourceControlPanelOpen,
12+
isSourceControlPanelOpen,
1313
parseDiffRouteSearch,
1414
stripRightPanelSearchParams,
1515
} from "../diffRouteSearch";
@@ -83,7 +83,9 @@ function DraftChatThreadRouteView() {
8383
}),
8484
[draftSession?.promotedTo, serverThread, serverThreadHasTurnActivity, serverThreadRef],
8585
);
86-
const rawSourceControlOpen = isDraftSourceControlPanelOpen(search);
86+
const rawSourceControlOpen = isSourceControlPanelOpen(search, {
87+
defaultOpen: !shouldUseSourceControlSheet,
88+
});
8789
const draftProjectRef = draftSession
8890
? scopeProjectRef(draftSession.environmentId, draftSession.projectId)
8991
: null;

0 commit comments

Comments
 (0)