chore: bump version to 0.1443

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-30 22:25:59 +01:00
co-authored by Claude Opus 4.5
parent ce0139e393
commit 2a8882a94b
19 changed files with 1071 additions and 1606 deletions
+167 -35
View File
@@ -78,6 +78,106 @@ const BUILTIN_RESPAWN_PRESETS = [
},
];
// ============================================================================
// Mobile Detection
// ============================================================================
/**
* MobileDetection - Detects device type and touch capability.
* Updates body classes for CSS targeting.
*/
const MobileDetection = {
/** Check if device supports touch input */
isTouchDevice() {
return 'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
},
/** Check if device is iOS (iPhone, iPad, iPod) */
isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
},
/** Check if browser is Safari */
isSafari() {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
},
/** Check if screen is small (phone-sized, <430px) */
isSmallScreen() {
return window.innerWidth < 430;
},
/** Check if screen is medium (tablet-sized, 430-768px) */
isMediumScreen() {
return window.innerWidth >= 430 && window.innerWidth < 768;
},
/** Get device type based on screen width */
getDeviceType() {
const width = window.innerWidth;
if (width < 430) return 'mobile';
if (width < 768) return 'tablet';
return 'desktop';
},
/** Update body classes based on device detection */
updateBodyClass() {
const body = document.body;
const deviceType = this.getDeviceType();
const isTouch = this.isTouchDevice();
// Remove existing device classes
body.classList.remove('device-mobile', 'device-tablet', 'device-desktop', 'touch-device', 'ios-device', 'safari-browser');
// Add current device class
body.classList.add(`device-${deviceType}`);
// Add touch device class if applicable
if (isTouch) {
body.classList.add('touch-device');
}
// Add iOS-specific class for safe area handling
if (this.isIOS()) {
body.classList.add('ios-device');
}
// Add Safari class for browser-specific fixes
if (this.isSafari()) {
body.classList.add('safari-browser');
}
},
/** Initialize mobile detection and set up resize listener */
init() {
this.updateBodyClass();
// Debounced resize handler
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => this.updateBodyClass(), 100);
});
}
};
/**
* Get unified coordinates from mouse or touch event.
* @param {MouseEvent|TouchEvent} e - The event
* @returns {{ clientX: number, clientY: number }} Coordinates
*/
function getEventCoords(e) {
if (e.touches && e.touches.length > 0) {
return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
}
if (e.changedTouches && e.changedTouches.length > 0) {
return { clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY };
}
return { clientX: e.clientX, clientY: e.clientY };
}
// ============================================================================
// Accessibility: Focus Trap for Modals
// ============================================================================
@@ -773,6 +873,8 @@ class ClaudemanApp {
}
init() {
// Initialize mobile detection first (adds device classes to body)
MobileDetection.init();
this.initTerminal();
this.loadFontSize();
this.applyHeaderVisibilitySettings();
@@ -4950,7 +5052,7 @@ class ClaudemanApp {
let dragUpdateScheduled = false;
let agentStartPositions = new Map(); // Track agent window start positions during drag
const mousedownHandler = (e) => {
const startHandler = (e) => {
// Don't drag if clicking buttons
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return;
@@ -4962,8 +5064,9 @@ class ClaudemanApp {
this.wizardPosition = { left: rect.left, top: rect.top };
}
startX = e.clientX;
startY = e.clientY;
const coords = getEventCoords(e);
startX = coords.clientX;
startY = coords.clientY;
startLeft = this.wizardPosition.left;
startTop = this.wizardPosition.top;
@@ -4989,8 +5092,9 @@ class ClaudemanApp {
const moveHandler = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const coords = getEventCoords(e);
const dx = coords.clientX - startX;
const dy = coords.clientY - startY;
// Constrain to viewport
const winWidth = wizardContent.offsetWidth || 480;
@@ -5025,13 +5129,18 @@ class ClaudemanApp {
}
};
header.addEventListener('mousedown', mousedownHandler);
// Mouse events
header.addEventListener('mousedown', startHandler);
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
// Touch events
header.addEventListener('touchstart', startHandler, { passive: false });
document.addEventListener('touchmove', moveHandler, { passive: false });
document.addEventListener('touchend', upHandler);
this.wizardDragListeners = {
header,
mousedown: mousedownHandler,
start: startHandler,
move: moveHandler,
up: upHandler,
};
@@ -5039,10 +5148,15 @@ class ClaudemanApp {
cleanupWizardDragging() {
if (this.wizardDragListeners) {
const { header, mousedown, move, up } = this.wizardDragListeners;
header?.removeEventListener('mousedown', mousedown);
const { header, start, move, up } = this.wizardDragListeners;
// Mouse events
header?.removeEventListener('mousedown', start);
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
// Touch events
header?.removeEventListener('touchstart', start);
document.removeEventListener('touchmove', move);
document.removeEventListener('touchend', up);
this.wizardDragListeners = null;
}
}
@@ -8908,28 +9022,32 @@ class ClaudemanApp {
let isDragging = false;
let startX, startY, startLeft, startTop;
const onMouseDown = (e) => {
const onStart = (e) => {
// Only drag from header, not from buttons
if (e.target.closest('button')) return;
if (!panel.classList.contains('detached')) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const coords = getEventCoords(e);
startX = coords.clientX;
startY = coords.clientY;
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
e.preventDefault();
};
const onMouseMove = (e) => {
const onMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const coords = getEventCoords(e);
const dx = coords.clientX - startX;
const dy = coords.clientY - startY;
let newLeft = startLeft + dx;
let newTop = startTop + dy;
@@ -8943,16 +9061,21 @@ class ClaudemanApp {
panel.style.top = newTop + 'px';
};
const onMouseUp = () => {
const onEnd = () => {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
};
// Remove existing listeners before adding new ones
header.removeEventListener('mousedown', header._dragHandler);
header._dragHandler = onMouseDown;
header.addEventListener('mousedown', onMouseDown);
header.removeEventListener('touchstart', header._touchDragHandler);
header._dragHandler = onStart;
header._touchDragHandler = onStart;
header.addEventListener('mousedown', onStart);
header.addEventListener('touchstart', onStart, { passive: false });
}
// ========== Subagents Panel Detach & Drag ==========
@@ -8994,28 +9117,32 @@ class ClaudemanApp {
let isDragging = false;
let startX, startY, startLeft, startTop;
const onMouseDown = (e) => {
const onStart = (e) => {
// Only drag from header, not from buttons
if (e.target.closest('button')) return;
if (!panel.classList.contains('detached')) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const coords = getEventCoords(e);
startX = coords.clientX;
startY = coords.clientY;
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
e.preventDefault();
};
const onMouseMove = (e) => {
const onMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const coords = getEventCoords(e);
const dx = coords.clientX - startX;
const dy = coords.clientY - startY;
let newLeft = startLeft + dx;
let newTop = startTop + dy;
@@ -9029,16 +9156,21 @@ class ClaudemanApp {
panel.style.top = newTop + 'px';
};
const onMouseUp = () => {
const onEnd = () => {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
};
// Remove existing listeners before adding new ones
header.removeEventListener('mousedown', header._dragHandler);
header._dragHandler = onMouseDown;
header.addEventListener('mousedown', onMouseDown);
header.removeEventListener('touchstart', header._touchDragHandler);
header._dragHandler = onStart;
header._touchDragHandler = onStart;
header.addEventListener('mousedown', onStart);
header.addEventListener('touchstart', onStart, { passive: false });
}
renderTaskPanel() {
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="Claude Code session manager with web interface">
<meta name="theme-color" content="#0a0a0a">
<title>Claudeman</title>
+349
View File
@@ -26,6 +26,15 @@
--session-blue: #3b82f6;
--session-purple: #a855f7;
--session-pink: #ec4899;
/* Safe area insets for notched devices (iPhone X+, Dynamic Island) */
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
/* Touch target minimum (iOS Human Interface Guidelines) */
--touch-target-min: 44px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -7776,3 +7785,343 @@ kbd {
.advanced-options-content {
padding-left: 0.5rem;
}
/* ============================================================================
Mobile & Touch Device Optimizations
============================================================================ */
/* Touch-friendly sizing for touch devices */
.touch-device .btn-toolbar {
min-height: var(--touch-target-min);
min-width: var(--touch-target-min);
}
.touch-device .btn-icon-header,
.touch-device .btn-icon-sm,
.touch-device .btn-icon-only {
min-height: var(--touch-target-min);
min-width: var(--touch-target-min);
}
.touch-device .session-tab {
min-height: var(--touch-target-min);
padding: 0.5rem 0.75rem;
}
/* Always show close/gear buttons on touch devices (no hover) */
.touch-device .session-tab .tab-close,
.touch-device .session-tab .tab-gear {
opacity: 1;
width: auto;
padding: 0.2rem 0.4rem;
}
.touch-device .tab-count-btn {
min-height: var(--touch-target-min);
min-width: var(--touch-target-min);
}
/* ============================================================================
Tablet Breakpoint (430px - 768px)
============================================================================ */
@media (max-width: 768px) and (min-width: 430px) {
/* Safe area padding for iOS devices */
.ios-device .app {
padding-left: var(--safe-area-left);
padding-right: var(--safe-area-right);
}
/* Increase header height for better touch targets */
.header {
min-height: 48px;
padding: 0.5rem 0.75rem;
}
/* Stack panels vertically */
.monitor-panel,
.subagents-panel {
width: 100%;
max-width: 100%;
left: 0;
right: 0;
border-radius: 8px 8px 0 0;
}
/* Larger toolbar buttons */
.btn-toolbar {
padding: 0.5rem 1rem;
font-size: 0.8rem;
}
/* Session tabs horizontal scroll */
.session-tabs {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.session-tabs::-webkit-scrollbar {
display: none;
}
}
/* ============================================================================
Phone Breakpoint (<430px)
============================================================================ */
@media (max-width: 430px) {
/* Safe area padding for iOS devices */
.ios-device .app {
padding-top: var(--safe-area-top);
padding-left: var(--safe-area-left);
padding-right: var(--safe-area-right);
}
/* Hide header brand on phones */
.header-brand {
display: none;
}
/* Hide font controls on phones */
.header-font-controls {
display: none;
}
/* Hide system stats on phones */
.header-system-stats {
display: none;
}
/* Compact header */
.header {
min-height: 48px;
padding: 0.4rem;
gap: 0.25rem;
}
.header-right {
padding-left: 0.5rem;
gap: 0.25rem;
}
/* Full-width session tabs with horizontal scroll */
.session-tabs {
flex: 1;
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
max-height: 56px;
}
.session-tabs::-webkit-scrollbar {
display: none;
}
.session-tab {
flex-shrink: 0;
min-height: var(--touch-target-min);
padding: 0.5rem 0.75rem;
}
/* Simplified toolbar */
.toolbar {
height: auto;
min-height: var(--toolbar-height);
padding: 0.5rem;
padding-bottom: calc(0.5rem + var(--safe-area-bottom));
flex-wrap: wrap;
gap: 0.5rem;
}
/* Hide secondary toolbar groups */
.toolbar-center {
display: none;
}
.toolbar-left .toolbar-group:not(:first-child) {
display: none;
}
/* Full-width primary toolbar group */
.toolbar-left {
flex: 1;
justify-content: center;
}
.toolbar-left .toolbar-group:first-child {
flex: 1;
justify-content: space-around;
}
/* Larger toolbar buttons for touch */
.btn-toolbar {
min-height: var(--touch-target-min);
padding: 0.5rem 1rem;
font-size: 0.85rem;
}
.btn-toolbar.btn-claude {
flex: 1;
justify-content: center;
}
/* Full-width panels */
.monitor-panel,
.subagents-panel {
width: 100%;
max-width: 100%;
left: 0;
right: 0;
border-radius: 8px 8px 0 0;
}
/* Full-screen modals on phones */
.modal-content {
width: 100%;
max-width: 100%;
height: 100%;
max-height: 100%;
border-radius: 0;
margin: 0;
}
/* Modal safe area padding */
.ios-device .modal-content {
padding-top: var(--safe-area-top);
padding-bottom: var(--safe-area-bottom);
}
.modal-header {
padding: 1rem;
}
.modal-body {
padding: 1rem;
flex: 1;
overflow-y: auto;
}
.modal-footer,
.form-actions {
padding: 1rem;
padding-bottom: calc(1rem + var(--safe-area-bottom));
}
/* Terminal container safe area */
.ios-device .terminal-container {
padding-bottom: var(--safe-area-bottom);
}
/* Welcome overlay adjustments */
.welcome-content {
padding: 1.5rem 1rem;
}
.welcome-title {
font-size: 1.4rem;
}
.welcome-actions {
flex-direction: column;
gap: 0.75rem;
}
.welcome-btn {
width: 100%;
justify-content: center;
min-height: var(--touch-target-min);
}
/* Ralph wizard responsive */
.modal-wizard {
display: flex;
flex-direction: column;
}
.wizard-progress {
padding: 0.75rem;
}
.wizard-step-label {
display: none;
}
/* Tab count group responsive */
.tab-count-group {
margin: 0;
}
.tab-count-btn {
min-width: var(--touch-target-min);
min-height: var(--touch-target-min);
}
/* Case select responsive */
.case-select-group {
flex: 0 0 auto;
}
.toolbar-select {
font-size: 0.8rem;
padding: 0.5rem 1.5rem 0.5rem 0.75rem;
}
}
/* ============================================================================
Virtual Keyboard Handling
============================================================================ */
.keyboard-visible .toolbar {
display: none;
}
.keyboard-visible .monitor-panel,
.keyboard-visible .subagents-panel {
display: none;
}
/* Ensure terminal fills space when keyboard is visible */
.keyboard-visible .main {
height: 100%;
}
/* ============================================================================
iOS Safari Specific Fixes
============================================================================ */
.ios-device.safari-browser {
/* Prevent pull-to-refresh */
overscroll-behavior: none;
}
.ios-device.safari-browser .terminal-container {
/* Prevent rubber-banding in terminal */
-webkit-overflow-scrolling: auto;
}
/* ============================================================================
Hover State Fallbacks for Touch
============================================================================ */
@media (hover: none) and (pointer: coarse) {
/* Remove hover-dependent UI patterns */
.session-tab .tab-close,
.session-tab .tab-gear {
opacity: 1;
width: auto;
padding: 0.2rem 0.4rem;
}
/* Simplify button hover states */
.btn-toolbar:hover {
transform: none;
}
.btn-icon-header:hover {
transform: none;
}
/* Show dropdown on tap instead of hover */
.subagent-dropdown-trigger:active + .subagent-dropdown-menu,
.subagent-dropdown-trigger:focus + .subagent-dropdown-menu {
display: block;
}
}