mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat: local echo for mobile input — instant keystroke feedback over high-latency connections
DOM overlay captures keystrokes locally and sends edited text + Enter as separate events (matching Ink's sendCommand pattern). Block cursor matches Claude Code's native cursor style. Enabled by default on mobile touch devices. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ When user says "COM":
|
|||||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
|
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version 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`
|
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.1545 (must match `package.json` for npm publish)
|
**Version**: 0.1546 (must match `package.json` for npm publish)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1545",
|
"version": "0.1546",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+27
-9
@@ -861,9 +861,9 @@ class LocalEchoOverlay {
|
|||||||
// Opaque background so overlay covers old buffer text underneath.
|
// Opaque background so overlay covers old buffer text underneath.
|
||||||
// xterm.js applies bg via internal rendering, not CSS — use theme option.
|
// xterm.js applies bg via internal rendering, not CSS — use theme option.
|
||||||
this.overlay.style.backgroundColor = terminal.options?.theme?.background || '#0d0d0d';
|
this.overlay.style.backgroundColor = terminal.options?.theme?.background || '#0d0d0d';
|
||||||
// Solid bar cursor (no blink — matches Claude Code's input cursor)
|
// Block cursor matching Claude Code's idle prompt cursor
|
||||||
this._cursor = document.createElement('span');
|
this._cursor = document.createElement('span');
|
||||||
this._cursor.style.cssText = 'display:inline-block;width:1.5px;height:1em;vertical-align:text-bottom;background:currentColor;margin-left:1px';
|
this._cursor.style.cssText = 'display:inline-block;vertical-align:top';
|
||||||
screen.appendChild(this.overlay);
|
screen.appendChild(this.overlay);
|
||||||
this.pendingText = '';
|
this.pendingText = '';
|
||||||
this._storageKey = 'claudeman_local_echo_pending';
|
this._storageKey = 'claudeman_local_echo_pending';
|
||||||
@@ -955,8 +955,14 @@ class LocalEchoOverlay {
|
|||||||
this.overlay.style.lineHeight = cellH + 'px';
|
this.overlay.style.lineHeight = cellH + 'px';
|
||||||
// Extend to right edge to cover old buffer text with opaque bg
|
// Extend to right edge to cover old buffer text with opaque bg
|
||||||
this.overlay.style.width = (this.terminal.cols * cellW - leftPx) + 'px';
|
this.overlay.style.width = (this.terminal.cols * cellW - leftPx) + 'px';
|
||||||
|
// Cover any subpixel seam above overlay with matching box-shadow
|
||||||
|
const bg = this.overlay.style.backgroundColor;
|
||||||
|
this.overlay.style.boxShadow = `0 -1px 0 0 ${bg}`;
|
||||||
this.overlay.textContent = this.pendingText;
|
this.overlay.textContent = this.pendingText;
|
||||||
// Solid cursor after text
|
// Block cursor: full cell width & height, matching Claude Code's cursor color
|
||||||
|
this._cursor.style.width = cellW + 'px';
|
||||||
|
this._cursor.style.height = cellH + 'px';
|
||||||
|
this._cursor.style.backgroundColor = this.terminal.options?.theme?.cursor || '#e0e0e0';
|
||||||
this.overlay.appendChild(this._cursor);
|
this.overlay.appendChild(this._cursor);
|
||||||
this.overlay.style.display = '';
|
this.overlay.style.display = '';
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1957,16 +1963,27 @@ class ClaudemanApp {
|
|||||||
this._localEchoOverlay?.removeChar();
|
this._localEchoOverlay?.removeChar();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data === '\r' || data === '\n' || data === '\r\n') {
|
if (/^[\r\n]+$/.test(data)) {
|
||||||
// Enter: send the final edited text + Enter to server
|
// Enter: send text first, then \r separately (like sendCommand does for /init).
|
||||||
|
// Ink needs them as distinct events — sending "text\r" in one shot causes newline.
|
||||||
const text = this._localEchoOverlay?.pendingText || '';
|
const text = this._localEchoOverlay?.pendingText || '';
|
||||||
this._localEchoOverlay?.clear();
|
this._localEchoOverlay?.clear();
|
||||||
this._pendingInput += text + '\r';
|
|
||||||
if (this._inputFlushTimeout) {
|
if (this._inputFlushTimeout) {
|
||||||
clearTimeout(this._inputFlushTimeout);
|
clearTimeout(this._inputFlushTimeout);
|
||||||
this._inputFlushTimeout = null;
|
this._inputFlushTimeout = null;
|
||||||
}
|
}
|
||||||
flushInput();
|
if (text) {
|
||||||
|
this._pendingInput += text;
|
||||||
|
flushInput();
|
||||||
|
setTimeout(() => {
|
||||||
|
this._pendingInput += '\r';
|
||||||
|
flushInput();
|
||||||
|
}, 120);
|
||||||
|
} else {
|
||||||
|
// No text in overlay — just send Enter
|
||||||
|
this._pendingInput += '\r';
|
||||||
|
flushInput();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.charCodeAt(0) < 32 || data.length > 1) {
|
if (data.charCodeAt(0) < 32 || data.length > 1) {
|
||||||
@@ -2274,7 +2291,8 @@ class ClaudemanApp {
|
|||||||
_updateLocalEchoState() {
|
_updateLocalEchoState() {
|
||||||
const settings = this.loadAppSettingsFromStorage();
|
const settings = this.loadAppSettingsFromStorage();
|
||||||
const session = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
const session = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
||||||
const shouldEnable = !!(settings.localEchoEnabled && session);
|
const echoEnabled = settings.localEchoEnabled ?? (MobileDetection.isTouchDevice() || false);
|
||||||
|
const shouldEnable = !!(echoEnabled && session);
|
||||||
if (this._localEchoEnabled && !shouldEnable) {
|
if (this._localEchoEnabled && !shouldEnable) {
|
||||||
this._localEchoOverlay?.clear();
|
this._localEchoOverlay?.clear();
|
||||||
}
|
}
|
||||||
@@ -9709,7 +9727,7 @@ class ClaudemanApp {
|
|||||||
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? defaults.subagentTrackingEnabled ?? true;
|
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? defaults.subagentTrackingEnabled ?? true;
|
||||||
document.getElementById('appSettingsSubagentActiveTabOnly').checked = settings.subagentActiveTabOnly ?? defaults.subagentActiveTabOnly ?? true;
|
document.getElementById('appSettingsSubagentActiveTabOnly').checked = settings.subagentActiveTabOnly ?? defaults.subagentActiveTabOnly ?? true;
|
||||||
document.getElementById('appSettingsImageWatcherEnabled').checked = settings.imageWatcherEnabled ?? defaults.imageWatcherEnabled ?? false;
|
document.getElementById('appSettingsImageWatcherEnabled').checked = settings.imageWatcherEnabled ?? defaults.imageWatcherEnabled ?? false;
|
||||||
document.getElementById('appSettingsLocalEcho').checked = settings.localEchoEnabled ?? false;
|
document.getElementById('appSettingsLocalEcho').checked = settings.localEchoEnabled ?? (MobileDetection.isTouchDevice() || false);
|
||||||
document.getElementById('appSettingsTabTwoRows').checked = settings.tabTwoRows ?? defaults.tabTwoRows ?? false;
|
document.getElementById('appSettingsTabTwoRows').checked = settings.tabTwoRows ?? defaults.tabTwoRows ?? false;
|
||||||
// Claude CLI settings
|
// Claude CLI settings
|
||||||
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
||||||
|
|||||||
Reference in New Issue
Block a user