claudedesk 4.3.1 → 4.4.0
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/.github/workflows/ci.yml +44 -2
- package/CLAUDE.md +36 -3
- package/PHASE_1_IMPLEMENTATION.md +313 -0
- package/PHASE_2_PARTIAL_IMPLEMENTATION.md +286 -0
- package/dist/main/cli-manager.js +67 -2
- package/dist/main/command-registry.js +196 -0
- package/dist/main/git-manager.js +841 -0
- package/dist/main/index.js +25 -1
- package/dist/main/ipc-handlers.js +347 -3
- package/dist/main/layout-presets-manager.js +233 -0
- package/dist/main/model-history-manager.js +187 -0
- package/dist/main/session-manager.js +83 -26
- package/dist/main/session-persistence.js +1 -0
- package/dist/main/session-pool.js +40 -9
- package/dist/main/settings-persistence.js +67 -12
- package/dist/renderer/assets/index-BNeYLqV4.css +32 -0
- package/dist/renderer/assets/index-D5O5Ljoo.js +17189 -0
- package/dist/renderer/index.html +2 -2
- package/dist/shared/ipc-contract.js +79 -0
- package/dist/shared/model-detector.js +83 -0
- package/dist/shared/types/command-types.js +5 -0
- package/dist/shared/types/git-types.js +2 -0
- package/dist/types/layout-presets.js +11 -0
- package/docs/git-integration-implementation-tasks.md +974 -0
- package/docs/git-integration-product-spec.md +916 -0
- package/docs/git-integration-ui-spec.md +1464 -0
- package/docs/repo-index.md +83 -8
- package/e2e/app-launch.spec.ts +31 -0
- package/e2e/fixtures/electron.ts +34 -0
- package/e2e/keyboard-shortcuts.spec.ts +50 -0
- package/e2e/session.spec.ts +34 -0
- package/e2e/split-view.spec.ts +21 -0
- package/package.json +16 -3
- package/playwright.config.ts +15 -0
- package/src/main/cli-manager.ts +74 -3
- package/src/main/command-registry.ts +221 -0
- package/src/main/git-manager.test.ts +374 -0
- package/src/main/git-manager.ts +909 -0
- package/src/main/index.ts +31 -1
- package/src/main/ipc-emitter.test.ts +60 -0
- package/src/main/ipc-handlers.ts +295 -3
- package/src/main/ipc-registry.test.ts +75 -0
- package/src/main/layout-presets-manager.ts +268 -0
- package/src/main/model-history-manager.ts +196 -0
- package/src/main/session-manager.ts +102 -30
- package/src/main/session-persistence.test.ts +215 -0
- package/src/main/session-persistence.ts +1 -0
- package/src/main/session-pool.ts +31 -9
- package/src/main/settings-persistence.ts +74 -12
- package/src/renderer/App.tsx +215 -43
- package/src/renderer/components/CustomLayoutBuilder.tsx +143 -0
- package/src/renderer/components/GitPanel.test.tsx +181 -0
- package/src/renderer/components/GitPanel.tsx +1407 -0
- package/src/renderer/components/LayoutPicker.tsx +182 -0
- package/src/renderer/components/LayoutPreviewCard.tsx +175 -0
- package/src/renderer/components/ModelHistoryPanel.tsx +435 -0
- package/src/renderer/components/PaneHeader.test.tsx +96 -0
- package/src/renderer/components/PaneHeader.tsx +28 -0
- package/src/renderer/components/SplitLayout.test.tsx +153 -0
- package/src/renderer/components/SplitLayout.tsx +36 -1
- package/src/renderer/components/Terminal.tsx +10 -10
- package/src/renderer/components/WelcomeWizard.tsx +143 -0
- package/src/renderer/components/WizardStepper.tsx +135 -0
- package/src/renderer/components/ui/ClaudeReadinessProgress.tsx +168 -0
- package/src/renderer/components/ui/CommitDialog.test.tsx +134 -0
- package/src/renderer/components/ui/CommitDialog.tsx +464 -0
- package/src/renderer/components/ui/EmptyState.test.tsx +87 -0
- package/src/renderer/components/ui/EmptyState.tsx +115 -86
- package/src/renderer/components/ui/FeatureShowcase.tsx +187 -0
- package/src/renderer/components/ui/FuelGaugeBar.tsx +59 -0
- package/src/renderer/components/ui/FuelStatusIndicator.tsx +358 -0
- package/src/renderer/components/ui/FuelTooltip.tsx +267 -0
- package/src/renderer/components/ui/HelpButton.tsx +43 -0
- package/src/renderer/components/ui/ModelBadge.tsx +72 -0
- package/src/renderer/components/ui/ModelSwitcher.tsx +180 -0
- package/src/renderer/components/ui/PanelFooter.tsx +90 -0
- package/src/renderer/components/ui/PanelHeader.tsx +87 -0
- package/src/renderer/components/ui/PanelHelpOverlay.tsx +274 -0
- package/src/renderer/components/ui/QuickActionCard.tsx +103 -0
- package/src/renderer/components/ui/RecentSessionsList.tsx +154 -0
- package/src/renderer/components/ui/SessionStatusIndicator.tsx +104 -0
- package/src/renderer/components/ui/SettingsDialog.tsx +94 -0
- package/src/renderer/components/ui/ShortcutsPanel.tsx +433 -0
- package/src/renderer/components/ui/StatusPopover.tsx +344 -0
- package/src/renderer/components/ui/TabBar.test.tsx +124 -0
- package/src/renderer/components/ui/TabBar.tsx +152 -168
- package/src/renderer/components/ui/ToolbarDropdown.tsx +227 -0
- package/src/renderer/components/ui/ToolsDropdown.tsx +119 -0
- package/src/renderer/components/ui/TooltipCoach.tsx +217 -0
- package/src/renderer/components/ui/WelcomeHero.tsx +85 -0
- package/src/renderer/components/ui/index.ts +5 -0
- package/src/renderer/components/wizard/Step1_Welcome.tsx +166 -0
- package/src/renderer/components/wizard/Step2_LayoutPicker.tsx +246 -0
- package/src/renderer/components/wizard/Step3_Features.tsx +278 -0
- package/src/renderer/components/wizard/Step4_Ready.tsx +279 -0
- package/src/renderer/hooks/useGit.test.ts +140 -0
- package/src/renderer/hooks/useGit.ts +395 -0
- package/src/renderer/hooks/useLayoutPicker.ts +77 -0
- package/src/renderer/hooks/useModelHistory.ts +69 -0
- package/src/renderer/hooks/useSessionManager.test.ts +146 -0
- package/src/renderer/hooks/useSessionManager.ts +5 -0
- package/src/renderer/hooks/useSplitView.test.ts +168 -0
- package/src/renderer/hooks/useSplitView.ts +126 -128
- package/src/renderer/styles/globals.css +505 -0
- package/src/renderer/utils/fuzzy-search.test.ts +121 -0
- package/src/renderer/utils/layout-tree.test.ts +310 -0
- package/src/renderer/utils/layout-tree.ts +170 -0
- package/src/renderer/utils/variable-resolver.test.ts +102 -0
- package/src/shared/ipc-contract.ts +157 -0
- package/src/shared/ipc-types.ts +52 -1
- package/src/shared/message-parser.test.ts +79 -0
- package/src/shared/model-detector.test.ts +90 -0
- package/src/shared/model-detector.ts +97 -0
- package/src/shared/types/command-types.ts +26 -0
- package/src/shared/types/git-types.ts +126 -0
- package/src/types/layout-presets.ts +22 -0
- package/test/helpers/electron-api-mock.ts +52 -0
- package/test/setup-main.ts +61 -0
- package/test/setup-renderer.ts +8 -0
- package/tsconfig.json +1 -0
- package/tsconfig.main.json +2 -1
- package/vitest.workspace.ts +37 -0
- package/dist/renderer/assets/index-CR22a7j2.css +0 -32
- package/dist/renderer/assets/index-Dp-eceNq.js +0 -13915
package/dist/main/index.js
CHANGED
|
@@ -45,6 +45,10 @@ const history_manager_1 = require("./history-manager");
|
|
|
45
45
|
const checkpoint_manager_1 = require("./checkpoint-manager");
|
|
46
46
|
const agent_team_manager_1 = require("./agent-team-manager");
|
|
47
47
|
const atlas_manager_1 = require("./atlas-manager");
|
|
48
|
+
const layout_presets_manager_1 = require("./layout-presets-manager");
|
|
49
|
+
const command_registry_1 = require("./command-registry");
|
|
50
|
+
const model_history_manager_1 = require("./model-history-manager");
|
|
51
|
+
const git_manager_1 = require("./git-manager");
|
|
48
52
|
const ipc_handlers_1 = require("./ipc-handlers");
|
|
49
53
|
// Check for dev mode via environment variable or if dist files don't exist
|
|
50
54
|
const isDev = process.env.ELECTRON_IS_DEV === 'true' ||
|
|
@@ -58,6 +62,10 @@ let historyManager = null;
|
|
|
58
62
|
let checkpointManager = null;
|
|
59
63
|
let agentTeamManager = null;
|
|
60
64
|
let atlasManager = null;
|
|
65
|
+
let layoutPresetsManager = null;
|
|
66
|
+
let commandRegistry = null;
|
|
67
|
+
let modelHistoryManager = null;
|
|
68
|
+
let gitManager = null;
|
|
61
69
|
const CONFIG_DIR = path.join(electron_1.app.getPath('home'), '.claudedesk');
|
|
62
70
|
const WINDOW_STATE_FILE = path.join(CONFIG_DIR, 'window-state.json');
|
|
63
71
|
function ensureConfigDir() {
|
|
@@ -135,9 +143,11 @@ function createWindow() {
|
|
|
135
143
|
}
|
|
136
144
|
// Initialize managers
|
|
137
145
|
settingsManager = new settings_persistence_1.SettingsManager();
|
|
146
|
+
layoutPresetsManager = new layout_presets_manager_1.LayoutPresetsManager(settingsManager);
|
|
138
147
|
templatesManager = new prompt_templates_manager_1.PromptTemplatesManager();
|
|
139
148
|
historyManager = new history_manager_1.HistoryManager();
|
|
140
149
|
checkpointManager = new checkpoint_manager_1.CheckpointManager(historyManager);
|
|
150
|
+
modelHistoryManager = new model_history_manager_1.ModelHistoryManager();
|
|
141
151
|
// Create session pool with config
|
|
142
152
|
const poolSettings = settingsManager.getSessionPoolSettings();
|
|
143
153
|
sessionPool = new session_pool_1.SessionPool({
|
|
@@ -147,6 +157,7 @@ function createWindow() {
|
|
|
147
157
|
});
|
|
148
158
|
// Create session manager with pool reference
|
|
149
159
|
sessionManager = new session_manager_1.SessionManager(historyManager, sessionPool);
|
|
160
|
+
sessionManager.setModelHistoryManager(modelHistoryManager);
|
|
150
161
|
sessionManager.initialize();
|
|
151
162
|
// Validate split view state against current sessions
|
|
152
163
|
const sessionList = sessionManager.listSessions();
|
|
@@ -155,6 +166,11 @@ function createWindow() {
|
|
|
155
166
|
// Initialize atlas manager
|
|
156
167
|
atlasManager = new atlas_manager_1.AtlasManager();
|
|
157
168
|
atlasManager.setMainWindow(mainWindow);
|
|
169
|
+
// Initialize git manager
|
|
170
|
+
gitManager = new git_manager_1.GitManager(checkpointManager);
|
|
171
|
+
gitManager.setMainWindow(mainWindow);
|
|
172
|
+
// Initialize command registry
|
|
173
|
+
commandRegistry = new command_registry_1.CommandRegistry();
|
|
158
174
|
// Initialize agent team manager
|
|
159
175
|
agentTeamManager = new agent_team_manager_1.AgentTeamManager();
|
|
160
176
|
agentTeamManager.setMainWindow(mainWindow);
|
|
@@ -164,7 +180,7 @@ function createWindow() {
|
|
|
164
180
|
agentTeamManager?.onSessionClosed(sessionId);
|
|
165
181
|
});
|
|
166
182
|
// Setup IPC handlers with pool reference
|
|
167
|
-
(0, ipc_handlers_1.setupIPCHandlers)(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager);
|
|
183
|
+
(0, ipc_handlers_1.setupIPCHandlers)(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager);
|
|
168
184
|
// Initialize pool (delayed, async)
|
|
169
185
|
setTimeout(() => {
|
|
170
186
|
if (sessionPool) {
|
|
@@ -209,6 +225,14 @@ function createWindow() {
|
|
|
209
225
|
});
|
|
210
226
|
mainWindow.on('closed', () => {
|
|
211
227
|
(0, ipc_handlers_1.removeIPCHandlers)();
|
|
228
|
+
if (modelHistoryManager) {
|
|
229
|
+
modelHistoryManager.shutdown();
|
|
230
|
+
modelHistoryManager = null;
|
|
231
|
+
}
|
|
232
|
+
if (gitManager) {
|
|
233
|
+
gitManager.destroy();
|
|
234
|
+
gitManager = null;
|
|
235
|
+
}
|
|
212
236
|
if (atlasManager) {
|
|
213
237
|
atlasManager.destroy();
|
|
214
238
|
atlasManager = null;
|
|
@@ -42,7 +42,7 @@ const quota_service_1 = require("./quota-service");
|
|
|
42
42
|
const file_dragdrop_handler_1 = require("./file-dragdrop-handler");
|
|
43
43
|
const ipc_registry_1 = require("./ipc-registry");
|
|
44
44
|
let registry = null;
|
|
45
|
-
function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager) {
|
|
45
|
+
function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager) {
|
|
46
46
|
// Connect managers to window
|
|
47
47
|
sessionManager.setMainWindow(mainWindow);
|
|
48
48
|
checkpointManager.setMainWindow(mainWindow);
|
|
@@ -50,7 +50,8 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
50
50
|
// ── Session management (invoke) ──
|
|
51
51
|
registry.handle('createSession', async (_e, request) => {
|
|
52
52
|
try {
|
|
53
|
-
|
|
53
|
+
const result = await sessionManager.createSession(request);
|
|
54
|
+
return result;
|
|
54
55
|
}
|
|
55
56
|
catch (err) {
|
|
56
57
|
console.error('Failed to create session:', err);
|
|
@@ -65,7 +66,8 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
65
66
|
});
|
|
66
67
|
registry.handle('renameSession', async (_e, sessionId, newName) => {
|
|
67
68
|
try {
|
|
68
|
-
|
|
69
|
+
const result = await sessionManager.renameSession(sessionId, newName);
|
|
70
|
+
return result;
|
|
69
71
|
}
|
|
70
72
|
catch (err) {
|
|
71
73
|
console.error('Failed to rename session:', err);
|
|
@@ -81,6 +83,47 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
81
83
|
registry.handle('getActiveSession', async () => {
|
|
82
84
|
return sessionManager.getActiveSessionId();
|
|
83
85
|
});
|
|
86
|
+
// ── Model switching ──
|
|
87
|
+
registry.handle('switchModel', async (_e, sessionId, model) => {
|
|
88
|
+
const session = sessionManager.getSession(sessionId);
|
|
89
|
+
console.log('[switchModel] Called with:', { sessionId, model, sessionStatus: session?.status });
|
|
90
|
+
if (!session) {
|
|
91
|
+
console.log('[switchModel] Session not found');
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (session.status === 'exited' || session.status === 'error') {
|
|
95
|
+
console.log('[switchModel] Session not active:', session.status);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
|
|
99
|
+
if (!VALID_MODELS.includes(model)) {
|
|
100
|
+
console.log('[switchModel] Unknown model:', model);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
104
|
+
// Use direct command syntax: /model haiku, /model sonnet, /model opus
|
|
105
|
+
// Type character-by-character to avoid autocomplete interference
|
|
106
|
+
const command = `/model ${model}`;
|
|
107
|
+
console.log('[switchModel] Typing command:', command);
|
|
108
|
+
for (const char of command) {
|
|
109
|
+
sessionManager.sendInput(sessionId, char);
|
|
110
|
+
await delay(50);
|
|
111
|
+
}
|
|
112
|
+
// Wait for autocomplete to settle
|
|
113
|
+
await delay(300);
|
|
114
|
+
// Press Enter to execute
|
|
115
|
+
sessionManager.sendInput(sessionId, '\r');
|
|
116
|
+
console.log('[switchModel] Command sent');
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
// ── Model History ──
|
|
120
|
+
registry.handle('getModelHistory', async (_e, sessionId) => {
|
|
121
|
+
return modelHistoryManager.getHistory(sessionId);
|
|
122
|
+
});
|
|
123
|
+
registry.handle('clearModelHistory', async (_e, sessionId) => {
|
|
124
|
+
modelHistoryManager.clearHistory(sessionId);
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
84
127
|
// ── Dialogs ──
|
|
85
128
|
registry.handle('browseDirectory', async () => {
|
|
86
129
|
const result = await electron_1.dialog.showOpenDialog(mainWindow, {
|
|
@@ -239,6 +282,43 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
239
282
|
throw err;
|
|
240
283
|
}
|
|
241
284
|
});
|
|
285
|
+
// ── Commands ──
|
|
286
|
+
registry.handle('searchCommands', async (_e, query, maxResults = 10) => {
|
|
287
|
+
try {
|
|
288
|
+
return commandRegistry.search(query, maxResults);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
console.error('Failed to search commands:', err);
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
registry.handle('getAllCommands', async () => {
|
|
296
|
+
try {
|
|
297
|
+
return commandRegistry.getAllCommands();
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
console.error('Failed to get all commands:', err);
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
registry.handle('executeCommand', async (_e, commandId, args) => {
|
|
305
|
+
try {
|
|
306
|
+
const command = commandRegistry.getCommand(commandId);
|
|
307
|
+
if (!command) {
|
|
308
|
+
console.error('Command not found:', commandId);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
// Execute command action
|
|
312
|
+
// For now, commands with UI actions will be handled by the renderer
|
|
313
|
+
// This is a placeholder for future server-side command execution
|
|
314
|
+
console.log('Executing command:', commandId, args);
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
console.error('Failed to execute command:', err);
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
242
322
|
// ── Drag-Drop ──
|
|
243
323
|
registry.handle('getFileInfo', async (_e, filePaths) => {
|
|
244
324
|
try {
|
|
@@ -438,6 +518,26 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
438
518
|
return false;
|
|
439
519
|
}
|
|
440
520
|
});
|
|
521
|
+
registry.handle('updateUIMode', async (_e, mode) => {
|
|
522
|
+
try {
|
|
523
|
+
settingsManager.updateUIMode(mode);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
console.error('Failed to update UI mode:', err);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
registry.handle('updateDefaultModel', async (_e, model) => {
|
|
532
|
+
try {
|
|
533
|
+
settingsManager.updateDefaultModel(model);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
console.error('Failed to update default model:', err);
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
});
|
|
441
541
|
// ── Repository Atlas ──
|
|
442
542
|
registry.handle('generateAtlas', async (_e, request) => {
|
|
443
543
|
try {
|
|
@@ -476,6 +576,250 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
476
576
|
throw err;
|
|
477
577
|
}
|
|
478
578
|
});
|
|
579
|
+
// ── Layout Presets ──
|
|
580
|
+
registry.handle('getLayoutPresets', async () => {
|
|
581
|
+
return layoutPresetsManager.getPresets();
|
|
582
|
+
});
|
|
583
|
+
registry.handle('applyLayoutPreset', async (_e, presetId) => {
|
|
584
|
+
try {
|
|
585
|
+
const preset = layoutPresetsManager.getPresetById(presetId);
|
|
586
|
+
if (!preset) {
|
|
587
|
+
console.error('Preset not found:', presetId);
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
// Validate the preset structure
|
|
591
|
+
if (!layoutPresetsManager.validateLayout(preset.structure)) {
|
|
592
|
+
console.error('Invalid preset structure:', presetId);
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
// Update split view state with preset layout
|
|
596
|
+
settingsManager.updateSplitViewState({
|
|
597
|
+
layout: preset.structure,
|
|
598
|
+
focusedPaneId: '', // Will be set by renderer
|
|
599
|
+
});
|
|
600
|
+
// Save the last used preset ID
|
|
601
|
+
layoutPresetsManager.saveLastUsedPreset(presetId);
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
catch (err) {
|
|
605
|
+
console.error('Failed to apply layout preset:', err);
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
registry.handle('applyCustomLayout', async (_e, rows, cols) => {
|
|
610
|
+
try {
|
|
611
|
+
const customLayout = layoutPresetsManager.createCustomGridLayout(rows, cols);
|
|
612
|
+
// Validate the generated layout
|
|
613
|
+
if (!layoutPresetsManager.validateLayout(customLayout)) {
|
|
614
|
+
console.error('Invalid custom layout generated');
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
// Update split view state
|
|
618
|
+
settingsManager.updateSplitViewState({
|
|
619
|
+
layout: customLayout,
|
|
620
|
+
focusedPaneId: '', // Will be set by renderer
|
|
621
|
+
});
|
|
622
|
+
// Save as custom preset
|
|
623
|
+
layoutPresetsManager.saveLastUsedPreset('custom');
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
console.error('Failed to apply custom layout:', err);
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
registry.handle('getCurrentLayout', async () => {
|
|
632
|
+
const settings = settingsManager.getSettings();
|
|
633
|
+
if (settings.splitViewState) {
|
|
634
|
+
return settings.splitViewState.layout;
|
|
635
|
+
}
|
|
636
|
+
// Return default single pane layout
|
|
637
|
+
const { v4: uuidv4 } = require('uuid');
|
|
638
|
+
const defaultLayout = {
|
|
639
|
+
type: 'leaf',
|
|
640
|
+
paneId: uuidv4(),
|
|
641
|
+
sessionId: null,
|
|
642
|
+
};
|
|
643
|
+
return defaultLayout;
|
|
644
|
+
});
|
|
645
|
+
// ── Git Integration ──
|
|
646
|
+
registry.handle('getGitStatus', async (_e, workDir) => {
|
|
647
|
+
try {
|
|
648
|
+
return await gitManager.getStatus(workDir);
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
console.error('Failed to get git status:', err);
|
|
652
|
+
throw err;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
registry.handle('getGitBranches', async (_e, workDir) => {
|
|
656
|
+
try {
|
|
657
|
+
return await gitManager.getBranches(workDir);
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
console.error('Failed to get git branches:', err);
|
|
661
|
+
throw err;
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
registry.handle('gitStageFiles', async (_e, workDir, files) => {
|
|
665
|
+
try {
|
|
666
|
+
return await gitManager.stageFiles(workDir, files);
|
|
667
|
+
}
|
|
668
|
+
catch (err) {
|
|
669
|
+
console.error('Failed to stage files:', err);
|
|
670
|
+
throw err;
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
registry.handle('gitUnstageFiles', async (_e, workDir, files) => {
|
|
674
|
+
try {
|
|
675
|
+
return await gitManager.unstageFiles(workDir, files);
|
|
676
|
+
}
|
|
677
|
+
catch (err) {
|
|
678
|
+
console.error('Failed to unstage files:', err);
|
|
679
|
+
throw err;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
registry.handle('gitStageAll', async (_e, workDir) => {
|
|
683
|
+
try {
|
|
684
|
+
return await gitManager.stageAll(workDir);
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
console.error('Failed to stage all:', err);
|
|
688
|
+
throw err;
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
registry.handle('gitUnstageAll', async (_e, workDir) => {
|
|
692
|
+
try {
|
|
693
|
+
return await gitManager.unstageAll(workDir);
|
|
694
|
+
}
|
|
695
|
+
catch (err) {
|
|
696
|
+
console.error('Failed to unstage all:', err);
|
|
697
|
+
throw err;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
registry.handle('gitCommit', async (_e, request) => {
|
|
701
|
+
try {
|
|
702
|
+
return await gitManager.commit(request);
|
|
703
|
+
}
|
|
704
|
+
catch (err) {
|
|
705
|
+
console.error('Failed to commit:', err);
|
|
706
|
+
throw err;
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
registry.handle('gitGenerateMessage', async (_e, workDir) => {
|
|
710
|
+
try {
|
|
711
|
+
return await gitManager.generateMessage(workDir);
|
|
712
|
+
}
|
|
713
|
+
catch (err) {
|
|
714
|
+
console.error('Failed to generate commit message:', err);
|
|
715
|
+
throw err;
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
registry.handle('gitPush', async (_e, workDir, setUpstream) => {
|
|
719
|
+
try {
|
|
720
|
+
return await gitManager.push(workDir, setUpstream);
|
|
721
|
+
}
|
|
722
|
+
catch (err) {
|
|
723
|
+
console.error('Failed to push:', err);
|
|
724
|
+
throw err;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
registry.handle('gitPull', async (_e, workDir) => {
|
|
728
|
+
try {
|
|
729
|
+
return await gitManager.pull(workDir);
|
|
730
|
+
}
|
|
731
|
+
catch (err) {
|
|
732
|
+
console.error('Failed to pull:', err);
|
|
733
|
+
throw err;
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
registry.handle('gitFetch', async (_e, workDir) => {
|
|
737
|
+
try {
|
|
738
|
+
return await gitManager.fetch(workDir);
|
|
739
|
+
}
|
|
740
|
+
catch (err) {
|
|
741
|
+
console.error('Failed to fetch:', err);
|
|
742
|
+
throw err;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
registry.handle('gitSwitchBranch', async (_e, workDir, branch) => {
|
|
746
|
+
try {
|
|
747
|
+
return await gitManager.switchBranch(workDir, branch);
|
|
748
|
+
}
|
|
749
|
+
catch (err) {
|
|
750
|
+
console.error('Failed to switch branch:', err);
|
|
751
|
+
throw err;
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
registry.handle('gitCreateBranch', async (_e, workDir, branch) => {
|
|
755
|
+
try {
|
|
756
|
+
return await gitManager.createBranch(workDir, branch);
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
console.error('Failed to create branch:', err);
|
|
760
|
+
throw err;
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
registry.handle('gitLog', async (_e, workDir, count) => {
|
|
764
|
+
try {
|
|
765
|
+
return await gitManager.log(workDir, count);
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
console.error('Failed to get git log:', err);
|
|
769
|
+
throw err;
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
registry.handle('gitDiff', async (_e, workDir, filePath, staged) => {
|
|
773
|
+
try {
|
|
774
|
+
return await gitManager.diff(workDir, filePath, staged);
|
|
775
|
+
}
|
|
776
|
+
catch (err) {
|
|
777
|
+
console.error('Failed to get diff:', err);
|
|
778
|
+
throw err;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
registry.handle('gitCommitDiff', async (_e, workDir, hash) => {
|
|
782
|
+
try {
|
|
783
|
+
return await gitManager.commitDiff(workDir, hash);
|
|
784
|
+
}
|
|
785
|
+
catch (err) {
|
|
786
|
+
console.error('Failed to get commit diff:', err);
|
|
787
|
+
throw err;
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
registry.handle('gitDiscardFile', async (_e, workDir, filePath) => {
|
|
791
|
+
try {
|
|
792
|
+
return await gitManager.discardFile(workDir, filePath);
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
console.error('Failed to discard file:', err);
|
|
796
|
+
throw err;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
registry.handle('gitDiscardAll', async (_e, workDir) => {
|
|
800
|
+
try {
|
|
801
|
+
return await gitManager.discardAll(workDir);
|
|
802
|
+
}
|
|
803
|
+
catch (err) {
|
|
804
|
+
console.error('Failed to discard all:', err);
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
registry.handle('gitInit', async (_e, workDir) => {
|
|
809
|
+
try {
|
|
810
|
+
return await gitManager.init(workDir);
|
|
811
|
+
}
|
|
812
|
+
catch (err) {
|
|
813
|
+
console.error('Failed to init git:', err);
|
|
814
|
+
throw err;
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
registry.handle('gitStartWatching', async (_e, workDir) => {
|
|
818
|
+
return gitManager.startWatching(workDir);
|
|
819
|
+
});
|
|
820
|
+
registry.handle('gitStopWatching', async (_e, workDir) => {
|
|
821
|
+
return gitManager.stopWatching(workDir);
|
|
822
|
+
});
|
|
479
823
|
// ── App info ──
|
|
480
824
|
registry.handle('getVersionInfo', async () => {
|
|
481
825
|
const { app } = require('electron');
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LayoutPresetsManager = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const layout_presets_1 = require("../types/layout-presets");
|
|
6
|
+
/**
|
|
7
|
+
* LayoutPresetsManager — Manages layout presets, custom grid generation, and validation.
|
|
8
|
+
*/
|
|
9
|
+
class LayoutPresetsManager {
|
|
10
|
+
constructor(settingsPersistence) {
|
|
11
|
+
this.settingsPersistence = settingsPersistence;
|
|
12
|
+
this.defaultPresets = this.buildDefaultPresets();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get all available layout presets
|
|
16
|
+
*/
|
|
17
|
+
getPresets() {
|
|
18
|
+
return this.defaultPresets;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get a specific preset by ID
|
|
22
|
+
*/
|
|
23
|
+
getPresetById(id) {
|
|
24
|
+
return this.defaultPresets.find(p => p.id === id) || null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate a custom grid layout from rows and columns
|
|
28
|
+
*/
|
|
29
|
+
createCustomGridLayout(rows, cols) {
|
|
30
|
+
// Validate inputs
|
|
31
|
+
if (rows < 1 || rows > 6 || cols < 1 || cols > 6) {
|
|
32
|
+
throw new Error('Rows and columns must be between 1 and 6');
|
|
33
|
+
}
|
|
34
|
+
// If single pane, return a leaf
|
|
35
|
+
if (rows === 1 && cols === 1) {
|
|
36
|
+
return this.createLeaf();
|
|
37
|
+
}
|
|
38
|
+
// If single row, create horizontal grid
|
|
39
|
+
if (rows === 1) {
|
|
40
|
+
return this.createHorizontalGrid(cols);
|
|
41
|
+
}
|
|
42
|
+
// If single column, create vertical grid
|
|
43
|
+
if (cols === 1) {
|
|
44
|
+
return this.createVerticalGrid(rows);
|
|
45
|
+
}
|
|
46
|
+
// Otherwise, create a grid of rows, each containing columns
|
|
47
|
+
const rowSize = 100 / rows;
|
|
48
|
+
const colSize = 100 / cols;
|
|
49
|
+
const rowChildren = [];
|
|
50
|
+
for (let r = 0; r < rows; r++) {
|
|
51
|
+
const colChildren = [];
|
|
52
|
+
for (let c = 0; c < cols; c++) {
|
|
53
|
+
colChildren.push(this.createLeaf());
|
|
54
|
+
}
|
|
55
|
+
// Create horizontal grid for this row
|
|
56
|
+
rowChildren.push({
|
|
57
|
+
type: 'grid',
|
|
58
|
+
id: (0, uuid_1.v4)(),
|
|
59
|
+
direction: 'horizontal',
|
|
60
|
+
children: colChildren,
|
|
61
|
+
sizes: new Array(cols).fill(colSize),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Return vertical grid of rows
|
|
65
|
+
return {
|
|
66
|
+
type: 'grid',
|
|
67
|
+
id: (0, uuid_1.v4)(),
|
|
68
|
+
direction: 'vertical',
|
|
69
|
+
children: rowChildren,
|
|
70
|
+
sizes: new Array(rows).fill(rowSize),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate a layout structure
|
|
75
|
+
*/
|
|
76
|
+
validateLayout(node) {
|
|
77
|
+
try {
|
|
78
|
+
return this.validateNode(node);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error('Layout validation failed:', err);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Save the last used preset ID to settings
|
|
87
|
+
*/
|
|
88
|
+
saveLastUsedPreset(presetId) {
|
|
89
|
+
try {
|
|
90
|
+
this.settingsPersistence.updateLastUsedLayoutPreset(presetId);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error('Failed to save last used preset:', err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ── Private helpers ──
|
|
97
|
+
buildDefaultPresets() {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
id: layout_presets_1.PRESET_IDS.SINGLE,
|
|
101
|
+
name: 'Single Pane',
|
|
102
|
+
description: 'One full-width terminal',
|
|
103
|
+
shortcut: '1',
|
|
104
|
+
structure: this.createLeaf(),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: layout_presets_1.PRESET_IDS.TWO_COLUMN,
|
|
108
|
+
name: '2-Column Layout',
|
|
109
|
+
description: 'Two terminals side-by-side',
|
|
110
|
+
shortcut: '2',
|
|
111
|
+
structure: this.createHorizontalGrid(2),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: layout_presets_1.PRESET_IDS.THREE_COLUMN,
|
|
115
|
+
name: '3-Column Layout',
|
|
116
|
+
description: 'Three equal-width terminals',
|
|
117
|
+
shortcut: '3',
|
|
118
|
+
structure: this.createHorizontalGrid(3),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: layout_presets_1.PRESET_IDS.QUAD_GRID,
|
|
122
|
+
name: '2×2 Grid',
|
|
123
|
+
description: 'Four terminals in a grid',
|
|
124
|
+
shortcut: '4',
|
|
125
|
+
structure: this.createQuadGrid(),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: layout_presets_1.PRESET_IDS.CUSTOM,
|
|
129
|
+
name: 'Custom Layout',
|
|
130
|
+
description: 'Build your own grid',
|
|
131
|
+
shortcut: '5',
|
|
132
|
+
structure: this.createLeaf(), // Placeholder, overridden by builder
|
|
133
|
+
isCustom: true,
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
createLeaf() {
|
|
138
|
+
return {
|
|
139
|
+
type: 'leaf',
|
|
140
|
+
paneId: (0, uuid_1.v4)(),
|
|
141
|
+
sessionId: null,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
createHorizontalGrid(count) {
|
|
145
|
+
const size = 100 / count;
|
|
146
|
+
const children = [];
|
|
147
|
+
for (let i = 0; i < count; i++) {
|
|
148
|
+
children.push(this.createLeaf());
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
type: 'grid',
|
|
152
|
+
id: (0, uuid_1.v4)(),
|
|
153
|
+
direction: 'horizontal',
|
|
154
|
+
children,
|
|
155
|
+
sizes: new Array(count).fill(size),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
createVerticalGrid(count) {
|
|
159
|
+
const size = 100 / count;
|
|
160
|
+
const children = [];
|
|
161
|
+
for (let i = 0; i < count; i++) {
|
|
162
|
+
children.push(this.createLeaf());
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
type: 'grid',
|
|
166
|
+
id: (0, uuid_1.v4)(),
|
|
167
|
+
direction: 'vertical',
|
|
168
|
+
children,
|
|
169
|
+
sizes: new Array(count).fill(size),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
createQuadGrid() {
|
|
173
|
+
// 2×2 grid: vertical split of two horizontal splits
|
|
174
|
+
const topRow = {
|
|
175
|
+
type: 'grid',
|
|
176
|
+
id: (0, uuid_1.v4)(),
|
|
177
|
+
direction: 'horizontal',
|
|
178
|
+
children: [this.createLeaf(), this.createLeaf()],
|
|
179
|
+
sizes: [50, 50],
|
|
180
|
+
};
|
|
181
|
+
const bottomRow = {
|
|
182
|
+
type: 'grid',
|
|
183
|
+
id: (0, uuid_1.v4)(),
|
|
184
|
+
direction: 'horizontal',
|
|
185
|
+
children: [this.createLeaf(), this.createLeaf()],
|
|
186
|
+
sizes: [50, 50],
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
type: 'grid',
|
|
190
|
+
id: (0, uuid_1.v4)(),
|
|
191
|
+
direction: 'vertical',
|
|
192
|
+
children: [topRow, bottomRow],
|
|
193
|
+
sizes: [50, 50],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
validateNode(node) {
|
|
197
|
+
if (!node || typeof node !== 'object') {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
if (node.type === 'leaf') {
|
|
201
|
+
return typeof node.paneId === 'string';
|
|
202
|
+
}
|
|
203
|
+
if (node.type === 'branch') {
|
|
204
|
+
return (typeof node.direction === 'string' &&
|
|
205
|
+
typeof node.ratio === 'number' &&
|
|
206
|
+
node.ratio >= 0 &&
|
|
207
|
+
node.ratio <= 1 &&
|
|
208
|
+
Array.isArray(node.children) &&
|
|
209
|
+
node.children.length === 2 &&
|
|
210
|
+
this.validateNode(node.children[0]) &&
|
|
211
|
+
this.validateNode(node.children[1]));
|
|
212
|
+
}
|
|
213
|
+
if (node.type === 'grid') {
|
|
214
|
+
if (typeof node.id !== 'string' ||
|
|
215
|
+
(node.direction !== 'horizontal' && node.direction !== 'vertical') ||
|
|
216
|
+
!Array.isArray(node.children) ||
|
|
217
|
+
!Array.isArray(node.sizes) ||
|
|
218
|
+
node.children.length !== node.sizes.length ||
|
|
219
|
+
node.children.length === 0) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// Validate sizes sum to approximately 100
|
|
223
|
+
const sum = node.sizes.reduce((a, b) => a + b, 0);
|
|
224
|
+
if (Math.abs(sum - 100) > 0.5) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
// Validate all children
|
|
228
|
+
return node.children.every(child => this.validateNode(child));
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.LayoutPresetsManager = LayoutPresetsManager;
|