mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
fix: update mobile CSS for OpenCode run button group
Replace dead .btn-claude selectors in mobile.css with proper styling for the new split run button (.btn-run + .btn-run-gear). Add mobile touch-friendly dropdown menu options (10px padding, 35px height). Include mode-specific colors for both Claude (blue) and OpenCode (green) on mobile. Also guard Claude-specific features (Ralph, Respawn) from running on OpenCode sessions in server.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1218,6 +1218,7 @@ export class Session extends EventEmitter {
|
||||
};
|
||||
|
||||
// Forward to Ralph tracker to detect Ralph loops and todos
|
||||
// (opencode sessions already returned early at line 1209)
|
||||
if (this._ralphTracker.enabled || !this._ralphTracker.autoEnableDisabled) {
|
||||
this._ralphTracker.processCleanData(getCleanData());
|
||||
}
|
||||
|
||||
+37
-9
@@ -4056,6 +4056,11 @@ class ClaudemanApp {
|
||||
this.terminal.scrollToBottom();
|
||||
// Re-position local echo overlay at new prompt location
|
||||
this._localEchoOverlay?.rerender();
|
||||
// Resize PTY to match actual browser dimensions (critical for OpenCode
|
||||
// TUI sessions that render at fixed 120x40 until told the real size)
|
||||
if (this.activeSessionId) {
|
||||
this.sendResize(this.activeSessionId);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('needsRefresh reload failed:', err);
|
||||
@@ -6191,6 +6196,14 @@ class ClaudemanApp {
|
||||
const sessionNameEl = document.getElementById('closeConfirmSessionName');
|
||||
sessionNameEl.textContent = name;
|
||||
|
||||
// Update kill button text based on session mode
|
||||
const killTitle = document.getElementById('closeConfirmKillTitle');
|
||||
if (killTitle) {
|
||||
killTitle.textContent = session.mode === 'opencode'
|
||||
? 'Kill Tmux & OpenCode'
|
||||
: 'Kill Tmux & Claude Code';
|
||||
}
|
||||
|
||||
document.getElementById('closeConfirmModal').classList.add('active');
|
||||
}
|
||||
|
||||
@@ -9629,9 +9642,9 @@ class ClaudemanApp {
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.error || 'Failed to start OpenCode');
|
||||
|
||||
// Switch to the new session
|
||||
// Switch to the new session (don't pre-set activeSessionId — selectSession
|
||||
// early-returns when IDs match, skipping buffer load and sendResize)
|
||||
if (data.sessionId) {
|
||||
this.activeSessionId = data.sessionId;
|
||||
await this.selectSession(data.sessionId);
|
||||
}
|
||||
|
||||
@@ -10461,13 +10474,28 @@ class ClaudemanApp {
|
||||
document.getElementById('respawnPresetSelect').value = '';
|
||||
document.getElementById('presetDescriptionHint').textContent = '';
|
||||
|
||||
// Populate Ralph Wiggum form with current session values
|
||||
const ralphState = this.ralphStates.get(sessionId);
|
||||
this.populateRalphForm({
|
||||
enabled: ralphState?.loop?.enabled ?? session.ralphLoop?.enabled ?? false,
|
||||
completionPhrase: ralphState?.loop?.completionPhrase || session.ralphLoop?.completionPhrase || '',
|
||||
maxIterations: ralphState?.loop?.maxIterations || session.ralphLoop?.maxIterations || 0,
|
||||
});
|
||||
// Hide Ralph/Todo tab and Respawn tab for opencode sessions (not supported)
|
||||
const ralphTabBtn = document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]');
|
||||
const respawnTabBtn = document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');
|
||||
if (isOpenCode) {
|
||||
if (ralphTabBtn) ralphTabBtn.style.display = 'none';
|
||||
if (respawnTabBtn) respawnTabBtn.style.display = 'none';
|
||||
// Default to Context tab for opencode sessions since Respawn is hidden
|
||||
this.switchOptionsTab('context');
|
||||
} else {
|
||||
if (ralphTabBtn) ralphTabBtn.style.display = '';
|
||||
if (respawnTabBtn) respawnTabBtn.style.display = '';
|
||||
}
|
||||
|
||||
// Populate Ralph Wiggum form with current session values (skip for opencode)
|
||||
if (!isOpenCode) {
|
||||
const ralphState = this.ralphStates.get(sessionId);
|
||||
this.populateRalphForm({
|
||||
enabled: ralphState?.loop?.enabled ?? session.ralphLoop?.enabled ?? false,
|
||||
completionPhrase: ralphState?.loop?.completionPhrase || session.ralphLoop?.completionPhrase || '',
|
||||
maxIterations: ralphState?.loop?.maxIterations || session.ralphLoop?.maxIterations || 0,
|
||||
});
|
||||
}
|
||||
|
||||
const modal = document.getElementById('sessionOptionsModal');
|
||||
modal.classList.add('active');
|
||||
|
||||
@@ -782,10 +782,10 @@
|
||||
<div class="close-options">
|
||||
<button class="close-option" onclick="app.confirmCloseSession(false)">
|
||||
<span class="close-option-title">Remove Tab</span>
|
||||
<span class="close-option-desc">Screen session keeps running in background</span>
|
||||
<span class="close-option-desc">Tmux session keeps running in background</span>
|
||||
</button>
|
||||
<button class="close-option close-option-danger" onclick="app.confirmCloseSession(true)">
|
||||
<span class="close-option-title">Kill Tmux & Claude Code</span>
|
||||
<span class="close-option-title" id="closeConfirmKillTitle">Kill Tmux & Claude Code</span>
|
||||
<span class="close-option-desc">Terminate the session completely</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+69
-24
@@ -560,26 +560,78 @@ html.mobile-init .file-browser-panel {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Primary action button - Run Claude (matches arrow button blue) */
|
||||
.btn-toolbar.btn-claude {
|
||||
/* Run button group — split button with dropdown */
|
||||
.run-btn-group {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
padding: 0 0.75rem !important;
|
||||
background: #1e3a5f;
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
color: #93c5fd;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-claude:hover,
|
||||
.btn-toolbar.btn-claude:active {
|
||||
.btn-toolbar.btn-run {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 0.6rem !important;
|
||||
font-weight: 500;
|
||||
border-radius: 4px 0 0 4px !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run-gear {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 0.35rem !important;
|
||||
min-width: unset !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
border-left: 1px solid rgba(255,255,255,0.15) !important;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run-gear svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* Claude mode colors on mobile */
|
||||
.btn-toolbar.btn-run.mode-claude,
|
||||
.btn-toolbar.btn-run-gear.mode-claude {
|
||||
background: #1e3a5f;
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run.mode-claude:active,
|
||||
.btn-toolbar.btn-run-gear.mode-claude:active {
|
||||
background: #2563eb;
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-claude svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
/* OpenCode mode colors on mobile */
|
||||
.btn-toolbar.btn-run.mode-opencode,
|
||||
.btn-toolbar.btn-run-gear.mode-opencode {
|
||||
background: #0a2e2a;
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run.mode-opencode:active,
|
||||
.btn-toolbar.btn-run-gear.mode-opencode:active {
|
||||
background: #0d4a40;
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
|
||||
/* Run mode dropdown menu — positioned above toolbar on mobile */
|
||||
.run-mode-menu {
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
margin-bottom: 6px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.run-mode-option {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Stop button - visible on mobile, icon-only */
|
||||
@@ -617,19 +669,12 @@ html.mobile-init .file-browser-panel {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Simplified toolbar layout - Run Claude, Shell, and Case */
|
||||
/* Simplified toolbar layout — Run, Shell, and Case */
|
||||
.toolbar-left .toolbar-group:first-child {
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-claude {
|
||||
flex: 0 0 auto;
|
||||
min-width: fit-content;
|
||||
white-space: nowrap;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-shell {
|
||||
flex: 0 0 auto;
|
||||
min-width: fit-content;
|
||||
@@ -643,7 +688,7 @@ html.mobile-init .file-browser-panel {
|
||||
flex: 1 1 0 !important;
|
||||
min-width: 0;
|
||||
padding: 0 8px !important;
|
||||
order: 2; /* Between Run Claude and Run Shell */
|
||||
order: 2; /* Between Run and Run Shell */
|
||||
overflow: hidden;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
@@ -656,7 +701,7 @@ html.mobile-init .file-browser-panel {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Mobile case settings gear button - next to Run Claude */
|
||||
/* Mobile case settings gear button - next to Run */
|
||||
.btn-case-settings-mobile {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
@@ -669,7 +714,7 @@ html.mobile-init .file-browser-panel {
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
order: 1; /* Next to Run Claude */
|
||||
order: 1; /* Next to Run */
|
||||
}
|
||||
|
||||
.btn-case-settings-mobile:active {
|
||||
|
||||
+41
-24
@@ -1389,6 +1389,11 @@ export class WebServer extends EventEmitter {
|
||||
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found');
|
||||
}
|
||||
|
||||
// Ralph tracker is not supported for opencode sessions
|
||||
if (session.mode === 'opencode') {
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Ralph tracker is not supported for opencode sessions');
|
||||
}
|
||||
|
||||
// Handle reset first (before other config)
|
||||
if (reset) {
|
||||
if (reset === 'full') {
|
||||
@@ -1668,7 +1673,8 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
try {
|
||||
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled and not explicitly disabled by user)
|
||||
if (this.store.getConfig().ralphEnabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
// Ralph tracker is not supported for opencode sessions
|
||||
if (session.mode !== 'opencode' && this.store.getConfig().ralphEnabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
autoConfigureRalph(session, session.workingDir, () => {});
|
||||
if (!session.ralphTracker.enabled) {
|
||||
session.ralphTracker.enable();
|
||||
@@ -1881,6 +1887,11 @@ export class WebServer extends EventEmitter {
|
||||
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found');
|
||||
}
|
||||
|
||||
// Respawn is not supported for opencode sessions
|
||||
if (session.mode === 'opencode') {
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Respawn is not supported for opencode sessions');
|
||||
}
|
||||
|
||||
// Create or get existing controller
|
||||
let controller = this.respawnControllers.get(id);
|
||||
if (!controller) {
|
||||
@@ -2017,7 +2028,8 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
try {
|
||||
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled and not explicitly disabled by user)
|
||||
if (this.store.getConfig().ralphEnabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
// Ralph tracker is not supported for opencode sessions
|
||||
if (session.mode !== 'opencode' && this.store.getConfig().ralphEnabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
autoConfigureRalph(session, session.workingDir, () => {});
|
||||
if (!session.ralphTracker.enabled) {
|
||||
session.ralphTracker.enable();
|
||||
@@ -4219,8 +4231,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
this.runSummaryTrackers.set(session.id, summaryTracker);
|
||||
summaryTracker.recordSessionStarted(session.mode, session.workingDir);
|
||||
|
||||
// Set working directory for Ralph tracker to auto-load @fix_plan.md
|
||||
session.ralphTracker.setWorkingDir(session.workingDir);
|
||||
// Set working directory for Ralph tracker to auto-load @fix_plan.md (not supported for opencode sessions)
|
||||
if (session.mode !== 'opencode') {
|
||||
session.ralphTracker.setWorkingDir(session.workingDir);
|
||||
}
|
||||
|
||||
// Start watching for new images in this session's working directory (if enabled globally and per-session)
|
||||
if (await this.isImageWatcherEnabled() && session.imageWatcherEnabled) {
|
||||
@@ -5422,20 +5436,22 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
console.log(`[Server] Restored tokens for session ${session.id}: ${totalTokens} tokens, $${(savedState.totalCost ?? 0).toFixed(4)}`);
|
||||
}
|
||||
}
|
||||
// Ralph / Todo tracker
|
||||
if (savedState.ralphAutoEnableDisabled) {
|
||||
session.ralphTracker.disableAutoEnable();
|
||||
console.log(`[Server] Restored Ralph auto-enable disabled for session ${session.id}`);
|
||||
} else if (savedState.ralphEnabled) {
|
||||
// If Ralph was enabled and not explicitly disabled, allow re-enabling on restart
|
||||
session.ralphTracker.enableAutoEnable();
|
||||
}
|
||||
if (savedState.ralphEnabled) {
|
||||
session.ralphTracker.enable();
|
||||
if (savedState.ralphCompletionPhrase) {
|
||||
session.ralphTracker.startLoop(savedState.ralphCompletionPhrase);
|
||||
// Ralph / Todo tracker (not supported for opencode sessions)
|
||||
if (session.mode !== 'opencode') {
|
||||
if (savedState.ralphAutoEnableDisabled) {
|
||||
session.ralphTracker.disableAutoEnable();
|
||||
console.log(`[Server] Restored Ralph auto-enable disabled for session ${session.id}`);
|
||||
} else if (savedState.ralphEnabled) {
|
||||
// If Ralph was enabled and not explicitly disabled, allow re-enabling on restart
|
||||
session.ralphTracker.enableAutoEnable();
|
||||
}
|
||||
if (savedState.ralphEnabled) {
|
||||
session.ralphTracker.enable();
|
||||
if (savedState.ralphCompletionPhrase) {
|
||||
session.ralphTracker.startLoop(savedState.ralphCompletionPhrase);
|
||||
}
|
||||
console.log(`[Server] Restored Ralph tracker for session ${session.id} (phrase: ${savedState.ralphCompletionPhrase || 'none'})`);
|
||||
}
|
||||
console.log(`[Server] Restored Ralph tracker for session ${session.id} (phrase: ${savedState.ralphCompletionPhrase || 'none'})`);
|
||||
}
|
||||
// Nice priority config
|
||||
if (savedState.niceEnabled !== undefined) {
|
||||
@@ -5448,8 +5464,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
if (savedState.flickerFilterEnabled !== undefined) {
|
||||
session.flickerFilterEnabled = savedState.flickerFilterEnabled;
|
||||
}
|
||||
// Respawn controller
|
||||
if (savedState.respawnEnabled && savedState.respawnConfig) {
|
||||
// Respawn controller (not supported for opencode sessions)
|
||||
if (session.mode !== 'opencode' && savedState.respawnEnabled && savedState.respawnConfig) {
|
||||
try {
|
||||
this.restoreRespawnController(session, savedState.respawnConfig, 'state.json');
|
||||
} catch (err) {
|
||||
@@ -5458,8 +5474,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: restore respawn from mux-sessions.json if state.json didn't have it
|
||||
if (!this.respawnControllers.has(session.id) && muxSession.respawnConfig?.enabled) {
|
||||
// Fallback: restore respawn from mux-sessions.json if state.json didn't have it (not supported for opencode)
|
||||
if (session.mode !== 'opencode' && !this.respawnControllers.has(session.id) && muxSession.respawnConfig?.enabled) {
|
||||
try {
|
||||
this.restoreRespawnController(session, muxSession.respawnConfig, 'mux-sessions.json');
|
||||
} catch (err) {
|
||||
@@ -5468,7 +5484,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
|
||||
// Fallback: restore Ralph state from state-inner.json if not already set and not explicitly disabled
|
||||
if (!session.ralphTracker.enabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
// Ralph tracker is not supported for opencode sessions
|
||||
if (session.mode !== 'opencode' && !session.ralphTracker.enabled && !session.ralphTracker.autoEnableDisabled) {
|
||||
const ralphState = this.store.getRalphState(muxSession.sessionId);
|
||||
if (ralphState?.loop?.enabled) {
|
||||
session.ralphTracker.restoreState(ralphState.loop, ralphState.todos);
|
||||
@@ -5476,8 +5493,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: auto-detect completion phrase from CLAUDE.md
|
||||
if (session.ralphTracker.enabled && !session.ralphTracker.loopState.completionPhrase) {
|
||||
// Fallback: auto-detect completion phrase from CLAUDE.md (not supported for opencode)
|
||||
if (session.mode !== 'opencode' && session.ralphTracker.enabled && !session.ralphTracker.loopState.completionPhrase) {
|
||||
const claudeMdPath = join(session.workingDir, 'CLAUDE.md');
|
||||
const completionPhrase = extractCompletionPhrase(claudeMdPath);
|
||||
if (completionPhrase) {
|
||||
|
||||
Reference in New Issue
Block a user