@myrialabs/clopen 0.1.7 → 0.1.9
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/backend/lib/database/migrations/023_create_user_unread_sessions_table.ts +32 -0
- package/backend/lib/database/migrations/index.ts +7 -0
- package/backend/lib/database/queries/session-queries.ts +37 -0
- package/backend/lib/git/git-service.ts +1 -0
- package/backend/ws/sessions/crud.ts +34 -2
- package/backend/ws/user/crud.ts +8 -4
- package/bun.lock +34 -12
- 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 +4 -6
- 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 +141 -101
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +81 -72
- package/frontend/lib/components/settings/SettingsModal.svelte +1 -8
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +3 -3
- package/frontend/lib/components/terminal/Terminal.svelte +1 -1
- package/frontend/lib/components/terminal/TerminalTabs.svelte +28 -26
- package/frontend/lib/components/workspace/PanelHeader.svelte +639 -623
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +3 -2
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +34 -92
- package/frontend/lib/stores/core/app.svelte.ts +46 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +24 -3
- package/frontend/lib/stores/ui/workspace.svelte.ts +14 -14
- package/package.json +8 -6
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
workspaceState,
|
|
9
9
|
initializeWorkspace,
|
|
10
10
|
} from '$frontend/lib/stores/ui/workspace.svelte';
|
|
11
|
-
import { appState, setAppLoading, setAppInitialized, restoreLastView } from '$frontend/lib/stores/core/app.svelte';
|
|
11
|
+
import { appState, setAppLoading, setAppInitialized, restoreLastView, restoreUnreadSessions } from '$frontend/lib/stores/core/app.svelte';
|
|
12
12
|
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
13
13
|
import { sessionState } from '$frontend/lib/stores/core/sessions.svelte';
|
|
14
14
|
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
|
|
90
90
|
// Step 3: Restore user state from server
|
|
91
91
|
setProgress(30, 'Restoring state...');
|
|
92
|
-
let serverState: { currentProjectId: string | null; lastView: string | null; settings: any } | null = null;
|
|
92
|
+
let serverState: { currentProjectId: string | null; lastView: string | null; settings: any; unreadSessions: any } | null = null;
|
|
93
93
|
try {
|
|
94
94
|
serverState = await ws.http('user:restore-state', {});
|
|
95
95
|
debug.log('workspace', 'Server state restored:', serverState);
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
applyServerSettings(serverState.settings);
|
|
104
104
|
}
|
|
105
105
|
restoreLastView(serverState?.lastView);
|
|
106
|
+
restoreUnreadSessions(serverState?.unreadSessions);
|
|
106
107
|
initPresence();
|
|
107
108
|
|
|
108
109
|
// Step 5: Load projects (with server-restored currentProjectId)
|
|
@@ -67,9 +67,6 @@
|
|
|
67
67
|
let newTagName = $state('');
|
|
68
68
|
let newTagMessage = $state('');
|
|
69
69
|
|
|
70
|
-
// More menu state (for Stash/Tags)
|
|
71
|
-
let showMoreMenu = $state(false);
|
|
72
|
-
|
|
73
70
|
// Tab system (like Files panel)
|
|
74
71
|
interface DiffTab {
|
|
75
72
|
id: string;
|
|
@@ -1053,12 +1050,21 @@
|
|
|
1053
1050
|
gitStatus.staged.length + allChanges.length + gitStatus.conflicted.length
|
|
1054
1051
|
);
|
|
1055
1052
|
|
|
1053
|
+
// View tabs config for tab bar
|
|
1054
|
+
const viewTabs = $derived([
|
|
1055
|
+
{ id: 'changes' as const, label: 'Changes', icon: 'lucide:file-pen' as IconName, badge: totalChanges > 0 ? totalChanges : null },
|
|
1056
|
+
{ id: 'log' as const, label: 'History', icon: 'lucide:history' as IconName, badge: null },
|
|
1057
|
+
{ id: 'stash' as const, label: 'Stash', icon: 'lucide:archive' as IconName, badge: stashEntries.length > 0 ? stashEntries.length : null },
|
|
1058
|
+
{ id: 'tags' as const, label: 'Tags', icon: 'lucide:tag' as IconName, badge: tags.length > 0 ? tags.length : null }
|
|
1059
|
+
]);
|
|
1060
|
+
|
|
1056
1061
|
// Exported panel actions for PanelHeader
|
|
1057
1062
|
export const panelActions = {
|
|
1058
1063
|
push: handlePush,
|
|
1059
1064
|
pull: handlePull,
|
|
1060
1065
|
fetch: handleFetch,
|
|
1061
1066
|
init: handleInit,
|
|
1067
|
+
openBranchManager: () => { showBranchManager = true; },
|
|
1062
1068
|
getBranchInfo: () => branchInfo,
|
|
1063
1069
|
getIsRepo: () => isRepo,
|
|
1064
1070
|
getIsFetching: () => isFetching,
|
|
@@ -1102,97 +1108,32 @@
|
|
|
1102
1108
|
{/if}
|
|
1103
1109
|
{/snippet}
|
|
1104
1110
|
|
|
1105
|
-
<!--
|
|
1106
|
-
{#snippet
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
type="button"
|
|
1111
|
-
class="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-slate-100 dark:bg-slate-800/60 text-slate-700 dark:text-slate-300 hover:bg-violet-500/10 hover:text-violet-600 transition-colors cursor-pointer border-none min-w-0"
|
|
1112
|
-
onclick={() => showBranchManager = true}
|
|
1113
|
-
title="Switch Branch"
|
|
1114
|
-
>
|
|
1115
|
-
<Icon name="lucide:git-branch" class="w-3.5 h-3.5 shrink-0" />
|
|
1116
|
-
<span class="truncate max-w-24">{branchInfo?.current || '...'}</span>
|
|
1117
|
-
</button>
|
|
1118
|
-
|
|
1119
|
-
<div class="flex-1"></div>
|
|
1120
|
-
|
|
1121
|
-
<!-- Primary tabs: Changes & History -->
|
|
1122
|
-
<button
|
|
1123
|
-
type="button"
|
|
1124
|
-
class="px-2 py-1 text-xs font-medium rounded-md transition-colors cursor-pointer border-none
|
|
1125
|
-
{activeView === 'changes'
|
|
1126
|
-
? 'bg-violet-500/10 text-violet-600'
|
|
1127
|
-
: 'bg-transparent text-slate-500 hover:text-slate-700 dark:hover:text-slate-300'}"
|
|
1128
|
-
onclick={() => { switchToView('changes'); showMoreMenu = false; }}
|
|
1129
|
-
>
|
|
1130
|
-
Changes{totalChanges > 0 ? ` (${totalChanges})` : ''}
|
|
1131
|
-
</button>
|
|
1132
|
-
<button
|
|
1133
|
-
type="button"
|
|
1134
|
-
class="px-2 py-1 text-xs font-medium rounded-md transition-colors cursor-pointer border-none
|
|
1135
|
-
{activeView === 'log'
|
|
1136
|
-
? 'bg-violet-500/10 text-violet-600'
|
|
1137
|
-
: 'bg-transparent text-slate-500 hover:text-slate-700 dark:hover:text-slate-300'}"
|
|
1138
|
-
onclick={() => { switchToView('log'); showMoreMenu = false; }}
|
|
1139
|
-
>
|
|
1140
|
-
History
|
|
1141
|
-
</button>
|
|
1142
|
-
|
|
1143
|
-
<!-- More menu (Stash, Tags) -->
|
|
1144
|
-
<div class="relative">
|
|
1111
|
+
<!-- View tabs snippet (always visible, even in single-column diff mode) -->
|
|
1112
|
+
{#snippet viewTabBar()}
|
|
1113
|
+
<div class="relative flex border-b border-slate-200 dark:border-slate-700">
|
|
1114
|
+
{#each viewTabs as tab (tab.id)}
|
|
1115
|
+
{@const isActive = activeView === tab.id}
|
|
1145
1116
|
<button
|
|
1146
1117
|
type="button"
|
|
1147
|
-
class="flex items-center justify-center
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
onclick={() => showMoreMenu = !showMoreMenu}
|
|
1152
|
-
title="More views"
|
|
1118
|
+
class="relative flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-medium transition-colors {isActive
|
|
1119
|
+
? 'text-violet-600 dark:text-violet-400'
|
|
1120
|
+
: 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300'}"
|
|
1121
|
+
onclick={() => switchToView(tab.id)}
|
|
1153
1122
|
>
|
|
1154
|
-
|
|
1123
|
+
{tab.label}
|
|
1124
|
+
{#if tab.badge}
|
|
1125
|
+
<span class="min-w-4 h-4 px-1 rounded-full bg-violet-500/15 dark:bg-violet-500/25 text-3xs font-semibold flex items-center justify-center">{tab.badge}</span>
|
|
1126
|
+
{/if}
|
|
1127
|
+
{#if isActive}
|
|
1128
|
+
<span class="absolute bottom-0 inset-x-0 h-px bg-violet-600 dark:bg-violet-400"></span>
|
|
1129
|
+
{/if}
|
|
1155
1130
|
</button>
|
|
1156
|
-
|
|
1157
|
-
{#if showMoreMenu}
|
|
1158
|
-
<div
|
|
1159
|
-
class="fixed inset-0 z-40"
|
|
1160
|
-
onclick={() => showMoreMenu = false}
|
|
1161
|
-
></div>
|
|
1162
|
-
<div class="absolute right-0 top-full mt-1 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg py-1 min-w-32">
|
|
1163
|
-
<button
|
|
1164
|
-
type="button"
|
|
1165
|
-
class="flex items-center gap-2 w-full px-3 py-1.5 text-xs font-medium text-left transition-colors cursor-pointer border-none
|
|
1166
|
-
{activeView === 'stash'
|
|
1167
|
-
? 'bg-violet-500/10 text-violet-600'
|
|
1168
|
-
: 'bg-transparent text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700'}"
|
|
1169
|
-
onclick={() => { switchToView('stash'); showMoreMenu = false; }}
|
|
1170
|
-
>
|
|
1171
|
-
<Icon name="lucide:archive" class="w-3.5 h-3.5" />
|
|
1172
|
-
Stash
|
|
1173
|
-
{#if stashEntries.length > 0}
|
|
1174
|
-
<span class="ml-auto text-3xs font-semibold text-slate-400">{stashEntries.length}</span>
|
|
1175
|
-
{/if}
|
|
1176
|
-
</button>
|
|
1177
|
-
<button
|
|
1178
|
-
type="button"
|
|
1179
|
-
class="flex items-center gap-2 w-full px-3 py-1.5 text-xs font-medium text-left transition-colors cursor-pointer border-none
|
|
1180
|
-
{activeView === 'tags'
|
|
1181
|
-
? 'bg-violet-500/10 text-violet-600'
|
|
1182
|
-
: 'bg-transparent text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700'}"
|
|
1183
|
-
onclick={() => { switchToView('tags'); showMoreMenu = false; }}
|
|
1184
|
-
>
|
|
1185
|
-
<Icon name="lucide:tag" class="w-3.5 h-3.5" />
|
|
1186
|
-
Tags
|
|
1187
|
-
{#if tags.length > 0}
|
|
1188
|
-
<span class="ml-auto text-3xs font-semibold text-slate-400">{tags.length}</span>
|
|
1189
|
-
{/if}
|
|
1190
|
-
</button>
|
|
1191
|
-
</div>
|
|
1192
|
-
{/if}
|
|
1193
|
-
</div>
|
|
1131
|
+
{/each}
|
|
1194
1132
|
</div>
|
|
1133
|
+
{/snippet}
|
|
1195
1134
|
|
|
1135
|
+
<!-- Changes list snippet -->
|
|
1136
|
+
{#snippet changesList()}
|
|
1196
1137
|
{#if activeView === 'changes'}
|
|
1197
1138
|
<!-- Commit form -->
|
|
1198
1139
|
<CommitForm
|
|
@@ -1253,7 +1194,7 @@
|
|
|
1253
1194
|
/>
|
|
1254
1195
|
{:else if activeView === 'stash'}
|
|
1255
1196
|
<!-- Stash View -->
|
|
1256
|
-
<div class="flex-1 overflow-y-auto">
|
|
1197
|
+
<div class="flex-1 overflow-y-auto pt-2">
|
|
1257
1198
|
<!-- Stash save button/form -->
|
|
1258
1199
|
<div class="px-2 pb-2">
|
|
1259
1200
|
{#if showStashSaveForm}
|
|
@@ -1337,7 +1278,7 @@
|
|
|
1337
1278
|
</div>
|
|
1338
1279
|
{:else if activeView === 'tags'}
|
|
1339
1280
|
<!-- Tags View -->
|
|
1340
|
-
<div class="flex-1 overflow-y-auto">
|
|
1281
|
+
<div class="flex-1 overflow-y-auto pt-2">
|
|
1341
1282
|
<!-- Create tag button/form -->
|
|
1342
1283
|
<div class="px-2 pb-2">
|
|
1343
1284
|
{#if showCreateTagForm}
|
|
@@ -1502,16 +1443,17 @@
|
|
|
1502
1443
|
<div class="flex-1 overflow-hidden">
|
|
1503
1444
|
<!-- Unified layout: always render both panels to preserve state (like Files panel) -->
|
|
1504
1445
|
<div class="h-full flex">
|
|
1505
|
-
<!-- Left panel: Changes list
|
|
1446
|
+
<!-- Left panel: Changes list -->
|
|
1506
1447
|
<div
|
|
1507
1448
|
class={isTwoColumnMode
|
|
1508
1449
|
? 'w-72 flex-shrink-0 h-full overflow-hidden border-r border-slate-200 dark:border-slate-700 flex flex-col'
|
|
1509
1450
|
: (viewMode === 'list' ? 'w-full h-full overflow-hidden flex flex-col' : 'hidden')}
|
|
1510
1451
|
>
|
|
1452
|
+
{@render viewTabBar()}
|
|
1511
1453
|
{@render changesList()}
|
|
1512
1454
|
</div>
|
|
1513
1455
|
|
|
1514
|
-
<!-- Right panel: Diff viewer
|
|
1456
|
+
<!-- Right panel: Diff viewer -->
|
|
1515
1457
|
<div
|
|
1516
1458
|
class={isTwoColumnMode
|
|
1517
1459
|
? 'flex-1 h-full overflow-hidden flex flex-col'
|
|
@@ -139,26 +139,72 @@ export function clearSessionProcessState(sessionId: string): void {
|
|
|
139
139
|
// UNREAD SESSION MANAGEMENT
|
|
140
140
|
// ========================================
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Persist the current unread sessions Map to the server via user:save-state.
|
|
144
|
+
* Uses the same proven infrastructure as currentProjectId/lastView persistence.
|
|
145
|
+
* Debounced: only the last call within 500ms actually persists.
|
|
146
|
+
*/
|
|
147
|
+
let saveUnreadTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
148
|
+
|
|
149
|
+
function persistUnreadSessions(): void {
|
|
150
|
+
if (saveUnreadTimeout) clearTimeout(saveUnreadTimeout);
|
|
151
|
+
saveUnreadTimeout = setTimeout(() => {
|
|
152
|
+
const serialized = Object.fromEntries(appState.unreadSessions);
|
|
153
|
+
debug.log('session', '[unread] Persisting to server:', serialized);
|
|
154
|
+
ws.http('user:save-state', { key: 'unreadSessions', value: serialized }).catch(err => {
|
|
155
|
+
debug.error('session', '[unread] Error persisting unread sessions:', err);
|
|
156
|
+
});
|
|
157
|
+
}, 500);
|
|
158
|
+
}
|
|
159
|
+
|
|
142
160
|
/**
|
|
143
161
|
* Mark a session as unread (has new activity the user hasn't seen).
|
|
162
|
+
* Persists to backend so the state survives browser refresh.
|
|
144
163
|
*/
|
|
145
164
|
export function markSessionUnread(sessionId: string, projectId: string): void {
|
|
146
165
|
const next = new Map(appState.unreadSessions);
|
|
147
166
|
next.set(sessionId, projectId);
|
|
148
167
|
appState.unreadSessions = next;
|
|
168
|
+
|
|
169
|
+
// Persist to backend via user:save-state (proven infrastructure)
|
|
170
|
+
debug.log('session', `[unread] markSessionUnread: sessionId=${sessionId}, projectId=${projectId}`);
|
|
171
|
+
ws.emit('sessions:mark-unread', { sessionId, projectId });
|
|
172
|
+
persistUnreadSessions();
|
|
149
173
|
}
|
|
150
174
|
|
|
151
175
|
/**
|
|
152
176
|
* Mark a session as read (user has viewed it).
|
|
177
|
+
* Persists to backend so the state survives browser refresh.
|
|
153
178
|
*/
|
|
154
179
|
export function markSessionRead(sessionId: string): void {
|
|
155
180
|
if (appState.unreadSessions.has(sessionId)) {
|
|
156
181
|
const next = new Map(appState.unreadSessions);
|
|
157
182
|
next.delete(sessionId);
|
|
158
183
|
appState.unreadSessions = next;
|
|
184
|
+
|
|
185
|
+
// Persist to backend via user:save-state (proven infrastructure)
|
|
186
|
+
debug.log('session', `[unread] markSessionRead: sessionId=${sessionId}`);
|
|
187
|
+
ws.emit('sessions:mark-read', { sessionId });
|
|
188
|
+
persistUnreadSessions();
|
|
159
189
|
}
|
|
160
190
|
}
|
|
161
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Restore unread sessions from server state (called during initialization).
|
|
194
|
+
*/
|
|
195
|
+
export function restoreUnreadSessions(saved: Record<string, string> | null): void {
|
|
196
|
+
if (!saved || typeof saved !== 'object') return;
|
|
197
|
+
|
|
198
|
+
const next = new Map(appState.unreadSessions);
|
|
199
|
+
for (const [sessionId, projectId] of Object.entries(saved)) {
|
|
200
|
+
if (typeof sessionId === 'string' && typeof projectId === 'string') {
|
|
201
|
+
next.set(sessionId, projectId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
appState.unreadSessions = next;
|
|
205
|
+
debug.log('session', '[unread] Restored from server:', Object.fromEntries(appState.unreadSessions));
|
|
206
|
+
}
|
|
207
|
+
|
|
162
208
|
/**
|
|
163
209
|
* Check if a session is unread.
|
|
164
210
|
*/
|
|
@@ -13,7 +13,7 @@ import { buildMetadataFromTransport } from '$shared/utils/message-formatter';
|
|
|
13
13
|
import ws from '$frontend/lib/utils/ws';
|
|
14
14
|
import { projectState } from './projects.svelte';
|
|
15
15
|
import { setupEditModeListener, restoreEditMode } from '$frontend/lib/stores/ui/edit-mode.svelte';
|
|
16
|
-
import { markSessionUnread, markSessionRead } from '$frontend/lib/stores/core/app.svelte';
|
|
16
|
+
import { markSessionUnread, markSessionRead, appState } from '$frontend/lib/stores/core/app.svelte';
|
|
17
17
|
import { debug } from '$shared/utils/logger';
|
|
18
18
|
|
|
19
19
|
interface SessionState {
|
|
@@ -236,9 +236,20 @@ export async function loadSessions() {
|
|
|
236
236
|
const response = await ws.http('sessions:list');
|
|
237
237
|
|
|
238
238
|
if (response) {
|
|
239
|
-
const { sessions, currentSessionId } = response;
|
|
239
|
+
const { sessions, currentSessionId, unreadSessionIds } = response;
|
|
240
240
|
sessionState.sessions = sessions;
|
|
241
241
|
|
|
242
|
+
// Restore unread session state from backend
|
|
243
|
+
debug.log('session', '[unread] loadSessions received unreadSessionIds:', unreadSessionIds);
|
|
244
|
+
if (unreadSessionIds && Array.isArray(unreadSessionIds) && unreadSessionIds.length > 0) {
|
|
245
|
+
const next = new Map(appState.unreadSessions);
|
|
246
|
+
for (const { sessionId, projectId } of unreadSessionIds) {
|
|
247
|
+
next.set(sessionId, projectId);
|
|
248
|
+
}
|
|
249
|
+
appState.unreadSessions = next;
|
|
250
|
+
debug.log('session', '[unread] Restored unread sessions:', Array.from(appState.unreadSessions.entries()));
|
|
251
|
+
}
|
|
252
|
+
|
|
242
253
|
// Auto-restore: find the active session for the current project
|
|
243
254
|
if (!sessionState.currentSession) {
|
|
244
255
|
const currentProject = projectState.currentProject;
|
|
@@ -310,7 +321,7 @@ export async function reloadSessionsForProject(): Promise<string | null> {
|
|
|
310
321
|
try {
|
|
311
322
|
const response = await ws.http('sessions:list');
|
|
312
323
|
if (response) {
|
|
313
|
-
const { sessions, currentSessionId } = response;
|
|
324
|
+
const { sessions, currentSessionId, unreadSessionIds } = response;
|
|
314
325
|
// Merge: keep sessions from other projects, replace sessions for current project
|
|
315
326
|
const currentProjectId = projectState.currentProject?.id;
|
|
316
327
|
if (currentProjectId) {
|
|
@@ -321,6 +332,16 @@ export async function reloadSessionsForProject(): Promise<string | null> {
|
|
|
321
332
|
} else {
|
|
322
333
|
sessionState.sessions = sessions;
|
|
323
334
|
}
|
|
335
|
+
|
|
336
|
+
// Restore unread session state from backend
|
|
337
|
+
if (unreadSessionIds && Array.isArray(unreadSessionIds)) {
|
|
338
|
+
const next = new Map(appState.unreadSessions);
|
|
339
|
+
for (const { sessionId, projectId } of unreadSessionIds) {
|
|
340
|
+
next.set(sessionId, projectId);
|
|
341
|
+
}
|
|
342
|
+
appState.unreadSessions = next;
|
|
343
|
+
}
|
|
344
|
+
|
|
324
345
|
return currentSessionId || null;
|
|
325
346
|
}
|
|
326
347
|
} catch (error) {
|
|
@@ -459,20 +459,20 @@ const defaultPanels: Record<PanelId, PanelConfig> = {
|
|
|
459
459
|
minimized: false,
|
|
460
460
|
order: 0
|
|
461
461
|
},
|
|
462
|
-
preview: {
|
|
463
|
-
id: 'preview',
|
|
464
|
-
title: 'Preview',
|
|
465
|
-
icon: 'lucide:globe',
|
|
466
|
-
visible: true,
|
|
467
|
-
minimized: false,
|
|
468
|
-
order: 1
|
|
469
|
-
},
|
|
470
462
|
files: {
|
|
471
463
|
id: 'files',
|
|
472
464
|
title: 'Files',
|
|
473
465
|
icon: 'lucide:folder',
|
|
474
466
|
visible: true,
|
|
475
467
|
minimized: false,
|
|
468
|
+
order: 1
|
|
469
|
+
},
|
|
470
|
+
git: {
|
|
471
|
+
id: 'git',
|
|
472
|
+
title: 'Source Control',
|
|
473
|
+
icon: 'lucide:git-branch',
|
|
474
|
+
visible: true,
|
|
475
|
+
minimized: false,
|
|
476
476
|
order: 2
|
|
477
477
|
},
|
|
478
478
|
terminal: {
|
|
@@ -483,10 +483,10 @@ const defaultPanels: Record<PanelId, PanelConfig> = {
|
|
|
483
483
|
minimized: false,
|
|
484
484
|
order: 3
|
|
485
485
|
},
|
|
486
|
-
|
|
487
|
-
id: '
|
|
488
|
-
title: '
|
|
489
|
-
icon: 'lucide:
|
|
486
|
+
preview: {
|
|
487
|
+
id: 'preview',
|
|
488
|
+
title: 'Preview',
|
|
489
|
+
icon: 'lucide:globe',
|
|
490
490
|
visible: true,
|
|
491
491
|
minimized: false,
|
|
492
492
|
order: 4
|
|
@@ -496,9 +496,9 @@ const defaultPanels: Record<PanelId, PanelConfig> = {
|
|
|
496
496
|
export const PANEL_OPTIONS: { id: PanelId; title: string; icon: IconName }[] = [
|
|
497
497
|
{ id: 'chat', title: 'AI Assistant', icon: 'lucide:bot' },
|
|
498
498
|
{ id: 'files', title: 'Files', icon: 'lucide:folder' },
|
|
499
|
-
{ id: '
|
|
499
|
+
{ id: 'git', title: 'Source Control', icon: 'lucide:git-branch' },
|
|
500
500
|
{ id: 'terminal', title: 'Terminal', icon: 'lucide:terminal' },
|
|
501
|
-
{ id: '
|
|
501
|
+
{ id: 'preview', title: 'Preview', icon: 'lucide:globe' }
|
|
502
502
|
];
|
|
503
503
|
|
|
504
504
|
// Default: Main + Stack layout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myrialabs/clopen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "All-in-one web workspace for Claude Code & OpenCode — chat, terminal, git, browser preview, checkpoints, and real-time collaboration",
|
|
5
5
|
"author": "Myria Labs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"@types/bun": "^1.2.18",
|
|
63
63
|
"@types/node": "^24.0.14",
|
|
64
64
|
"@types/qrcode": "^1.5.6",
|
|
65
|
-
"@types/xterm": "^3.0.0",
|
|
66
65
|
"concurrently": "^9.2.1",
|
|
67
66
|
"eslint": "^9.31.0",
|
|
68
67
|
"eslint-plugin-svelte": "^3.10.1",
|
|
@@ -83,8 +82,12 @@
|
|
|
83
82
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
84
83
|
"@monaco-editor/loader": "^1.5.0",
|
|
85
84
|
"@opencode-ai/sdk": "^1.2.15",
|
|
86
|
-
"@xterm/addon-
|
|
87
|
-
"@xterm/addon-
|
|
85
|
+
"@xterm/addon-clipboard": "^0.2.0",
|
|
86
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
87
|
+
"@xterm/addon-ligatures": "^0.10.0",
|
|
88
|
+
"@xterm/addon-unicode11": "^0.9.0",
|
|
89
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
90
|
+
"@xterm/xterm": "^6.0.0",
|
|
88
91
|
"bun-pty": "^0.4.2",
|
|
89
92
|
"cloudflared": "^0.7.1",
|
|
90
93
|
"elysia": "^1.4.19",
|
|
@@ -95,8 +98,7 @@
|
|
|
95
98
|
"nanoid": "^5.1.6",
|
|
96
99
|
"puppeteer": "^24.33.0",
|
|
97
100
|
"puppeteer-cluster": "^0.25.0",
|
|
98
|
-
"qrcode": "^1.5.4"
|
|
99
|
-
"xterm": "^5.3.0"
|
|
101
|
+
"qrcode": "^1.5.4"
|
|
100
102
|
},
|
|
101
103
|
"trustedDependencies": [
|
|
102
104
|
"cloudflared",
|