@myrialabs/clopen 0.1.6 → 0.1.8
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 +7 -1
- package/backend/lib/git/git-service.ts +1 -0
- package/bin/clopen.ts +89 -0
- package/bun.lock +38 -214
- package/frontend/lib/components/common/MonacoEditor.svelte +6 -6
- package/frontend/lib/components/common/xterm/XTerm.svelte +27 -108
- package/frontend/lib/components/common/xterm/terminal-config.ts +2 -2
- package/frontend/lib/components/common/xterm/types.ts +1 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +69 -20
- package/frontend/lib/components/files/FileTree.svelte +34 -25
- package/frontend/lib/components/files/FileViewer.svelte +45 -101
- package/frontend/lib/components/git/CommitForm.svelte +1 -1
- package/frontend/lib/components/git/GitLog.svelte +117 -91
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +3 -3
- package/frontend/lib/components/workspace/PanelHeader.svelte +639 -623
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +34 -92
- package/frontend/lib/stores/ui/workspace.svelte.ts +14 -14
- package/package.json +9 -15
|
@@ -85,33 +85,8 @@
|
|
|
85
85
|
// PDF blob URL
|
|
86
86
|
let pdfBlobUrl = $state<string | null>(null);
|
|
87
87
|
|
|
88
|
-
//
|
|
89
|
-
let containerRef = $state<HTMLDivElement | null>(null);
|
|
90
|
-
let editorHeight = $state('0px'); // Default height
|
|
91
|
-
|
|
92
|
-
// Update editor height based on container
|
|
93
|
-
function updateEditorHeight() {
|
|
94
|
-
if (containerRef) {
|
|
95
|
-
const rect = containerRef.getBoundingClientRect();
|
|
96
|
-
// Get actual available height
|
|
97
|
-
const availableHeight = rect.height;
|
|
98
|
-
if (availableHeight > 0) {
|
|
99
|
-
const adjustedHeight = availableHeight;
|
|
100
|
-
editorHeight = `${Math.max(200, adjustedHeight)}px`;
|
|
101
|
-
} else {
|
|
102
|
-
const viewportHeight = window.innerHeight;
|
|
103
|
-
const estimatedHeight = viewportHeight - 200;
|
|
104
|
-
editorHeight = `${Math.max(400, estimatedHeight)}px`;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Update height on mount and resize
|
|
88
|
+
// Keyboard shortcut for save
|
|
110
89
|
onMount(() => {
|
|
111
|
-
setTimeout(updateEditorHeight, 100);
|
|
112
|
-
|
|
113
|
-
window.addEventListener('resize', updateEditorHeight);
|
|
114
|
-
|
|
115
90
|
function handleKeyDown(e: KeyboardEvent) {
|
|
116
91
|
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
117
92
|
e.preventDefault();
|
|
@@ -123,21 +98,7 @@
|
|
|
123
98
|
|
|
124
99
|
window.addEventListener('keydown', handleKeyDown);
|
|
125
100
|
|
|
126
|
-
if (containerRef && typeof ResizeObserver !== 'undefined') {
|
|
127
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
128
|
-
updateEditorHeight();
|
|
129
|
-
});
|
|
130
|
-
resizeObserver.observe(containerRef);
|
|
131
|
-
|
|
132
|
-
return () => {
|
|
133
|
-
resizeObserver.disconnect();
|
|
134
|
-
window.removeEventListener('resize', updateEditorHeight);
|
|
135
|
-
window.removeEventListener('keydown', handleKeyDown);
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
101
|
return () => {
|
|
140
|
-
window.removeEventListener('resize', updateEditorHeight);
|
|
141
102
|
window.removeEventListener('keydown', handleKeyDown);
|
|
142
103
|
};
|
|
143
104
|
});
|
|
@@ -152,14 +113,6 @@
|
|
|
152
113
|
}
|
|
153
114
|
});
|
|
154
115
|
|
|
155
|
-
// Update height when container ref changes
|
|
156
|
-
$effect(() => {
|
|
157
|
-
if (containerRef) {
|
|
158
|
-
updateEditorHeight();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
|
|
163
116
|
// Load binary content (images, PDF) via WebSocket when file changes
|
|
164
117
|
$effect(() => {
|
|
165
118
|
if (file && (isImageFile(file.name) || isPdfFile(file.name))) {
|
|
@@ -577,38 +530,35 @@
|
|
|
577
530
|
</div>
|
|
578
531
|
{:else}
|
|
579
532
|
<!-- SVG code view (editable) -->
|
|
580
|
-
<div class="h-full flex flex-col
|
|
581
|
-
<div class="flex-1
|
|
582
|
-
<div class="
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
/>
|
|
599
|
-
{/key}
|
|
600
|
-
</div>
|
|
601
|
-
|
|
602
|
-
{#if hasChanges}
|
|
603
|
-
<div class="flex-shrink-0 p-4 bg-amber-50 dark:bg-amber-900/30 border-t border-amber-200 dark:border-amber-800">
|
|
604
|
-
<div class="text-xs text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
|
605
|
-
<Icon name="lucide:circle-alert" class="w-3 h-3" />
|
|
606
|
-
Unsaved changes
|
|
607
|
-
</div>
|
|
608
|
-
</div>
|
|
609
|
-
{/if}
|
|
533
|
+
<div class="h-full flex flex-col bg-slate-50 dark:bg-slate-950">
|
|
534
|
+
<div class="flex-1 relative overflow-hidden">
|
|
535
|
+
<div class="absolute inset-0">
|
|
536
|
+
{#key themeKey}
|
|
537
|
+
<MonacoEditor
|
|
538
|
+
bind:this={monacoEditorRef}
|
|
539
|
+
bind:value={editableContent}
|
|
540
|
+
language="xml"
|
|
541
|
+
readonly={false}
|
|
542
|
+
onChange={handleContentChange}
|
|
543
|
+
options={{
|
|
544
|
+
minimap: { enabled: false },
|
|
545
|
+
wordWrap: 'off',
|
|
546
|
+
renderWhitespace: 'none',
|
|
547
|
+
mouseWheelZoom: false
|
|
548
|
+
}}
|
|
549
|
+
/>
|
|
550
|
+
{/key}
|
|
610
551
|
</div>
|
|
611
552
|
</div>
|
|
553
|
+
|
|
554
|
+
{#if hasChanges}
|
|
555
|
+
<div class="flex-shrink-0 p-4 bg-amber-50 dark:bg-amber-900/30 border-t border-amber-200 dark:border-amber-800">
|
|
556
|
+
<div class="text-xs text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
|
557
|
+
<Icon name="lucide:circle-alert" class="w-3 h-3" />
|
|
558
|
+
Unsaved changes
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
{/if}
|
|
612
562
|
</div>
|
|
613
563
|
{/if}
|
|
614
564
|
{:else if isPdfFile(file.name)}
|
|
@@ -644,29 +594,23 @@
|
|
|
644
594
|
</div>
|
|
645
595
|
{:else}
|
|
646
596
|
<!-- Code content (always in edit mode) -->
|
|
647
|
-
<div class="h-full
|
|
648
|
-
<div class="
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}}
|
|
665
|
-
/>
|
|
666
|
-
{/key}
|
|
667
|
-
</div>
|
|
668
|
-
|
|
669
|
-
</div>
|
|
597
|
+
<div class="h-full relative bg-slate-50 dark:bg-slate-950">
|
|
598
|
+
<div class="absolute inset-0">
|
|
599
|
+
{#key themeKey}
|
|
600
|
+
<MonacoEditor
|
|
601
|
+
bind:this={monacoEditorRef}
|
|
602
|
+
bind:value={editableContent}
|
|
603
|
+
language={getDetectedLanguage()}
|
|
604
|
+
readonly={false}
|
|
605
|
+
onChange={handleContentChange}
|
|
606
|
+
options={{
|
|
607
|
+
minimap: { enabled: false },
|
|
608
|
+
wordWrap: wordWrap ? 'on' : 'off',
|
|
609
|
+
renderWhitespace: 'none',
|
|
610
|
+
mouseWheelZoom: false
|
|
611
|
+
}}
|
|
612
|
+
/>
|
|
613
|
+
{/key}
|
|
670
614
|
</div>
|
|
671
615
|
</div>
|
|
672
616
|
{/if}
|
|
@@ -25,14 +25,13 @@
|
|
|
25
25
|
lanes: Array<{ col: number; color: string }>;
|
|
26
26
|
mergeFrom: Array<{ col: number; color: string }>;
|
|
27
27
|
branchTo: Array<{ col: number; color: string }>;
|
|
28
|
-
|
|
28
|
+
branchToCols: Set<number>;
|
|
29
29
|
prevLaneCols: Set<number>;
|
|
30
|
-
/** Which lane columns will exist in the next row (for drawing bottom half of lines) */
|
|
31
30
|
nextLaneCols: Set<number>;
|
|
32
|
-
/** Whether this node's lane existed in the previous row */
|
|
33
31
|
nodeHasTop: boolean;
|
|
34
|
-
/** Whether this node's lane continues to the next row */
|
|
35
32
|
nodeHasBottom: boolean;
|
|
33
|
+
/** Max column index used in this row (for per-row graph width) */
|
|
34
|
+
maxCol: number;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
const LANE_COLORS = [
|
|
@@ -41,10 +40,6 @@
|
|
|
41
40
|
];
|
|
42
41
|
|
|
43
42
|
const graphRows = $derived(computeGraph(commits));
|
|
44
|
-
const maxCols = $derived(graphRows.reduce((max, row) => {
|
|
45
|
-
const cols = [row.col, ...row.lanes.map(l => l.col), ...row.mergeFrom.map(m => m.col), ...row.branchTo.map(b => b.col)];
|
|
46
|
-
return Math.max(max, ...cols);
|
|
47
|
-
}, 0) + 1);
|
|
48
43
|
|
|
49
44
|
function computeGraph(commits: GitCommit[]): GraphRow[] {
|
|
50
45
|
if (commits.length === 0) return [];
|
|
@@ -53,6 +48,7 @@
|
|
|
53
48
|
const laneColorMap = new Map<number, string>();
|
|
54
49
|
let colorIdx = 0;
|
|
55
50
|
const rows: GraphRow[] = [];
|
|
51
|
+
const processedSet = new Set<string>();
|
|
56
52
|
|
|
57
53
|
function getColor(col: number): string {
|
|
58
54
|
if (!laneColorMap.has(col)) {
|
|
@@ -70,7 +66,6 @@
|
|
|
70
66
|
return s;
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
// Track previous row's active lane columns
|
|
74
69
|
let prevActiveCols = new Set<number>();
|
|
75
70
|
|
|
76
71
|
for (const commit of commits) {
|
|
@@ -81,11 +76,13 @@
|
|
|
81
76
|
|
|
82
77
|
let col: number;
|
|
83
78
|
const mergeFrom: Array<{ col: number; color: string }> = [];
|
|
79
|
+
const mergeFromCols = new Set<number>();
|
|
84
80
|
|
|
85
81
|
if (myLanes.length > 0) {
|
|
86
82
|
col = myLanes[0];
|
|
87
83
|
for (let i = 1; i < myLanes.length; i++) {
|
|
88
84
|
mergeFrom.push({ col: myLanes[i], color: getColor(myLanes[i]) });
|
|
85
|
+
mergeFromCols.add(myLanes[i]);
|
|
89
86
|
lanes[myLanes[i]] = null;
|
|
90
87
|
}
|
|
91
88
|
} else {
|
|
@@ -97,17 +94,26 @@
|
|
|
97
94
|
getColor(col);
|
|
98
95
|
const nodeHasTop = prevActiveCols.has(col);
|
|
99
96
|
|
|
100
|
-
// Snapshot current active lanes (before parent assignment)
|
|
101
97
|
const currentPrevCols = new Set(prevActiveCols);
|
|
102
98
|
|
|
103
99
|
const branchTo: Array<{ col: number; color: string }> = [];
|
|
100
|
+
const branchToCols = new Set<number>();
|
|
104
101
|
if (commit.parents.length > 0) {
|
|
105
|
-
|
|
102
|
+
// First parent: skip if already processed (non-topo edge case)
|
|
103
|
+
if (processedSet.has(commit.parents[0])) {
|
|
104
|
+
lanes[col] = null;
|
|
105
|
+
} else {
|
|
106
|
+
lanes[col] = commit.parents[0];
|
|
107
|
+
}
|
|
106
108
|
|
|
107
109
|
for (let p = 1; p < commit.parents.length; p++) {
|
|
110
|
+
// Skip parents that were already processed
|
|
111
|
+
if (processedSet.has(commit.parents[p])) continue;
|
|
112
|
+
|
|
108
113
|
const existingIdx = lanes.indexOf(commit.parents[p]);
|
|
109
114
|
if (existingIdx >= 0 && existingIdx !== col) {
|
|
110
115
|
branchTo.push({ col: existingIdx, color: getColor(existingIdx) });
|
|
116
|
+
branchToCols.add(existingIdx);
|
|
111
117
|
} else {
|
|
112
118
|
let newCol = -1;
|
|
113
119
|
for (let i = 0; i < lanes.length; i++) {
|
|
@@ -119,6 +125,7 @@
|
|
|
119
125
|
}
|
|
120
126
|
lanes[newCol] = commit.parents[p];
|
|
121
127
|
branchTo.push({ col: newCol, color: getColor(newCol) });
|
|
128
|
+
branchToCols.add(newCol);
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
} else {
|
|
@@ -134,18 +141,27 @@
|
|
|
134
141
|
|
|
135
142
|
const nextActiveCols = getActiveCols();
|
|
136
143
|
|
|
144
|
+
// Calculate max column used in this row
|
|
145
|
+
let rowMaxCol = col;
|
|
146
|
+
for (const lane of activeLanes) rowMaxCol = Math.max(rowMaxCol, lane.col);
|
|
147
|
+
for (const m of mergeFrom) rowMaxCol = Math.max(rowMaxCol, m.col);
|
|
148
|
+
for (const b of branchTo) rowMaxCol = Math.max(rowMaxCol, b.col);
|
|
149
|
+
|
|
137
150
|
rows.push({
|
|
138
151
|
col,
|
|
139
152
|
nodeColor: getColor(col),
|
|
140
153
|
lanes: activeLanes,
|
|
141
154
|
mergeFrom,
|
|
142
155
|
branchTo,
|
|
156
|
+
branchToCols,
|
|
143
157
|
prevLaneCols: currentPrevCols,
|
|
144
158
|
nextLaneCols: nextActiveCols,
|
|
145
159
|
nodeHasTop,
|
|
146
|
-
nodeHasBottom: nextActiveCols.has(col)
|
|
160
|
+
nodeHasBottom: nextActiveCols.has(col),
|
|
161
|
+
maxCol: rowMaxCol
|
|
147
162
|
});
|
|
148
163
|
|
|
164
|
+
processedSet.add(commit.hash);
|
|
149
165
|
prevActiveCols = nextActiveCols;
|
|
150
166
|
|
|
151
167
|
while (lanes.length > 0 && lanes[lanes.length - 1] === null) {
|
|
@@ -160,9 +176,18 @@
|
|
|
160
176
|
// Helpers
|
|
161
177
|
// ========================
|
|
162
178
|
|
|
163
|
-
const LANE_WIDTH =
|
|
164
|
-
const ROW_HEIGHT =
|
|
165
|
-
const NODE_R =
|
|
179
|
+
const LANE_WIDTH = 10;
|
|
180
|
+
const ROW_HEIGHT = 48;
|
|
181
|
+
const NODE_R = 3;
|
|
182
|
+
const GRAPH_PAD = 3;
|
|
183
|
+
const LINE_W = '1.5';
|
|
184
|
+
const MAX_VISIBLE_REFS = 3;
|
|
185
|
+
const MAX_REF_LENGTH = 22;
|
|
186
|
+
|
|
187
|
+
function truncateRef(ref: string): string {
|
|
188
|
+
if (ref.length <= MAX_REF_LENGTH) return ref;
|
|
189
|
+
return ref.substring(0, MAX_REF_LENGTH - 1) + '\u2026';
|
|
190
|
+
}
|
|
166
191
|
|
|
167
192
|
function formatDate(dateStr: string): string {
|
|
168
193
|
const date = new Date(dateStr);
|
|
@@ -194,7 +219,7 @@
|
|
|
194
219
|
}
|
|
195
220
|
</script>
|
|
196
221
|
|
|
197
|
-
<div class="h-
|
|
222
|
+
<div class="flex-1 min-h-0 flex flex-col">
|
|
198
223
|
{#if isLoading && commits.length === 0}
|
|
199
224
|
<div class="flex-1 flex items-center justify-center">
|
|
200
225
|
<div class="w-5 h-5 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
@@ -205,15 +230,16 @@
|
|
|
205
230
|
<span>No commits yet</span>
|
|
206
231
|
</div>
|
|
207
232
|
{:else}
|
|
208
|
-
<div class="flex-1 overflow-y-auto">
|
|
233
|
+
<div class="flex-1 overflow-y-auto overflow-x-hidden pt-2">
|
|
209
234
|
{#each commits as commit, idx (commit.hash)}
|
|
210
235
|
{@const graph = graphRows[idx]}
|
|
211
|
-
{@const graphWidth =
|
|
236
|
+
{@const graphWidth = (graph ? graph.maxCol + 1 : 1) * LANE_WIDTH + GRAPH_PAD * 2}
|
|
212
237
|
<div
|
|
213
238
|
class="group flex items-stretch w-full text-left cursor-pointer transition-colors
|
|
214
239
|
{selectedHash === commit.hash
|
|
215
240
|
? 'bg-violet-50 dark:bg-violet-900/10'
|
|
216
241
|
: 'hover:bg-slate-50 dark:hover:bg-slate-800/40'}"
|
|
242
|
+
style="height: {ROW_HEIGHT}px;"
|
|
217
243
|
role="button"
|
|
218
244
|
tabindex="0"
|
|
219
245
|
onclick={() => handleViewCommit(commit.hash)}
|
|
@@ -221,124 +247,124 @@
|
|
|
221
247
|
>
|
|
222
248
|
<!-- Git Graph Column -->
|
|
223
249
|
{#if graph}
|
|
224
|
-
<div class="shrink-0 relative" style="width: {graphWidth}px;
|
|
225
|
-
<svg class="absolute inset-0 w-full h-full"
|
|
226
|
-
<!-- Vertical lines for
|
|
250
|
+
<div class="shrink-0 relative" style="width: {graphWidth}px;">
|
|
251
|
+
<svg class="absolute inset-0 w-full h-full">
|
|
252
|
+
<!-- Vertical lines for pass-through lanes (skip node col and branchTo cols) -->
|
|
227
253
|
{#each graph.lanes as lane}
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
254
|
+
{#if lane.col !== graph.col && !graph.branchToCols.has(lane.col)}
|
|
255
|
+
{@const lx = lane.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
256
|
+
{@const hasTop = graph.prevLaneCols.has(lane.col)}
|
|
257
|
+
<line
|
|
258
|
+
x1={lx} y1={hasTop ? 0 : ROW_HEIGHT / 2}
|
|
259
|
+
x2={lx} y2={ROW_HEIGHT}
|
|
260
|
+
stroke={lane.color}
|
|
261
|
+
stroke-width={LINE_W}
|
|
262
|
+
/>
|
|
263
|
+
{/if}
|
|
238
264
|
{/each}
|
|
239
265
|
|
|
240
|
-
<!--
|
|
266
|
+
<!-- Top-half lines for branchTo cols that existed in previous row (skip if also mergeFrom) -->
|
|
267
|
+
{#each graph.branchTo as branch}
|
|
268
|
+
{#if graph.prevLaneCols.has(branch.col) && !graph.mergeFrom.some(m => m.col === branch.col)}
|
|
269
|
+
{@const bx = branch.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
270
|
+
<line
|
|
271
|
+
x1={bx} y1={0}
|
|
272
|
+
x2={bx} y2={ROW_HEIGHT / 2}
|
|
273
|
+
stroke={branch.color}
|
|
274
|
+
stroke-width={LINE_W}
|
|
275
|
+
/>
|
|
276
|
+
{/if}
|
|
277
|
+
{/each}
|
|
278
|
+
|
|
279
|
+
<!-- Merge curves (from other lanes into this node) -->
|
|
241
280
|
{#each graph.mergeFrom as merge}
|
|
242
|
-
{@const mx = merge.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
243
|
-
{@const nx = graph.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
281
|
+
{@const mx = merge.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
282
|
+
{@const nx = graph.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
244
283
|
<path
|
|
245
284
|
d="M {mx} 0 C {mx} {ROW_HEIGHT * 0.4}, {nx} {ROW_HEIGHT * 0.3}, {nx} {ROW_HEIGHT / 2}"
|
|
246
285
|
fill="none"
|
|
247
286
|
stroke={merge.color}
|
|
248
|
-
stroke-width=
|
|
249
|
-
opacity="0.5"
|
|
287
|
+
stroke-width={LINE_W}
|
|
250
288
|
/>
|
|
251
289
|
{/each}
|
|
252
290
|
|
|
253
|
-
<!-- Branch
|
|
291
|
+
<!-- Branch curves (from this node to new lanes) -->
|
|
254
292
|
{#each graph.branchTo as branch}
|
|
255
|
-
{@const bx = branch.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
256
|
-
{@const nx = graph.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
293
|
+
{@const bx = branch.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
294
|
+
{@const nx = graph.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
257
295
|
<path
|
|
258
296
|
d="M {nx} {ROW_HEIGHT / 2} C {nx} {ROW_HEIGHT * 0.7}, {bx} {ROW_HEIGHT * 0.6}, {bx} {ROW_HEIGHT}"
|
|
259
297
|
fill="none"
|
|
260
298
|
stroke={branch.color}
|
|
261
|
-
stroke-width=
|
|
262
|
-
opacity="0.5"
|
|
299
|
+
stroke-width={LINE_W}
|
|
263
300
|
/>
|
|
264
301
|
{/each}
|
|
265
302
|
|
|
266
303
|
<!-- Main vertical line through this node's lane -->
|
|
267
304
|
<line
|
|
268
|
-
x1={graph.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
269
|
-
x2={graph.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
305
|
+
x1={graph.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD} y1={graph.nodeHasTop ? 0 : ROW_HEIGHT / 2}
|
|
306
|
+
x2={graph.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD} y2={graph.nodeHasBottom ? ROW_HEIGHT : ROW_HEIGHT / 2}
|
|
270
307
|
stroke={graph.nodeColor}
|
|
271
|
-
stroke-width=
|
|
272
|
-
opacity="0.4"
|
|
308
|
+
stroke-width={LINE_W}
|
|
273
309
|
/>
|
|
274
310
|
|
|
275
311
|
<!-- Node circle -->
|
|
276
312
|
<circle
|
|
277
|
-
cx={graph.col * LANE_WIDTH + LANE_WIDTH / 2 +
|
|
313
|
+
cx={graph.col * LANE_WIDTH + LANE_WIDTH / 2 + GRAPH_PAD}
|
|
278
314
|
cy={ROW_HEIGHT / 2}
|
|
279
|
-
r={
|
|
315
|
+
r={NODE_R}
|
|
280
316
|
fill={graph.nodeColor}
|
|
281
317
|
stroke="white"
|
|
282
|
-
stroke-width="
|
|
318
|
+
stroke-width="1.5"
|
|
283
319
|
/>
|
|
284
|
-
{#if commit.parents.length > 1}
|
|
285
|
-
<circle
|
|
286
|
-
cx={graph.col * LANE_WIDTH + LANE_WIDTH / 2 + 4}
|
|
287
|
-
cy={ROW_HEIGHT / 2}
|
|
288
|
-
r={NODE_R + 3}
|
|
289
|
-
fill="none"
|
|
290
|
-
stroke={graph.nodeColor}
|
|
291
|
-
stroke-width="1.5"
|
|
292
|
-
opacity="0.5"
|
|
293
|
-
/>
|
|
294
|
-
{/if}
|
|
295
320
|
</svg>
|
|
296
321
|
</div>
|
|
297
322
|
{/if}
|
|
298
323
|
|
|
299
|
-
<!-- Commit info -->
|
|
300
|
-
<div class="flex-1 min-w-0
|
|
301
|
-
|
|
302
|
-
|
|
324
|
+
<!-- Commit info (3-line layout) -->
|
|
325
|
+
<div class="flex-1 min-w-0 px-1.5 py-0.5 flex flex-col justify-center overflow-hidden">
|
|
326
|
+
<!-- Line 1: Message + Date -->
|
|
327
|
+
<div class="flex items-center gap-2">
|
|
328
|
+
<p class="flex-1 min-w-0 text-sm text-slate-900 dark:text-slate-100 leading-tight truncate">
|
|
303
329
|
{commit.message}
|
|
304
330
|
</p>
|
|
305
|
-
<
|
|
306
|
-
<span class="text-xs font-mono text-violet-600 dark:text-violet-400">
|
|
307
|
-
{commit.hashShort}
|
|
308
|
-
</span>
|
|
309
|
-
<span class="text-xs text-slate-500 truncate">{commit.author}</span>
|
|
310
|
-
{#if commit.refs && commit.refs.length > 0}
|
|
311
|
-
{#each commit.refs as ref}
|
|
312
|
-
<span class="text-3xs px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400 truncate shrink-0">
|
|
313
|
-
{ref}
|
|
314
|
-
</span>
|
|
315
|
-
{/each}
|
|
316
|
-
{/if}
|
|
317
|
-
</div>
|
|
331
|
+
<span class="text-3xs text-slate-400 shrink-0">{formatDate(commit.date)}</span>
|
|
318
332
|
</div>
|
|
319
333
|
|
|
320
|
-
<!--
|
|
321
|
-
<div class="items-center gap-
|
|
334
|
+
<!-- Line 2: Hash + Author -->
|
|
335
|
+
<div class="flex items-center gap-1.5 mt-px">
|
|
322
336
|
<button
|
|
323
337
|
type="button"
|
|
324
|
-
class="
|
|
338
|
+
class="text-xs font-mono text-violet-600 dark:text-violet-400 hover:text-violet-800 dark:hover:text-violet-300 bg-transparent border-none cursor-pointer p-0 shrink-0 transition-colors"
|
|
325
339
|
onclick={(e) => copyCommitHash(commit.hash, e)}
|
|
326
|
-
title="Copy
|
|
327
|
-
>
|
|
328
|
-
<Icon name="lucide:copy" class="w-3.5 h-3.5" />
|
|
329
|
-
</button>
|
|
330
|
-
<button
|
|
331
|
-
type="button"
|
|
332
|
-
class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-violet-500/10 hover:text-violet-600 transition-colors bg-transparent border-none cursor-pointer"
|
|
333
|
-
onclick={(e) => { e.stopPropagation(); copyCommitHash(commit.hashShort, e); }}
|
|
334
|
-
title="Copy short hash"
|
|
340
|
+
title="Copy commit hash"
|
|
335
341
|
>
|
|
336
|
-
|
|
342
|
+
{commit.hashShort}
|
|
337
343
|
</button>
|
|
344
|
+
<span class="text-xs text-slate-500 truncate">{commit.author}</span>
|
|
338
345
|
</div>
|
|
339
346
|
|
|
340
|
-
<!--
|
|
341
|
-
|
|
347
|
+
<!-- Line 3: Refs -->
|
|
348
|
+
{#if commit.refs && commit.refs.length > 0}
|
|
349
|
+
<div class="flex items-center gap-1 mt-px overflow-hidden">
|
|
350
|
+
{#each commit.refs.slice(0, MAX_VISIBLE_REFS) as ref}
|
|
351
|
+
<span
|
|
352
|
+
class="text-3xs px-1 py-px rounded bg-blue-500/10 text-blue-600 dark:text-blue-400 shrink-0 truncate max-w-28"
|
|
353
|
+
title={ref}
|
|
354
|
+
>
|
|
355
|
+
{truncateRef(ref)}
|
|
356
|
+
</span>
|
|
357
|
+
{/each}
|
|
358
|
+
{#if commit.refs.length > MAX_VISIBLE_REFS}
|
|
359
|
+
<span
|
|
360
|
+
class="text-3xs px-1 py-px rounded bg-slate-500/10 text-slate-500 shrink-0 cursor-default"
|
|
361
|
+
title={commit.refs.slice(MAX_VISIBLE_REFS).join(', ')}
|
|
362
|
+
>
|
|
363
|
+
+{commit.refs.length - MAX_VISIBLE_REFS}
|
|
364
|
+
</span>
|
|
365
|
+
{/if}
|
|
366
|
+
</div>
|
|
367
|
+
{/if}
|
|
342
368
|
</div>
|
|
343
369
|
</div>
|
|
344
370
|
{/each}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { isDarkMode } from '$frontend/lib/utils/theme';
|
|
8
8
|
import { ENGINES } from '$shared/constants/engines';
|
|
9
9
|
import { claudeAccountsStore, type ClaudeAccountItem as ClaudeCodeAccountItem } from '$frontend/lib/stores/features/claude-accounts.svelte';
|
|
10
|
-
import type { Terminal } from 'xterm';
|
|
10
|
+
import type { Terminal } from '@xterm/xterm';
|
|
11
11
|
import type { FitAddon } from '@xterm/addon-fit';
|
|
12
12
|
|
|
13
13
|
const claudeCodeEngine = ENGINES.find(e => e.type === 'claude-code')!;
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
if (!browser || !debugTermContainer || debugTerminal) return;
|
|
100
100
|
|
|
101
101
|
const [{ Terminal }, { FitAddon }] = await Promise.all([
|
|
102
|
-
import('xterm'),
|
|
102
|
+
import('@xterm/xterm'),
|
|
103
103
|
import('@xterm/addon-fit')
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
|
-
await import('xterm/css/xterm.css');
|
|
106
|
+
await import('@xterm/xterm/css/xterm.css');
|
|
107
107
|
|
|
108
108
|
debugTerminal = new Terminal({
|
|
109
109
|
theme: {
|