mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-28 11:59:33 +01:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5078f5251d | ||
|
|
196af8fba7 | ||
|
|
a9b22b86a4 | ||
|
|
28cace5858 | ||
|
|
0a594b61bd |
No files matched your search
@@ -1,5 +1,25 @@
|
|||||||
# aicodeman
|
# aicodeman
|
||||||
|
|
||||||
|
## 0.5.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: allow bracket characters in model validation regex so models like opus[1m] (1M context window) are accepted instead of silently dropped. Quote the model flag value in tmux spawn commands to prevent bash glob expansion of bracket patterns.
|
||||||
|
|
||||||
|
docs: update macOS launchd instructions to use `launchctl bootstrap` instead of deprecated `load`. Clean up README install and service sections.
|
||||||
|
|
||||||
|
## 0.5.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Mobile keyboard accessory bar: add configurable "Extended Keyboard Bar" setting (Settings > Display > Input) that toggles between simple mode (up/down arrows, /init, /clear, /compact, paste, dismiss) and extended mode (adds left/right arrows, Tab, Shift+Tab, Ctrl+O, Alt+Enter, Esc). Default is simple mode. Setting is device-specific (not synced to server).
|
||||||
|
|
||||||
|
Restyle dismiss button: muted steel-blue tone, fills remaining bar space via flex, larger tap target. Arrow buttons now blue.
|
||||||
|
|
||||||
|
Fix paste overlay visibility on mobile: dialog repositioned to top of screen (15vh from top) so the virtual keyboard doesn't cover it. Textarea enlarged for better usability.
|
||||||
|
|
||||||
|
(Also includes all v0.5.8 changes: case reorder/delete, XSS sanitization, auto-attach PTY on restart, mobile keyboard buttons, macOS installer fixes, terminal flicker fix, state store collision fix.)
|
||||||
|
|
||||||
## 0.5.8
|
## 0.5.8
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ When user says "COM":
|
|||||||
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
|
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
|
||||||
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
|
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
|
||||||
|
|
||||||
**Version**: 0.5.8 (must match `package.json`)
|
**Version**: 0.5.10 (must match `package.json`)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|||||||
@@ -30,24 +30,6 @@ curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | b
|
|||||||
|
|
||||||
This installs Node.js and tmux if missing, clones Codeman to `~/.codeman/app`, and builds it.
|
This installs Node.js and tmux if missing, clones Codeman to `~/.codeman/app`, and builds it.
|
||||||
|
|
||||||
**Install from a fork or specific branch:**
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://raw.githubusercontent.com/<user>/Codeman/<branch>/install.sh | \
|
|
||||||
CODEMAN_REPO_URL=https://github.com/<user>/Codeman.git \
|
|
||||||
CODEMAN_BRANCH=<branch> bash
|
|
||||||
```
|
|
||||||
|
|
||||||
The installer supports these environment variables:
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `CODEMAN_REPO_URL` | upstream Codeman | Custom git repository URL |
|
|
||||||
| `CODEMAN_BRANCH` | `master` | Git branch to install |
|
|
||||||
| `CODEMAN_INSTALL_DIR` | `~/.codeman/app` | Custom install directory |
|
|
||||||
| `CODEMAN_SKIP_SYSTEMD` | `0` | Skip systemd service setup prompt |
|
|
||||||
| `CODEMAN_NODE_VERSION` | `22` | Node.js major version to install |
|
|
||||||
| `CODEMAN_NONINTERACTIVE` | `0` | Skip all prompts (for CI/automation) |
|
|
||||||
|
|
||||||
You'll need at least one AI coding CLI installed — [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [OpenCode](https://opencode.ai) (or both). After install:
|
You'll need at least one AI coding CLI installed — [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [OpenCode](https://opencode.ai) (or both). After install:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -60,12 +42,53 @@ codeman web
|
|||||||
|
|
||||||
**Linux (systemd):**
|
**Linux (systemd):**
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.config/systemd/user && printf '[Unit]\nDescription=Codeman Web Server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=%s %s/dist/index.js web\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n' "$(which node)" "$HOME/.codeman/app" > ~/.config/systemd/user/codeman-web.service && systemctl --user daemon-reload && systemctl --user enable --now codeman-web && loginctl enable-linger $USER
|
mkdir -p ~/.config/systemd/user
|
||||||
|
cat > ~/.config/systemd/user/codeman-web.service << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Codeman Web Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=$(which node) $HOME/.codeman/app/dist/index.js web
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now codeman-web
|
||||||
|
loginctl enable-linger $USER
|
||||||
```
|
```
|
||||||
|
|
||||||
**macOS (launchd):**
|
**macOS (launchd):**
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/Library/LaunchAgents && printf '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.codeman.web</string><key>ProgramArguments</key><array><string>%s</string><string>%s/dist/index.js</string><string>web</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>/tmp/codeman.log</string><key>StandardErrorPath</key><string>/tmp/codeman.log</string></dict></plist>\n' "$(which node)" "$HOME/.codeman/app" > ~/Library/LaunchAgents/com.codeman.web.plist && launchctl load ~/Library/LaunchAgents/com.codeman.web.plist
|
mkdir -p ~/Library/LaunchAgents
|
||||||
|
cat > ~/Library/LaunchAgents/com.codeman.web.plist << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||||
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.codeman.web</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>$(which node)</string>
|
||||||
|
<string>$HOME/.codeman/app/dist/index.js</string>
|
||||||
|
<string>web</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key><true/>
|
||||||
|
<key>KeepAlive</key><true/>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/tmp/codeman.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/tmp/codeman.log</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.codeman.web.plist
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aicodeman",
|
"name": "aicodeman",
|
||||||
"version": "0.5.8",
|
"version": "0.5.10",
|
||||||
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+2
-2
@@ -185,8 +185,8 @@ function buildSpawnCommand(options: {
|
|||||||
}): string {
|
}): string {
|
||||||
if (options.mode === 'claude') {
|
if (options.mode === 'claude') {
|
||||||
// Validate model to prevent command injection
|
// Validate model to prevent command injection
|
||||||
const safeModel = options.model && /^[a-zA-Z0-9._-]+$/.test(options.model) ? options.model : undefined;
|
const safeModel = options.model && /^[a-zA-Z0-9._\-[\]]+$/.test(options.model) ? options.model : undefined;
|
||||||
const modelFlag = safeModel ? ` --model ${safeModel}` : '';
|
const modelFlag = safeModel ? ` --model "${safeModel}"` : '';
|
||||||
// Use --resume to restore a previous conversation, otherwise --session-id for new sessions.
|
// Use --resume to restore a previous conversation, otherwise --session-id for new sessions.
|
||||||
// Wrap --resume in a fallback: if it exits non-zero (session not found, corrupt, etc.),
|
// Wrap --resume in a fallback: if it exits non-zero (session not found, corrupt, etc.),
|
||||||
// fall back to a new session with --session-id so the pane doesn't die.
|
// fall back to a new session with --session-id so the pane doesn't die.
|
||||||
|
|||||||
@@ -523,6 +523,9 @@ class CodemanApp {
|
|||||||
SwipeHandler.init();
|
SwipeHandler.init();
|
||||||
VoiceInput.init();
|
VoiceInput.init();
|
||||||
KeyboardAccessoryBar.init();
|
KeyboardAccessoryBar.init();
|
||||||
|
// Apply keyboard bar mode from settings
|
||||||
|
const _kbSettings = this.loadAppSettingsFromStorage();
|
||||||
|
if (_kbSettings.extendedKeyboardBar) KeyboardAccessoryBar.setMode('extended');
|
||||||
this.applyHeaderVisibilitySettings();
|
this.applyHeaderVisibilitySettings();
|
||||||
this.applyTabWrapSettings();
|
this.applyTabWrapSettings();
|
||||||
this.applyMonitorVisibility();
|
this.applyMonitorVisibility();
|
||||||
|
|||||||
@@ -892,6 +892,17 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-item settings-item-multiline" title="Show additional buttons (Tab, Shift+Tab, Ctrl+O, Esc, Alt+Enter, left/right arrows) in the mobile keyboard accessory bar.">
|
||||||
|
<div class="settings-item-text">
|
||||||
|
<span class="settings-item-label">Extended Keyboard Bar</span>
|
||||||
|
<span class="settings-item-desc">Extra keys: Tab, Esc, arrows, Ctrl+O</span>
|
||||||
|
</div>
|
||||||
|
<label class="switch switch-sm">
|
||||||
|
<input type="checkbox" id="appSettingsExtendedKeyboardBar">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Header Displays Section -->
|
<!-- Header Displays Section -->
|
||||||
<div class="settings-section-header">Header Displays</div>
|
<div class="settings-section-header">Header Displays</div>
|
||||||
<div class="settings-item" title="Show A-/A+ font size buttons in header">
|
<div class="settings-item" title="Show A-/A+ font size buttons in header">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Defines two exports:
|
* Defines two exports:
|
||||||
*
|
*
|
||||||
* - KeyboardAccessoryBar (singleton object) — Quick action buttons shown above the virtual
|
* - KeyboardAccessoryBar (singleton object) — Quick action buttons shown above the virtual
|
||||||
* keyboard on mobile: Esc, arrow up/down, Tab, Shift+Tab, Ctrl+O, /init, /clear, /compact, paste, and dismiss.
|
* keyboard on mobile: arrow up/down, /init, /clear, /compact, paste, and dismiss.
|
||||||
* Destructive actions (/clear, /compact) require double-tap confirmation (2s amber state).
|
* Destructive actions (/clear, /compact) require double-tap confirmation (2s amber state).
|
||||||
* Commands are sent as text + Enter separately for Ink compatibility.
|
* Commands are sent as text + Enter separately for Ink compatibility.
|
||||||
* Only initializes on touch devices (MobileDetection.isTouchDevice guard).
|
* Only initializes on touch devices (MobileDetection.isTouchDevice guard).
|
||||||
@@ -33,16 +33,37 @@
|
|||||||
*/
|
*/
|
||||||
const KeyboardAccessoryBar = {
|
const KeyboardAccessoryBar = {
|
||||||
element: null,
|
element: null,
|
||||||
|
_mode: 'simple', // 'simple' or 'extended'
|
||||||
|
|
||||||
/** Create and inject the accessory bar */
|
/** HTML for simple mode: arrows, commands, paste, dismiss */
|
||||||
init() {
|
_simpleButtons: `
|
||||||
// Only on mobile
|
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
|
||||||
if (!MobileDetection.isTouchDevice()) return;
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M5 15l7-7 7 7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-down" title="Arrow down">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="accessory-btn" data-action="init" title="/init">/init</button>
|
||||||
|
<button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
|
||||||
|
<button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
|
||||||
|
<button class="accessory-btn" data-action="paste" title="Paste from clipboard">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
|
||||||
|
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
||||||
|
<path d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>`,
|
||||||
|
|
||||||
// Create accessory bar element
|
/** HTML for extended mode: all keys including arrows, Tab, Esc, etc. */
|
||||||
this.element = document.createElement('div');
|
_extendedButtons: `
|
||||||
this.element.className = 'keyboard-accessory-bar';
|
|
||||||
this.element.innerHTML = `
|
|
||||||
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
|
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
<path d="M5 15l7-7 7 7"/>
|
<path d="M5 15l7-7 7 7"/>
|
||||||
@@ -77,12 +98,21 @@ const KeyboardAccessoryBar = {
|
|||||||
<button class="accessory-btn" data-action="init" title="/init">/init</button>
|
<button class="accessory-btn" data-action="init" title="/init">/init</button>
|
||||||
<button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
|
<button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
|
||||||
<button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
|
<button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
|
||||||
<button class="accessory-btn accessory-btn-arrow" data-action="dismiss" title="Dismiss keyboard">
|
<button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
||||||
<path d="M19 9l-7 7-7-7"/>
|
<path d="M19 9l-7 7-7-7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>`,
|
||||||
`;
|
|
||||||
|
/** Create and inject the accessory bar */
|
||||||
|
init() {
|
||||||
|
// Only on mobile
|
||||||
|
if (!MobileDetection.isTouchDevice()) return;
|
||||||
|
|
||||||
|
// Create accessory bar element
|
||||||
|
this.element = document.createElement('div');
|
||||||
|
this.element.className = 'keyboard-accessory-bar';
|
||||||
|
this.element.innerHTML = this._simpleButtons;
|
||||||
|
|
||||||
// Add click handlers — preventDefault stops event from reaching terminal
|
// Add click handlers — preventDefault stops event from reaching terminal
|
||||||
this.element.addEventListener('click', (e) => {
|
this.element.addEventListener('click', (e) => {
|
||||||
@@ -95,7 +125,8 @@ const KeyboardAccessoryBar = {
|
|||||||
this.handleAction(action, btn);
|
this.handleAction(action, btn);
|
||||||
|
|
||||||
// Refocus terminal so keyboard stays open (tap blurs terminal → keyboard dismisses → toolbar shifts)
|
// Refocus terminal so keyboard stays open (tap blurs terminal → keyboard dismisses → toolbar shifts)
|
||||||
if ((action === 'scroll-up' || action === 'scroll-down' || action === 'arrow-left' || action === 'arrow-right' || action === 'tab' || action === 'shift-tab' || action === 'ctrl-o' || action === 'opt-enter' || action === 'esc') ||
|
const refocusActions = new Set(['scroll-up', 'scroll-down', 'arrow-left', 'arrow-right', 'tab', 'shift-tab', 'ctrl-o', 'opt-enter', 'esc']);
|
||||||
|
if (refocusActions.has(action) ||
|
||||||
((action === 'clear' || action === 'compact') && this._confirmAction)) {
|
((action === 'clear' || action === 'compact') && this._confirmAction)) {
|
||||||
if (typeof app !== 'undefined' && app.terminal) {
|
if (typeof app !== 'undefined' && app.terminal) {
|
||||||
app.terminal.focus();
|
app.terminal.focus();
|
||||||
@@ -110,6 +141,14 @@ const KeyboardAccessoryBar = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Switch between 'simple' and 'extended' button layouts */
|
||||||
|
setMode(mode) {
|
||||||
|
if (mode === this._mode || !this.element) return;
|
||||||
|
this._mode = mode;
|
||||||
|
this.clearConfirm();
|
||||||
|
this.element.innerHTML = mode === 'extended' ? this._extendedButtons : this._simpleButtons;
|
||||||
|
},
|
||||||
|
|
||||||
_confirmTimer: null,
|
_confirmTimer: null,
|
||||||
_confirmAction: null,
|
_confirmAction: null,
|
||||||
|
|
||||||
|
|||||||
+51
-12
@@ -937,13 +937,33 @@ html.mobile-init .file-browser-panel {
|
|||||||
|
|
||||||
.accessory-btn-arrow {
|
.accessory-btn-arrow {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: #1e3a5f;
|
background: #2563eb;
|
||||||
border-color: rgba(59, 130, 246, 0.3);
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
color: #93c5fd;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accessory-btn-arrow:active {
|
.accessory-btn-arrow:active {
|
||||||
|
background: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss {
|
||||||
|
margin-left: auto;
|
||||||
|
flex: 1 1 0;
|
||||||
|
max-width: 100px;
|
||||||
|
padding: 10px 8px;
|
||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss:active {
|
||||||
|
background: #1d4ed8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Voice preview — positioned above accessory bar on mobile */
|
/* Voice preview — positioned above accessory bar on mobile */
|
||||||
@@ -975,9 +995,9 @@ html.mobile-init .file-browser-panel {
|
|||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: env(safe-area-inset-bottom, 12px);
|
padding-top: 15vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paste-dialog {
|
.paste-dialog {
|
||||||
@@ -987,13 +1007,12 @@ html.mobile-init .file-browser-panel {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
width: calc(100% - 24px);
|
width: calc(100% - 24px);
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.paste-textarea {
|
.paste-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 60px;
|
min-height: 80px;
|
||||||
max-height: 120px;
|
max-height: 200px;
|
||||||
background: var(--bg-primary, #0d0d14);
|
background: var(--bg-primary, #0d0d14);
|
||||||
color: var(--text-primary, #e0e0e0);
|
color: var(--text-primary, #e0e0e0);
|
||||||
border: 1px solid var(--border-color, #444);
|
border: 1px solid var(--border-color, #444);
|
||||||
@@ -2104,13 +2123,33 @@ html.mobile-init .file-browser-panel {
|
|||||||
|
|
||||||
.accessory-btn-arrow {
|
.accessory-btn-arrow {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: #1e3a5f;
|
background: #2563eb;
|
||||||
border-color: rgba(59, 130, 246, 0.3);
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
color: #93c5fd;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accessory-btn-arrow:active {
|
.accessory-btn-arrow:active {
|
||||||
background: #2563eb;
|
background: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss {
|
||||||
|
margin-left: auto;
|
||||||
|
flex: 1 1 0;
|
||||||
|
max-width: 80px;
|
||||||
|
padding: 10px 8px;
|
||||||
|
background: #334d6e;
|
||||||
|
border-color: rgba(100, 150, 200, 0.4);
|
||||||
|
color: #c0d4e8;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss:active {
|
||||||
|
background: #3d5f85;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
this.loadTunnelStatus();
|
this.loadTunnelStatus();
|
||||||
document.getElementById('appSettingsLocalEcho').checked = settings.localEchoEnabled ?? MobileDetection.isTouchDevice();
|
document.getElementById('appSettingsLocalEcho').checked = settings.localEchoEnabled ?? MobileDetection.isTouchDevice();
|
||||||
document.getElementById('appSettingsCjkInput').checked = settings.cjkInputEnabled ?? false;
|
document.getElementById('appSettingsCjkInput').checked = settings.cjkInputEnabled ?? false;
|
||||||
|
document.getElementById('appSettingsExtendedKeyboardBar').checked = settings.extendedKeyboardBar ?? 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');
|
||||||
@@ -1125,6 +1126,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
tunnelEnabled: document.getElementById('appSettingsTunnelEnabled').checked,
|
tunnelEnabled: document.getElementById('appSettingsTunnelEnabled').checked,
|
||||||
localEchoEnabled: document.getElementById('appSettingsLocalEcho').checked,
|
localEchoEnabled: document.getElementById('appSettingsLocalEcho').checked,
|
||||||
cjkInputEnabled: document.getElementById('appSettingsCjkInput').checked,
|
cjkInputEnabled: document.getElementById('appSettingsCjkInput').checked,
|
||||||
|
extendedKeyboardBar: document.getElementById('appSettingsExtendedKeyboardBar').checked,
|
||||||
tabTwoRows: document.getElementById('appSettingsTabTwoRows').checked,
|
tabTwoRows: document.getElementById('appSettingsTabTwoRows').checked,
|
||||||
// Claude CLI settings
|
// Claude CLI settings
|
||||||
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
||||||
@@ -1245,9 +1247,12 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
// Apply CJK input visibility immediately
|
// Apply CJK input visibility immediately
|
||||||
this._updateCjkInputState();
|
this._updateCjkInputState();
|
||||||
|
|
||||||
|
// Apply keyboard bar mode
|
||||||
|
KeyboardAccessoryBar.setMode(settings.extendedKeyboardBar ? 'extended' : 'simple');
|
||||||
|
|
||||||
// Save to server (includes notification prefs for cross-browser persistence)
|
// Save to server (includes notification prefs for cross-browser persistence)
|
||||||
// Strip device-specific keys — localEchoEnabled/cjkInputEnabled are per-platform
|
// Strip device-specific keys — localEchoEnabled/cjkInputEnabled are per-platform
|
||||||
const { localEchoEnabled: _leo, cjkInputEnabled: _cjk, ...serverSettings } = settings;
|
const { localEchoEnabled: _leo, cjkInputEnabled: _cjk, extendedKeyboardBar: _ekb, ...serverSettings } = settings;
|
||||||
try {
|
try {
|
||||||
await this._apiPut('/api/settings', { ...serverSettings, notificationPreferences: notifPrefsToSave, voiceSettings });
|
await this._apiPut('/api/settings', { ...serverSettings, notificationPreferences: notifPrefsToSave, voiceSettings });
|
||||||
|
|
||||||
@@ -1631,7 +1636,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
const displayKeys = new Set([
|
const displayKeys = new Set([
|
||||||
'showFontControls', 'showSystemStats', 'showTokenCount', 'showCost',
|
'showFontControls', 'showSystemStats', 'showTokenCount', 'showCost',
|
||||||
'showMonitor', 'showProjectInsights', 'showFileBrowser', 'showSubagents',
|
'showMonitor', 'showProjectInsights', 'showFileBrowser', 'showSubagents',
|
||||||
'subagentActiveTabOnly', 'tabTwoRows', 'localEchoEnabled', 'cjkInputEnabled',
|
'subagentActiveTabOnly', 'tabTwoRows', 'localEchoEnabled', 'cjkInputEnabled', 'extendedKeyboardBar',
|
||||||
]);
|
]);
|
||||||
// Merge settings: non-display keys always sync from server,
|
// Merge settings: non-display keys always sync from server,
|
||||||
// display keys only seed from server when localStorage has no value
|
// display keys only seed from server when localStorage has no value
|
||||||
|
|||||||
Reference in new issue
Block a user