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
22 changes: 18 additions & 4 deletions src/__tests__/unit/claude-session-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import assert from 'node:assert/strict';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { pathToFileURL } from 'node:url';

// We test the parser functions by creating temporary JSONL files
// that mimic Claude Code's session storage format.

const TEST_DIR = path.join(os.tmpdir(), `codepilot-test-sessions-${Date.now()}`);
const PROJECTS_DIR = path.join(TEST_DIR, '.claude', 'projects');
const originalEnv = {
HOME: process.env.HOME,
USERPROFILE: process.env.USERPROFILE,
HOMEDRIVE: process.env.HOMEDRIVE,
HOMEPATH: process.env.HOMEPATH,
};

// Helper to create a JSONL session file
function createSessionFile(
Expand Down Expand Up @@ -121,18 +128,25 @@ describe('claude-session-parser', () => {
let parser: typeof import('../../lib/claude-session-parser');

before(async () => {
// Set HOME to our test directory so the parser looks for sessions there
// Point all common home env vars to test dir so os.homedir() is deterministic on Windows/macOS/Linux.
const parsed = path.parse(TEST_DIR);
process.env.HOME = TEST_DIR;
process.env.USERPROFILE = TEST_DIR;
process.env.HOMEDRIVE = parsed.root.replace(/[\\\/]$/, '');
process.env.HOMEPATH = TEST_DIR.slice(parsed.root.length - 1);

// Dynamic import - tsx handles the TypeScript + path alias resolution
parser = await import(parserPath);
parser = await import(pathToFileURL(parserPath).href);
});

after(() => {
// Clean up test directory
fs.rmSync(TEST_DIR, { recursive: true, force: true });
// Restore HOME
process.env.HOME = os.homedir();
// Restore env
process.env.HOME = originalEnv.HOME;
process.env.USERPROFILE = originalEnv.USERPROFILE;
process.env.HOMEDRIVE = originalEnv.HOMEDRIVE;
process.env.HOMEPATH = originalEnv.HOMEPATH;
});

describe('decodeProjectPath', () => {
Expand Down
30 changes: 30 additions & 0 deletions src/components/ai-elements/file-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
useState,
} from "react";

const FILE_TREE_DRAG_MIME = "application/x-codepilot-path";
// Keep a text/* fallback because some drag-and-drop consumers strip unknown custom MIME types.
const FILE_TREE_DRAG_FALLBACK_MIME = "text/x-codepilot-path";

interface FileTreeContextType {
expandedPaths: Set<string>;
togglePath: (path: string) => void;
Expand Down Expand Up @@ -132,6 +136,17 @@ export const FileTreeFolder = ({
togglePath(path);
}, [togglePath, path]);

const handleDragStart = useCallback(
(e: React.DragEvent) => {
const payload = JSON.stringify({ path, name, type: "directory" });
e.dataTransfer.setData(FILE_TREE_DRAG_MIME, payload);
e.dataTransfer.setData(FILE_TREE_DRAG_FALLBACK_MIME, payload);
e.dataTransfer.setData("text/plain", path);
e.dataTransfer.effectAllowed = "copy";
},
[name, path]
);

const folderContextValue = useMemo(
() => ({ isExpanded, name, path }),
[isExpanded, name, path]
Expand All @@ -148,6 +163,8 @@ export const FileTreeFolder = ({
>
<div
className="flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50"
draggable
onDragStart={handleDragStart}
>
<CollapsibleTrigger asChild>
<button
Expand Down Expand Up @@ -232,6 +249,17 @@ export const FileTreeFile = ({

const fileContextValue = useMemo(() => ({ name, path }), [name, path]);

const handleDragStart = useCallback(
(e: React.DragEvent) => {
const payload = JSON.stringify({ path, name, type: "file" });
e.dataTransfer.setData(FILE_TREE_DRAG_MIME, payload);
e.dataTransfer.setData(FILE_TREE_DRAG_FALLBACK_MIME, payload);
e.dataTransfer.setData("text/plain", path);
e.dataTransfer.effectAllowed = "copy";
},
[name, path]
);

return (
<FileTreeFileContext.Provider value={fileContextValue}>
<div
Expand All @@ -242,6 +270,8 @@ export const FileTreeFile = ({
)}
onClick={handleClick}
onKeyDown={handleKeyDown}
draggable
onDragStart={handleDragStart}
role="treeitem"
tabIndex={0}
{...props}
Expand Down
Loading