From cd4905287ea4bcbf4f774692cce43da519a74cec Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 31 Jan 2026 22:17:47 +0100 Subject: [PATCH] feat: add mobile case picker to toolbar - Add Case button to mobile toolbar (between Run Claude and Run Shell) - Add bottom sheet modal for case selection on mobile - Show current case name on button (truncated) - Allow creating new cases from mobile picker - Sync selection with desktop dropdown Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 2 +- package.json | 2 +- src/web/public/app.js | 89 +++++++++++++++ src/web/public/index.html | 27 +++++ src/web/public/styles.css | 231 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 339 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a8840a80..fc8a54d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` -**Version**: 0.1472 (must match `package.json` for npm publish) +**Version**: 0.1473 (must match `package.json` for npm publish) ## Project Overview diff --git a/package.json b/package.json index f15c3dc2..649dab46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1472", + "version": "0.1473", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/src/web/public/app.js b/src/web/public/app.js index 30ea8dfe..953c6062 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -6717,19 +6717,23 @@ class ClaudemanApp { if (selectCaseName) { select.value = selectCaseName; this.updateDirDisplayForCase(selectCaseName); + this.updateMobileCaseLabel(selectCaseName); } else if (lastUsedCase && cases.some(c => c.name === lastUsedCase)) { // Use lastUsedCase if available and exists select.value = lastUsedCase; this.updateDirDisplayForCase(lastUsedCase); + this.updateMobileCaseLabel(lastUsedCase); } else if (cases.length > 0) { // Fallback to testcase or first case const firstCase = cases.find(c => c.name === 'testcase') || cases[0]; select.value = firstCase.name; this.updateDirDisplayForCase(firstCase.name); + this.updateMobileCaseLabel(firstCase.name); } else { // No cases exist yet - show the default case name as directory select.value = 'testcase'; document.getElementById('dirDisplay').textContent = '~/claudeman-cases/testcase'; + this.updateMobileCaseLabel('testcase'); } // Only add event listener once (on first load) @@ -6737,6 +6741,7 @@ class ClaudemanApp { select.addEventListener('change', () => { this.updateDirDisplayForCase(select.value); this.saveLastUsedCase(select.value); + this.updateMobileCaseLabel(select.value); }); select.dataset.listenerAdded = 'true'; } @@ -13380,6 +13385,90 @@ class ClaudemanApp { } } + // ============================================================================ + // Mobile Case Picker + // ============================================================================ + + showMobileCasePicker() { + const modal = document.getElementById('mobileCasePickerModal'); + const listContainer = document.getElementById('mobileCaseList'); + const select = document.getElementById('quickStartCase'); + const currentCase = select.value; + + // Build case list HTML + let html = ''; + const cases = this.cases || []; + + // Add testcase if not in list + const hasTestcase = cases.some(c => c.name === 'testcase'); + const allCases = hasTestcase ? cases : [{ name: 'testcase' }, ...cases]; + + for (const c of allCases) { + const isSelected = c.name === currentCase; + html += ` + + `; + } + + listContainer.innerHTML = html; + modal.classList.add('active'); + } + + closeMobileCasePicker() { + document.getElementById('mobileCasePickerModal').classList.remove('active'); + } + + selectMobileCase(caseName) { + // Update the desktop select (source of truth) + const select = document.getElementById('quickStartCase'); + select.value = caseName; + + // Update mobile button label + this.updateMobileCaseLabel(caseName); + + // Update directory display + this.updateDirDisplayForCase(caseName); + + // Save as last used + this.saveLastUsedCase(caseName); + + // Close the picker + this.closeMobileCasePicker(); + + this.showToast(`Selected: ${caseName}`, 'success'); + } + + updateMobileCaseLabel(caseName) { + const label = document.getElementById('mobileCaseName'); + if (label) { + // Truncate for mobile display + const maxLen = 6; + label.textContent = caseName.length > maxLen + ? caseName.substring(0, maxLen) + '…' + : caseName; + } + } + + showCreateCaseFromMobile() { + // Close mobile picker first + this.closeMobileCasePicker(); + // Open the create case modal + this.showCreateCaseModal(); + } + renderScreenSessions() { // Debounce renders at 100ms to prevent excessive DOM updates if (this.renderScreenSessionsTimeout) { diff --git a/src/web/public/index.html b/src/web/public/index.html index 49b5e213..1fc8978a 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -286,6 +286,11 @@ + + @@ -1092,6 +1097,28 @@ + + +