Skip to content
Merged
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
24 changes: 8 additions & 16 deletions apps/server/src/git/Layers/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,19 +787,12 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
Effect.gen(function* () {
const repoDir = yield* makeTempDir("okcode-git-manager-");
yield* initRepo(repoDir);
const remoteDir = yield* createBareRemote();
yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
yield* runGit(repoDir, ["checkout", "-b", "feature/rebase-before-commit"]);

const updaterDir = yield* makeTempDir("okcode-git-manager-updater-");
yield* runGit(updaterDir, ["clone", "--branch", "main", remoteDir, "."]);
yield* runGit(updaterDir, ["config", "user.email", "test@example.com"]);
yield* runGit(updaterDir, ["config", "user.name", "Test User"]);
fs.writeFileSync(path.join(updaterDir, "base.txt"), "remote main update\n");
yield* runGit(updaterDir, ["add", "base.txt"]);
yield* runGit(updaterDir, ["commit", "-m", "Remote main update"]);
yield* runGit(updaterDir, ["push", "origin", "main"]);
yield* runGit(repoDir, ["checkout", "main"]);
fs.writeFileSync(path.join(repoDir, "base.txt"), "local main update\n");
yield* runGit(repoDir, ["add", "base.txt"]);
yield* runGit(repoDir, ["commit", "-m", "Local main update"]);
yield* runGit(repoDir, ["checkout", "feature/rebase-before-commit"]);

fs.writeFileSync(path.join(repoDir, "README.md"), "hello\nrebased feature work\n");

Expand All @@ -812,15 +805,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
});

expect(result.commit.status).toBe("created");

const remoteMainSha = yield* runGit(repoDir, ["rev-parse", "origin/main"]).pipe(
const mainSha = yield* runGit(repoDir, ["rev-parse", "main"]).pipe(
Effect.map((gitResult) => gitResult.stdout.trim()),
);
const mergeBase = yield* runGit(repoDir, ["merge-base", "HEAD", "origin/main"]).pipe(
const mergeBase = yield* runGit(repoDir, ["merge-base", "HEAD", "main"]).pipe(
Effect.map((gitResult) => gitResult.stdout.trim()),
);

expect(mergeBase).toBe(remoteMainSha);
expect(mergeBase).toBe(mainSha);
}),
30_000,
);
Expand Down
43 changes: 23 additions & 20 deletions apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,26 +552,29 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
} catch {
// Ignore invalid JSON — treat as no ref
}
return {
id: row.threadId,
projectId: row.projectId,
title: row.title,
model: row.model,
runtimeMode: row.runtimeMode,
interactionMode: row.interactionMode,
branch: row.branch,
worktreePath: row.worktreePath,
...(githubRef ? { githubRef } : {}),
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
messages: messagesByThread.get(row.threadId) ?? [],
proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
activities: activitiesByThread.get(row.threadId) ?? [],
checkpoints: checkpointsByThread.get(row.threadId) ?? [],
session: sessionsByThread.get(row.threadId) ?? null,
};
const thread = Object.assign(
{
id: row.threadId,
projectId: row.projectId,
title: row.title,
model: row.model,
runtimeMode: row.runtimeMode,
interactionMode: row.interactionMode,
branch: row.branch,
worktreePath: row.worktreePath,
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
messages: messagesByThread.get(row.threadId) ?? [],
proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
activities: activitiesByThread.get(row.threadId) ?? [],
checkpoints: checkpointsByThread.get(row.threadId) ?? [],
session: sessionsByThread.get(row.threadId) ?? null,
},
githubRef ? { githubRef } : {},
) as OrchestrationThread;
return thread;
});

const snapshot = {
Expand Down
1 change: 0 additions & 1 deletion apps/server/src/persistence/Services/ProjectionThreads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
ThreadId,
TurnId,
} from "@okcode/contracts";
import type { GitHubRef } from "@okcode/contracts";
import { Option, Schema, ServiceMap } from "effect";
import type { Effect } from "effect";

Expand Down
102 changes: 0 additions & 102 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {
DEFAULT_CHAT_FILE_MIME_TYPE,
DEFAULT_MODEL_BY_PROVIDER,
type ClaudeCodeEffort,
type GitHubIssueDetail,
type GitHubRef,
type MessageId,
type ProjectScript,
type ModelSlug,
Expand Down Expand Up @@ -184,7 +182,6 @@ import { deriveLatestContextWindowSnapshot } from "../lib/contextWindow";
import { shouldUseCompactComposerFooter } from "./composerFooterLayout";
import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";
import { ComposerPromptEditor, type ComposerPromptEditorHandle } from "./ComposerPromptEditor";
import { IssueThreadDialog } from "./IssueThreadDialog";
import { PullRequestThreadDialog } from "./PullRequestThreadDialog";
import { MessagesTimeline } from "./chat/MessagesTimeline";
import { ChatHeader } from "./chat/ChatHeader";
Expand Down Expand Up @@ -221,7 +218,6 @@ import {
deriveComposerSendState,
LAST_INVOKED_SCRIPT_BY_PROJECT_KEY,
LastInvokedScriptByProjectSchema,
IssueDialogState,
PullRequestDialogState,
QueuedMessage,
readFileAsDataUrl,
Expand Down Expand Up @@ -563,7 +559,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
const [composerHighlightedItemId, setComposerHighlightedItemId] = useState<string | null>(null);
const [pullRequestDialogState, setPullRequestDialogState] =
useState<PullRequestDialogState | null>(null);
const [issueDialogState, setIssueDialogState] = useState<IssueDialogState | null>(null);
const [pendingProjectScriptRun, setPendingProjectScriptRun] = useState<{
script: ProjectScript;
inputIds: string[];
Expand Down Expand Up @@ -746,24 +741,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
setPullRequestDialogState(null);
}, []);

const openIssueDialog = useCallback(
(reference?: string) => {
if (!isLocalDraftThread) {
return;
}
setIssueDialogState({
initialReference: reference ?? null,
key: Date.now(),
});
setComposerHighlightedItemId(null);
},
[isLocalDraftThread],
);

const closeIssueDialog = useCallback(() => {
setIssueDialogState(null);
}, []);

const openOrReuseProjectDraftThread = useCallback(
async (input: { branch: string; worktreePath: string | null; envMode: DraftThreadEnvMode }) => {
if (!activeProject) {
Expand Down Expand Up @@ -826,68 +803,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
[openOrReuseProjectDraftThread],
);

const handleStartIssueThread = useCallback(
async (input: { issue: GitHubIssueDetail; mode: "local" | "worktree" }) => {
if (!activeProject) {
return;
}
// Extract owner/repo from the issue URL
let owner = "";
let repo = "";
try {
const url = new URL(input.issue.url);
const parts = url.pathname.split("/").filter(Boolean);
if (parts.length >= 2) {
owner = parts[0]!;
repo = parts[1]!;
}
} catch {
// Fallback: cannot parse URL
}

if (!owner || !repo) {
return;
}

const githubRef = {
kind: "issue" as const,
owner,
repo,
number: input.issue.number,
} satisfies GitHubRef;

// Always create a fresh thread for an issue
clearProjectDraftThreadId(activeProject.id);
const nextId = newThreadId();
setProjectDraftThreadId(activeProject.id, nextId, {
createdAt: new Date().toISOString(),
runtimeMode: DEFAULT_RUNTIME_MODE,
envMode: input.mode,
githubRef,
});

// Pre-populate the composer with an issue context prompt
const { setPrompt: storeSetPrompt } = useComposerDraftStore.getState();
const labelsText =
input.issue.labels.length > 0
? `Labels: ${input.issue.labels.map((l) => l.name).join(", ")}\n`
: "";
const bodyPreview = input.issue.body
? input.issue.body.slice(0, 2000) + (input.issue.body.length > 2000 ? "\n..." : "")
: "";
storeSetPrompt(
nextId,
`Resolve GitHub issue #${input.issue.number}: ${input.issue.title}\n\n${labelsText}${bodyPreview ? `${bodyPreview}\n\n` : ""}Please analyze this issue and implement a fix.`,
);

await navigate({
to: "/$threadId",
params: { threadId: nextId },
});
},
[activeProject, clearProjectDraftThreadId, navigate, setProjectDraftThreadId],
);

useEffect(() => {
if (!activeThread?.id) return;
if (!latestTurnSettled) return;
Expand Down Expand Up @@ -1609,11 +1524,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {

const pendingContext = useCodeViewerStore((state) => state.pendingContext);
const clearPendingContext = useCodeViewerStore((state) => state.clearPendingContext);
const codeViewerOpen = useCodeViewerStore((state) => state.isOpen);
const toggleCodeViewer = useCodeViewerStore((state) => state.toggle);
const diffViewerOpen = useDiffViewerStore((state) => state.isOpen);
const openDiffViewerConversation = useDiffViewerStore((state) => state.openConversation);
const closeDiffViewer = useDiffViewerStore((state) => state.close);
const openTurnDiffViewer = useDiffViewerStore((state) => state.openTurnDiff);
const handleOpenTurnDiff = useCallback(
(turnId: TurnId, filePath?: string) => {
Expand All @@ -1623,14 +1533,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
[activeThread, openTurnDiffViewer],
);

const handleToggleDiffViewer = useCallback(() => {
if (diffViewerOpen) {
closeDiffViewer();
} else if (activeThread) {
openDiffViewerConversation(activeThread.id);
}
}, [diffViewerOpen, closeDiffViewer, activeThread, openDiffViewerConversation]);

// When Cmd+L is pressed in the code viewer, insert the @file:lines mention into the composer
useEffect(() => {
if (!pendingContext) return;
Expand Down Expand Up @@ -4853,8 +4755,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
terminalAvailable={activeProject !== undefined}
terminalOpen={terminalState.terminalOpen}
terminalToggleShortcutLabel={terminalToggleShortcutLabel}
codeViewerOpen={codeViewerOpen}
diffViewerOpen={diffViewerOpen}
previewAvailable={isElectron && activeProject !== undefined}
previewOpen={previewOpen}
previewDock={previewDock}
Expand All @@ -4872,8 +4772,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
onImportProjectScripts={importProjectScripts}
onToggleTerminal={toggleTerminalVisibility}
onPrefetchTerminal={preloadThreadTerminalDrawer}
onToggleCodeViewer={toggleCodeViewer}
onToggleDiffViewer={handleToggleDiffViewer}
onTogglePreview={() => activeProjectId && togglePreviewOpen(activeProjectId)}
onTogglePreviewLayout={() => activeProjectId && togglePreviewLayout(activeProjectId)}
onMinimize={onMinimize}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ConnectionIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { memo } from "react";
import { WifiIcon, WifiOffIcon, RefreshCwIcon } from "lucide-react";
import { RefreshCwIcon } from "lucide-react";

import { useConnectionHealth } from "../hooks/useConnectionHealth";
import { Tooltip, TooltipTrigger, TooltipPopup } from "./ui/tooltip";
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/ScreenshotTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ async function captureRegion(rect: {
function loadImage(src: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.addEventListener("load", () => resolve(img), { once: true });
img.addEventListener("error", reject, { once: true });
img.src = src;
});
}
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/Sidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";

describe("Sidebar file tree mounting", () => {
it("keeps the workspace file tree mounted when the files section is collapsed", () => {
describe("Sidebar file tree shortcut", () => {
it("opens the right-panel file tree instead of mounting the tree inline", () => {
const src = readFileSync(resolve(import.meta.dirname, "./Sidebar.tsx"), "utf8");

expect(src).toContain("<WorkspaceFileTree");
expect(src).toContain('className={cn(filesCollapsedByProject.has(project.id) && "hidden")}');
expect(src).not.toContain("!filesCollapsedByProject.has(project.id) && (");
expect(src).toContain('aria-label="Open file tree"');
expect(src).toContain('useRightPanelStore.getState().open("files")');
expect(src).not.toContain("<WorkspaceFileTree");
});
});
Loading
Loading