Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src-tauri/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ pub(crate) struct WorkspaceSettings {
pub(crate) launch_script: Option<String>,
#[serde(default, rename = "launchScripts")]
pub(crate) launch_scripts: Option<Vec<LaunchScriptEntry>>,
#[serde(default, rename = "ideas")]
pub(crate) ideas: Option<Vec<IdeaEntry>>,
#[serde(default, rename = "worktreeSetupScript")]
pub(crate) worktree_setup_script: Option<String>,
}
Expand All @@ -283,6 +285,13 @@ pub(crate) struct LaunchScriptEntry {
pub(crate) label: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct IdeaEntry {
pub(crate) id: String,
pub(crate) title: String,
pub(crate) body: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct WorktreeSetupStatus {
#[serde(rename = "shouldRun")]
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/workspaces/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fn workspace_with_id_and_kind(
codex_args: None,
launch_script: None,
launch_scripts: None,
ideas: None,
worktree_setup_script: None,
},
}
Expand Down
12 changes: 12 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "./styles/diff-viewer.css";
import "./styles/file-tree.css";
import "./styles/panel-tabs.css";
import "./styles/prompts.css";
import "./styles/ideas.css";
import "./styles/debug.css";
import "./styles/terminal.css";
import "./styles/plan.css";
Expand Down Expand Up @@ -93,6 +94,7 @@ import { useCopyThread } from "./features/threads/hooks/useCopyThread";
import { useTerminalController } from "./features/terminal/hooks/useTerminalController";
import { useWorkspaceLaunchScript } from "./features/app/hooks/useWorkspaceLaunchScript";
import { useWorkspaceLaunchScripts } from "./features/app/hooks/useWorkspaceLaunchScripts";
import { useWorkspaceIdeas } from "./features/ideas/hooks/useWorkspaceIdeas";
import { useWorktreeSetupScript } from "./features/app/hooks/useWorktreeSetupScript";
import { useGitCommitController } from "./features/app/hooks/useGitCommitController";
import { WorkspaceHome } from "./features/workspaces/components/WorkspaceHome";
Expand Down Expand Up @@ -821,6 +823,11 @@ function MainApp() {
activeTerminalId,
});

const ideasState = useWorkspaceIdeas({
activeWorkspace,
updateWorkspaceSettings,
});

const worktreeSetupScriptState = useWorktreeSetupScript({
ensureTerminalWithTitle,
restartTerminalSession,
Expand Down Expand Up @@ -1927,6 +1934,11 @@ function MainApp() {
pushError,
syncError,
commitsAhead: gitLogAhead,
ideas: ideasState.ideas,
onCreateIdea: ideasState.createIdea,
onUpdateIdea: ideasState.updateIdea,
onDeleteIdea: ideasState.deleteIdea,
onSendIdea: handleSendPrompt,
onSendPrompt: handleSendPrompt,
onSendPromptToNewAgent: handleSendPromptToNewAgent,
onCreatePrompt: handleCreatePrompt,
Expand Down
2 changes: 1 addition & 1 deletion src/features/app/components/MainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ export function MainHeader({
draftIcon={launchScriptsState.draftIcon}
draftLabel={launchScriptsState.draftLabel}
isSaving={launchScriptsState.isSaving}
error={launchScriptsState.errorById[entry.id] ?? null}
error={launchScriptsState.errorById?.[entry.id] ?? null}
onRun={() => launchScriptsState.onRunScript(entry.id)}
onOpenEditor={() => launchScriptsState.onOpenEditor(entry.id)}
onCloseEditor={launchScriptsState.onCloseEditor}
Expand Down
2 changes: 1 addition & 1 deletion src/features/app/hooks/useGitPanelController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function useGitPanelController({
"split" | "unified"
>("split");
const [filePanelMode, setFilePanelMode] = useState<
"git" | "files" | "prompts"
"git" | "files" | "prompts" | "ideas"
>("git");
const [selectedPullRequest, setSelectedPullRequest] =
useState<GitHubPullRequest | null>(null);
Expand Down
6 changes: 4 additions & 2 deletions src/features/app/hooks/useWorkspaceLaunchScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ type PendingLaunch = {

type UseWorkspaceLaunchScriptOptions = {
activeWorkspace: WorkspaceInfo | null;
updateWorkspaceSettings: (id: string, settings: WorkspaceSettings) => Promise<WorkspaceInfo>;
updateWorkspaceSettings: (
id: string,
settings: Partial<WorkspaceSettings>,
) => Promise<WorkspaceInfo>;
openTerminal: () => void;
ensureLaunchTerminal: (workspaceId: string) => string;
restartLaunchSession: (workspaceId: string, terminalId: string) => Promise<void>;
Expand Down Expand Up @@ -82,7 +85,6 @@ export function useWorkspaceLaunchScript({
const nextScript = trimmed.length > 0 ? draftScript : null;
try {
await updateWorkspaceSettings(activeWorkspace.id, {
...activeWorkspace.settings,
launchScript: nextScript,
});
setEditorOpen(false);
Expand Down
12 changes: 5 additions & 7 deletions src/features/app/hooks/useWorkspaceLaunchScripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ type PendingLaunch = {

type UseWorkspaceLaunchScriptsOptions = {
activeWorkspace: WorkspaceInfo | null;
updateWorkspaceSettings: (id: string, settings: WorkspaceSettings) => Promise<WorkspaceInfo>;
updateWorkspaceSettings: (
id: string,
settings: Partial<WorkspaceSettings>,
) => Promise<WorkspaceInfo>;
openTerminal: () => void;
ensureLaunchTerminal: (workspaceId: string, entry: LaunchScriptEntry, title: string) => string;
restartLaunchSession: (workspaceId: string, terminalId: string) => Promise<void>;
Expand Down Expand Up @@ -196,7 +199,6 @@ export function useWorkspaceLaunchScripts({
},
];
await updateWorkspaceSettings(activeWorkspace.id, {
...activeWorkspace.settings,
launchScripts: nextScripts,
});
setNewEditorOpen(false);
Expand Down Expand Up @@ -240,7 +242,6 @@ export function useWorkspaceLaunchScripts({
};
});
await updateWorkspaceSettings(activeWorkspace.id, {
...activeWorkspace.settings,
launchScripts: nextScripts,
});
setEditorOpenId(null);
Expand Down Expand Up @@ -271,7 +272,6 @@ export function useWorkspaceLaunchScripts({
try {
const nextScripts = launchScripts.filter((entry) => entry.id !== editorOpenId);
await updateWorkspaceSettings(activeWorkspace.id, {
...activeWorkspace.settings,
launchScripts: nextScripts,
});
setEditorOpenId(null);
Expand Down Expand Up @@ -326,9 +326,7 @@ export function useWorkspaceLaunchScripts({

useEffect(() => {
const pending = pendingRunRef.current;
const pendingKey = pending
? `${pending.workspaceId}:${pending.terminalId}`
: null;
const pendingKey = pending ? `${pending.workspaceId}:${pending.terminalId}` : null;
if (
!pending ||
terminalState?.readyKey !== pendingKey ||
Expand Down
2 changes: 1 addition & 1 deletion src/features/git/hooks/usePullRequestComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type UsePullRequestComposerOptions = {
activeWorkspace: WorkspaceInfo | null;
selectedPullRequest: GitHubPullRequest | null;
gitPullRequestDiffs: GitHubPullRequestDiff[];
filePanelMode: "git" | "files" | "prompts";
filePanelMode: "git" | "files" | "prompts" | "ideas";
gitPanelMode: "diff" | "log" | "issues" | "prs";
centerMode: "chat" | "diff";
isCompact: boolean;
Expand Down
96 changes: 96 additions & 0 deletions src/features/ideas/components/IdeasPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @vitest-environment jsdom
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { IdeaEntry } from "../../../types";
import { IdeasPanel } from "./IdeasPanel";

describe("IdeasPanel", () => {
it("hides the title line when title is empty", () => {
const ideas: IdeaEntry[] = [
{ id: "idea-1", title: "", body: "Body only" },
];

render(
<IdeasPanel
ideas={ideas}
filePanelMode="ideas"
onFilePanelModeChange={vi.fn()}
onSendIdea={vi.fn()}
onCreateIdea={vi.fn()}
onUpdateIdea={vi.fn()}
onDeleteIdea={vi.fn()}
/>,
);

expect(screen.getAllByText("Body only").length).toBeGreaterThan(0);
});

it("sends body-only ideas", () => {
const onSendIdea = vi.fn();
const ideas: IdeaEntry[] = [
{ id: "idea-1", title: "", body: "Body only" },
];

const { container } = render(
<IdeasPanel
ideas={ideas}
filePanelMode="ideas"
onFilePanelModeChange={vi.fn()}
onSendIdea={onSendIdea}
onCreateIdea={vi.fn()}
onUpdateIdea={vi.fn()}
onDeleteIdea={vi.fn()}
/>,
);

const row = container.querySelector(".prompt-row");
if (!row) {
throw new Error("Idea row not found");
}
const sendButton = row.querySelector('button[title="Send to current agent"]');
if (!sendButton) {
throw new Error("Send button not found");
}
fireEvent.click(sendButton);

expect(onSendIdea).toHaveBeenCalledWith("Body only");
});

it("confirms delete before invoking callback", () => {
const onDeleteIdea = vi.fn().mockResolvedValue(undefined);
const ideas: IdeaEntry[] = [
{ id: "idea-1", title: "Idea", body: "Body" },
];

const { container } = render(
<IdeasPanel
ideas={ideas}
filePanelMode="ideas"
onFilePanelModeChange={vi.fn()}
onSendIdea={vi.fn()}
onCreateIdea={vi.fn()}
onUpdateIdea={vi.fn()}
onDeleteIdea={onDeleteIdea}
/>,
);

const row = container.querySelector(".prompt-row");
if (!row) {
throw new Error("Idea row not found");
}

const deleteButton = row.querySelector('button[title="Delete idea"]');
if (!deleteButton) {
throw new Error("Delete button not found");
}
fireEvent.click(deleteButton);

expect(onDeleteIdea).not.toHaveBeenCalled();

const confirmButtons = row.querySelectorAll('button.idea-delete');
const confirmButton = confirmButtons[confirmButtons.length - 1];
fireEvent.click(confirmButton);

expect(onDeleteIdea).toHaveBeenCalledWith("idea-1");
});
});
Loading
Loading