claudeship 0.2.12 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/apps/server/dist/app.module.js +10 -0
- package/apps/server/dist/app.module.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.js +156 -0
- package/apps/server/dist/chat/prompts/web-system-prompt.js.map +1 -1
- package/apps/server/dist/checkpoint/checkpoint.controller.d.ts +19 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js +93 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.module.d.ts +2 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js +25 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.service.d.ts +41 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js +261 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js.map +1 -0
- package/apps/server/dist/database/database.controller.d.ts +23 -0
- package/apps/server/dist/database/database.controller.js +109 -0
- package/apps/server/dist/database/database.controller.js.map +1 -0
- package/apps/server/dist/database/database.module.d.ts +2 -0
- package/apps/server/dist/database/database.module.js +25 -0
- package/apps/server/dist/database/database.module.js.map +1 -0
- package/apps/server/dist/database/database.service.d.ts +32 -0
- package/apps/server/dist/database/database.service.js +238 -0
- package/apps/server/dist/database/database.service.js.map +1 -0
- package/apps/server/dist/env/env.controller.d.ts +14 -0
- package/apps/server/dist/env/env.controller.js +84 -0
- package/apps/server/dist/env/env.controller.js.map +1 -0
- package/apps/server/dist/env/env.module.d.ts +2 -0
- package/apps/server/dist/env/env.module.js +25 -0
- package/apps/server/dist/env/env.module.js.map +1 -0
- package/apps/server/dist/env/env.service.d.ts +21 -0
- package/apps/server/dist/env/env.service.js +194 -0
- package/apps/server/dist/env/env.service.js.map +1 -0
- package/apps/server/dist/preview/preview.controller.d.ts +5 -0
- package/apps/server/dist/preview/preview.controller.js +41 -0
- package/apps/server/dist/preview/preview.controller.js.map +1 -1
- package/apps/server/dist/preview/preview.service.d.ts +20 -0
- package/apps/server/dist/preview/preview.service.js +51 -2
- package/apps/server/dist/preview/preview.service.js.map +1 -1
- package/apps/server/dist/project/project.controller.d.ts +10 -1
- package/apps/server/dist/project/project.controller.js +57 -0
- package/apps/server/dist/project/project.controller.js.map +1 -1
- package/apps/server/dist/project/project.service.d.ts +15 -0
- package/apps/server/dist/project/project.service.js +111 -0
- package/apps/server/dist/project/project.service.js.map +1 -1
- package/apps/server/dist/project-context/project-context.controller.d.ts +42 -0
- package/apps/server/dist/project-context/project-context.controller.js +127 -0
- package/apps/server/dist/project-context/project-context.controller.js.map +1 -0
- package/apps/server/dist/project-context/project-context.module.d.ts +2 -0
- package/apps/server/dist/project-context/project-context.module.js +25 -0
- package/apps/server/dist/project-context/project-context.module.js.map +1 -0
- package/apps/server/dist/project-context/project-context.service.d.ts +36 -0
- package/apps/server/dist/project-context/project-context.service.js +260 -0
- package/apps/server/dist/project-context/project-context.service.js.map +1 -0
- package/apps/server/dist/testing/testing.controller.d.ts +24 -0
- package/apps/server/dist/testing/testing.controller.js +126 -0
- package/apps/server/dist/testing/testing.controller.js.map +1 -0
- package/apps/server/dist/testing/testing.module.d.ts +2 -0
- package/apps/server/dist/testing/testing.module.js +26 -0
- package/apps/server/dist/testing/testing.module.js.map +1 -0
- package/apps/server/dist/testing/testing.service.d.ts +62 -0
- package/apps/server/dist/testing/testing.service.js +269 -0
- package/apps/server/dist/testing/testing.service.js.map +1 -0
- package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/server/package.json +1 -1
- package/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/app-build-manifest.json +5 -5
- package/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/cache/.previewinfo +1 -1
- package/apps/web/.next/cache/.rscinfo +1 -1
- package/apps/web/.next/cache/.tsbuildinfo +1 -1
- package/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/prerender-manifest.json +10 -10
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/_not-found.html +1 -1
- package/apps/web/.next/server/app/_not-found.rsc +2 -2
- package/apps/web/.next/server/app/index.html +1 -1
- package/apps/web/.next/server/app/index.rsc +3 -3
- package/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/project/[id]/page.js +2 -2
- package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings.html +1 -1
- package/apps/web/.next/server/app/settings.rsc +3 -3
- package/apps/web/.next/server/chunks/392.js +1 -1
- package/apps/web/.next/server/pages/404.html +1 -1
- package/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/static/chunks/574-1fe2bcd6cfb41646.js +1 -0
- package/apps/web/.next/static/chunks/app/page-f19cfa58541ca83d.js +1 -0
- package/apps/web/.next/static/chunks/app/project/[id]/page-dffaa1d02f012216.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-d1318c2fd58729a5.js +1 -0
- package/apps/web/.next/static/css/0a24552d9794f8c8.css +3 -0
- package/apps/web/.next/trace +18 -17
- package/apps/web/node_modules/.bin/eslint +2 -2
- package/apps/web/package.json +2 -1
- package/apps/web/src/components/checkpoint/CheckpointPanel.tsx +384 -0
- package/apps/web/src/components/database/DatabasePanel.tsx +405 -0
- package/apps/web/src/components/env/EnvPanel.tsx +356 -0
- package/apps/web/src/components/preview/ConsoleViewer.tsx +270 -0
- package/apps/web/src/components/preview/ErrorOverlay.tsx +189 -0
- package/apps/web/src/components/preview/PreviewPanel.tsx +148 -6
- package/apps/web/src/components/testing/TestRunner.tsx +481 -0
- package/apps/web/src/components/ui/tabs.tsx +55 -0
- package/apps/web/src/components/visual-editor/VisualEditor.tsx +382 -0
- package/apps/web/src/components/workspace/WorkspaceLayout.tsx +66 -4
- package/apps/web/src/lib/api.ts +5 -2
- package/package.json +1 -1
- package/apps/web/.next/static/chunks/298-6f3d6b321c288cd3.js +0 -1
- package/apps/web/.next/static/chunks/app/page-3d093f7f480a8599.js +0 -1
- package/apps/web/.next/static/chunks/app/project/[id]/page-e5cda6f9050b0a52.js +0 -1
- package/apps/web/.next/static/chunks/app/settings/page-92d28565c3d8c755.js +0 -1
- package/apps/web/.next/static/css/8f946046a2047594.css +0 -3
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_ssgManifest.js +0 -0
|
@@ -6,9 +6,9 @@ case `uname` in
|
|
|
6
6
|
esac
|
|
7
7
|
|
|
8
8
|
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@
|
|
9
|
+
export NODE_PATH="/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules/eslint/bin/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules/eslint/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/node_modules"
|
|
10
10
|
else
|
|
11
|
-
export NODE_PATH="/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@
|
|
11
|
+
export NODE_PATH="/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules/eslint/bin/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules/eslint/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/eslint@9.39.2_jiti@1.21.7/node_modules:/home/runner/work/ClaudeShip/ClaudeShip/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
12
|
fi
|
|
13
13
|
if [ -x "$basedir/node" ]; then
|
|
14
14
|
exec "$basedir/node" "$basedir/../eslint/bin/eslint.js" "$@"
|
package/apps/web/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudeship/web",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "node scripts/dev-with-recovery.mjs",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
17
17
|
"@radix-ui/react-label": "^2.1.8",
|
|
18
18
|
"@radix-ui/react-radio-group": "^1.3.8",
|
|
19
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
19
20
|
"class-variance-authority": "^0.7.1",
|
|
20
21
|
"clsx": "^2.1.1",
|
|
21
22
|
"lucide-react": "^0.460.0",
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import {
|
|
5
|
+
GitBranch,
|
|
6
|
+
RotateCcw,
|
|
7
|
+
Plus,
|
|
8
|
+
RefreshCw,
|
|
9
|
+
AlertCircle,
|
|
10
|
+
Clock,
|
|
11
|
+
FileText,
|
|
12
|
+
ChevronDown,
|
|
13
|
+
ChevronRight,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import { Button } from "@/components/ui/button";
|
|
16
|
+
import { Input } from "@/components/ui/input";
|
|
17
|
+
import { api } from "@/lib/api";
|
|
18
|
+
|
|
19
|
+
interface Checkpoint {
|
|
20
|
+
id: string;
|
|
21
|
+
hash: string;
|
|
22
|
+
message: string;
|
|
23
|
+
author: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
filesChanged: number;
|
|
26
|
+
insertions: number;
|
|
27
|
+
deletions: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface FileDiff {
|
|
31
|
+
path: string;
|
|
32
|
+
status: "added" | "modified" | "deleted";
|
|
33
|
+
additions: number;
|
|
34
|
+
deletions: number;
|
|
35
|
+
diff: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface CheckpointPanelProps {
|
|
39
|
+
projectId: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function CheckpointPanel({ projectId }: CheckpointPanelProps) {
|
|
43
|
+
const [checkpoints, setCheckpoints] = useState<Checkpoint[]>([]);
|
|
44
|
+
const [selectedCheckpoint, setSelectedCheckpoint] = useState<Checkpoint | null>(null);
|
|
45
|
+
const [diff, setDiff] = useState<FileDiff[] | null>(null);
|
|
46
|
+
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
const [error, setError] = useState<string | null>(null);
|
|
49
|
+
const [newMessage, setNewMessage] = useState("");
|
|
50
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
51
|
+
|
|
52
|
+
// Fetch checkpoints
|
|
53
|
+
const fetchCheckpoints = useCallback(async () => {
|
|
54
|
+
setIsLoading(true);
|
|
55
|
+
setError(null);
|
|
56
|
+
try {
|
|
57
|
+
const result = await api.get<Checkpoint[]>(
|
|
58
|
+
`/projects/${projectId}/checkpoints`
|
|
59
|
+
);
|
|
60
|
+
setCheckpoints(result);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
setError(e instanceof Error ? e.message : "Failed to load checkpoints");
|
|
63
|
+
} finally {
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
}
|
|
66
|
+
}, [projectId]);
|
|
67
|
+
|
|
68
|
+
// Check for changes
|
|
69
|
+
const checkStatus = useCallback(async () => {
|
|
70
|
+
try {
|
|
71
|
+
const result = await api.get<{ hasChanges: boolean }>(
|
|
72
|
+
`/projects/${projectId}/checkpoints/status`
|
|
73
|
+
);
|
|
74
|
+
setHasChanges(result.hasChanges);
|
|
75
|
+
} catch {
|
|
76
|
+
// Ignore
|
|
77
|
+
}
|
|
78
|
+
}, [projectId]);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
fetchCheckpoints();
|
|
82
|
+
checkStatus();
|
|
83
|
+
}, [fetchCheckpoints, checkStatus]);
|
|
84
|
+
|
|
85
|
+
// Poll for changes
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const interval = setInterval(checkStatus, 5000);
|
|
88
|
+
return () => clearInterval(interval);
|
|
89
|
+
}, [checkStatus]);
|
|
90
|
+
|
|
91
|
+
const handleSelectCheckpoint = async (checkpoint: Checkpoint) => {
|
|
92
|
+
setSelectedCheckpoint(checkpoint);
|
|
93
|
+
setExpandedFiles(new Set());
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await api.get<{ files: FileDiff[] }>(
|
|
97
|
+
`/projects/${projectId}/checkpoints/diff?from=${checkpoint.hash}`
|
|
98
|
+
);
|
|
99
|
+
setDiff(result.files);
|
|
100
|
+
} catch {
|
|
101
|
+
setDiff(null);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleCreateCheckpoint = async () => {
|
|
106
|
+
if (!newMessage.trim()) {
|
|
107
|
+
setError("Please enter a checkpoint message");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setIsLoading(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
try {
|
|
114
|
+
await api.post(`/projects/${projectId}/checkpoints`, {
|
|
115
|
+
message: newMessage,
|
|
116
|
+
});
|
|
117
|
+
setNewMessage("");
|
|
118
|
+
await fetchCheckpoints();
|
|
119
|
+
await checkStatus();
|
|
120
|
+
} catch (e) {
|
|
121
|
+
setError(e instanceof Error ? e.message : "Failed to create checkpoint");
|
|
122
|
+
} finally {
|
|
123
|
+
setIsLoading(false);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handleRestore = async (checkpoint: Checkpoint) => {
|
|
128
|
+
if (
|
|
129
|
+
!confirm(
|
|
130
|
+
`Restore to "${checkpoint.message}"? This will discard current changes.`
|
|
131
|
+
)
|
|
132
|
+
) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setIsLoading(true);
|
|
137
|
+
setError(null);
|
|
138
|
+
try {
|
|
139
|
+
await api.post(`/projects/${projectId}/checkpoints/${checkpoint.hash}/restore`);
|
|
140
|
+
await fetchCheckpoints();
|
|
141
|
+
await checkStatus();
|
|
142
|
+
setSelectedCheckpoint(null);
|
|
143
|
+
setDiff(null);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
setError(e instanceof Error ? e.message : "Failed to restore checkpoint");
|
|
146
|
+
} finally {
|
|
147
|
+
setIsLoading(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const toggleFileExpand = (path: string) => {
|
|
152
|
+
const newExpanded = new Set(expandedFiles);
|
|
153
|
+
if (newExpanded.has(path)) {
|
|
154
|
+
newExpanded.delete(path);
|
|
155
|
+
} else {
|
|
156
|
+
newExpanded.add(path);
|
|
157
|
+
}
|
|
158
|
+
setExpandedFiles(newExpanded);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const formatTime = (timestamp: number) => {
|
|
162
|
+
const date = new Date(timestamp);
|
|
163
|
+
const now = new Date();
|
|
164
|
+
const diff = now.getTime() - date.getTime();
|
|
165
|
+
|
|
166
|
+
if (diff < 60000) return "Just now";
|
|
167
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
|
168
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
|
169
|
+
return date.toLocaleDateString();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (isLoading && checkpoints.length === 0) {
|
|
173
|
+
return (
|
|
174
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
175
|
+
<RefreshCw className="h-5 w-5 animate-spin mr-2" />
|
|
176
|
+
Loading...
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="flex h-full flex-col">
|
|
183
|
+
{/* Header */}
|
|
184
|
+
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
185
|
+
<h3 className="font-medium flex items-center gap-2">
|
|
186
|
+
<GitBranch className="h-4 w-4" />
|
|
187
|
+
Checkpoints
|
|
188
|
+
</h3>
|
|
189
|
+
<Button
|
|
190
|
+
variant="ghost"
|
|
191
|
+
size="sm"
|
|
192
|
+
onClick={fetchCheckpoints}
|
|
193
|
+
disabled={isLoading}
|
|
194
|
+
className="h-8 w-8 p-0"
|
|
195
|
+
>
|
|
196
|
+
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
|
|
197
|
+
</Button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Error */}
|
|
201
|
+
{error && (
|
|
202
|
+
<div className="flex items-center gap-2 px-4 py-2 bg-destructive/10 text-destructive text-sm">
|
|
203
|
+
<AlertCircle className="h-4 w-4" />
|
|
204
|
+
{error}
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{/* Create Checkpoint */}
|
|
209
|
+
{hasChanges && (
|
|
210
|
+
<div className="border-b p-4 space-y-2">
|
|
211
|
+
<div className="flex gap-2">
|
|
212
|
+
<Input
|
|
213
|
+
value={newMessage}
|
|
214
|
+
onChange={(e) => setNewMessage(e.target.value)}
|
|
215
|
+
placeholder="Checkpoint message..."
|
|
216
|
+
className="flex-1"
|
|
217
|
+
onKeyDown={(e) => {
|
|
218
|
+
if (e.key === "Enter") handleCreateCheckpoint();
|
|
219
|
+
}}
|
|
220
|
+
/>
|
|
221
|
+
<Button onClick={handleCreateCheckpoint} disabled={isLoading} size="sm">
|
|
222
|
+
<Plus className="h-4 w-4 mr-1" />
|
|
223
|
+
Save
|
|
224
|
+
</Button>
|
|
225
|
+
</div>
|
|
226
|
+
<p className="text-xs text-muted-foreground">
|
|
227
|
+
You have unsaved changes
|
|
228
|
+
</p>
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
|
|
232
|
+
<div className="flex flex-1 overflow-hidden">
|
|
233
|
+
{/* Timeline */}
|
|
234
|
+
<div className="w-72 border-r overflow-auto">
|
|
235
|
+
<div className="p-2 space-y-1">
|
|
236
|
+
{/* Current state */}
|
|
237
|
+
<div className="flex items-start gap-3 p-3 rounded-lg bg-primary/5 border border-primary/20">
|
|
238
|
+
<div className="w-3 h-3 rounded-full bg-primary mt-1" />
|
|
239
|
+
<div className="flex-1 min-w-0">
|
|
240
|
+
<p className="font-medium text-sm">Current State</p>
|
|
241
|
+
<p className="text-xs text-muted-foreground">
|
|
242
|
+
{hasChanges ? "Unsaved changes" : "No changes"}
|
|
243
|
+
</p>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Checkpoints */}
|
|
248
|
+
{checkpoints.map((checkpoint, i) => (
|
|
249
|
+
<button
|
|
250
|
+
key={checkpoint.hash}
|
|
251
|
+
onClick={() => handleSelectCheckpoint(checkpoint)}
|
|
252
|
+
className={`w-full flex items-start gap-3 p-3 rounded-lg text-left transition-colors ${
|
|
253
|
+
selectedCheckpoint?.hash === checkpoint.hash
|
|
254
|
+
? "bg-muted"
|
|
255
|
+
: "hover:bg-muted/50"
|
|
256
|
+
}`}
|
|
257
|
+
>
|
|
258
|
+
<div className="relative">
|
|
259
|
+
<div className="w-3 h-3 rounded-full bg-muted-foreground/30 mt-1" />
|
|
260
|
+
{i < checkpoints.length - 1 && (
|
|
261
|
+
<div className="absolute top-4 left-1.5 w-px h-full bg-muted-foreground/20" />
|
|
262
|
+
)}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex-1 min-w-0">
|
|
265
|
+
<p className="font-medium text-sm truncate">{checkpoint.message}</p>
|
|
266
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-1">
|
|
267
|
+
<Clock className="h-3 w-3" />
|
|
268
|
+
{formatTime(checkpoint.timestamp)}
|
|
269
|
+
</div>
|
|
270
|
+
{checkpoint.filesChanged > 0 && (
|
|
271
|
+
<div className="flex items-center gap-2 text-xs mt-1">
|
|
272
|
+
<span className="text-green-500">+{checkpoint.insertions}</span>
|
|
273
|
+
<span className="text-red-500">-{checkpoint.deletions}</span>
|
|
274
|
+
<span className="text-muted-foreground">
|
|
275
|
+
{checkpoint.filesChanged} files
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</button>
|
|
281
|
+
))}
|
|
282
|
+
|
|
283
|
+
{checkpoints.length === 0 && (
|
|
284
|
+
<p className="text-sm text-muted-foreground px-3 py-2">
|
|
285
|
+
No checkpoints yet
|
|
286
|
+
</p>
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{/* Diff Viewer */}
|
|
292
|
+
<div className="flex-1 overflow-auto">
|
|
293
|
+
{selectedCheckpoint && diff ? (
|
|
294
|
+
<div className="p-4 space-y-4">
|
|
295
|
+
<div className="flex items-center justify-between">
|
|
296
|
+
<div>
|
|
297
|
+
<h4 className="font-medium">{selectedCheckpoint.message}</h4>
|
|
298
|
+
<p className="text-sm text-muted-foreground">
|
|
299
|
+
{new Date(selectedCheckpoint.timestamp).toLocaleString()}
|
|
300
|
+
</p>
|
|
301
|
+
</div>
|
|
302
|
+
<Button
|
|
303
|
+
variant="outline"
|
|
304
|
+
size="sm"
|
|
305
|
+
onClick={() => handleRestore(selectedCheckpoint)}
|
|
306
|
+
>
|
|
307
|
+
<RotateCcw className="h-4 w-4 mr-1" />
|
|
308
|
+
Restore
|
|
309
|
+
</Button>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{diff.length === 0 ? (
|
|
313
|
+
<p className="text-sm text-muted-foreground">
|
|
314
|
+
No differences from current state
|
|
315
|
+
</p>
|
|
316
|
+
) : (
|
|
317
|
+
<div className="space-y-2">
|
|
318
|
+
{diff.map((file) => (
|
|
319
|
+
<div key={file.path} className="border rounded-lg overflow-hidden">
|
|
320
|
+
<button
|
|
321
|
+
onClick={() => toggleFileExpand(file.path)}
|
|
322
|
+
className="w-full flex items-center gap-2 p-3 hover:bg-muted/50 text-left"
|
|
323
|
+
>
|
|
324
|
+
{expandedFiles.has(file.path) ? (
|
|
325
|
+
<ChevronDown className="h-4 w-4" />
|
|
326
|
+
) : (
|
|
327
|
+
<ChevronRight className="h-4 w-4" />
|
|
328
|
+
)}
|
|
329
|
+
<FileText className="h-4 w-4" />
|
|
330
|
+
<span className="flex-1 font-mono text-sm truncate">
|
|
331
|
+
{file.path}
|
|
332
|
+
</span>
|
|
333
|
+
<span
|
|
334
|
+
className={`text-xs px-2 py-0.5 rounded ${
|
|
335
|
+
file.status === "added"
|
|
336
|
+
? "bg-green-500/20 text-green-500"
|
|
337
|
+
: file.status === "deleted"
|
|
338
|
+
? "bg-red-500/20 text-red-500"
|
|
339
|
+
: "bg-yellow-500/20 text-yellow-500"
|
|
340
|
+
}`}
|
|
341
|
+
>
|
|
342
|
+
{file.status}
|
|
343
|
+
</span>
|
|
344
|
+
<span className="text-xs text-green-500">+{file.additions}</span>
|
|
345
|
+
<span className="text-xs text-red-500">-{file.deletions}</span>
|
|
346
|
+
</button>
|
|
347
|
+
{expandedFiles.has(file.path) && (
|
|
348
|
+
<pre className="bg-zinc-950 p-4 text-xs font-mono overflow-x-auto max-h-96">
|
|
349
|
+
{file.diff.split("\n").map((line, i) => (
|
|
350
|
+
<div
|
|
351
|
+
key={i}
|
|
352
|
+
className={
|
|
353
|
+
line.startsWith("+") && !line.startsWith("+++")
|
|
354
|
+
? "text-green-400 bg-green-500/10"
|
|
355
|
+
: line.startsWith("-") && !line.startsWith("---")
|
|
356
|
+
? "text-red-400 bg-red-500/10"
|
|
357
|
+
: line.startsWith("@@")
|
|
358
|
+
? "text-blue-400"
|
|
359
|
+
: "text-zinc-400"
|
|
360
|
+
}
|
|
361
|
+
>
|
|
362
|
+
{line}
|
|
363
|
+
</div>
|
|
364
|
+
))}
|
|
365
|
+
</pre>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
))}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
) : (
|
|
373
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
374
|
+
<div className="text-center">
|
|
375
|
+
<GitBranch className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
376
|
+
<p>Select a checkpoint to view changes</p>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
)}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
);
|
|
384
|
+
}
|