Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/task-workers.test.js && node test/git-clone.test.js",
"test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/task-workers.test.js && node test/git-clone.test.js && node test/responsive-ui.test.js",
"postinstall": "node scripts/install-hooks.js",
"release": "node scripts/release.js",
"electron:dev": "electron .",
Expand Down
24 changes: 21 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2110,7 +2110,7 @@
.lang-sel option { background: var(--s2); }

/* ─── Directory browser ─── */
.dir-modal { width: 520px; }
.dir-modal { width: 520px; max-width: calc(100vw - 24px); }
.dir-path-bar { padding: 8px 16px; border-bottom: 1px solid var(--border); background: var(--bg); display: flex; align-items: center; gap: 6px; }
.dir-path-input { flex: 1; background: var(--s2); border: 1px solid var(--border); color: var(--accent2); padding: 6px 10px; border-radius: 6px; font-size: 12px; font-family: var(--font-mono); outline: none; min-width: 0; }
.dir-path-input:focus { border-color: var(--accent); }
Expand Down Expand Up @@ -2167,7 +2167,7 @@
.rh-item .cfg { opacity:0; background:none; border:none; color:var(--muted); cursor:pointer; font-size:13px; padding:3px 6px; border-radius:4px; transition:opacity .12s, color .12s; flex-shrink:0; }
.rh-item:hover .cfg { opacity:.6; }
.rh-item .cfg:hover { opacity:1 !important; color:var(--accent2); background:rgba(124,106,239,.1); }
.ssh-host-modal { width:480px; }
.ssh-host-modal { width: 480px; max-width: calc(100vw - 24px); }
.ssh-host-form { padding:16px; display:flex; flex-direction:column; gap:12px; }
.ssh-host-form label { font-size:12px; color:var(--muted); display:block; margin-bottom:4px; }
.ssh-host-form input { width:100%; background:var(--s2); border:1px solid var(--border); color:var(--text); padding:6px 10px; border-radius:6px; font-size:12px; font-family:inherit; outline:none; box-sizing:border-box; transition:border .12s; }
Expand Down Expand Up @@ -2657,8 +2657,24 @@
.chain-progress-card { max-width: calc(100vw - 40px); }
.cd-btn-ok, .cd-btn-cancel { min-height: 44px; padding: 10px 20px; }
.fpv-modal { width: 100vw !important; max-width: 100vw !important; height: 100vh !important; height: 100dvh !important; max-height: 100vh !important; max-height: 100dvh !important; border-radius: 0 !important; }
.modal { max-height: 92vh; max-height: 92dvh; }
.modal {
max-width: calc(100vw - 24px) !important;
min-width: 0 !important;
max-height: 92vh;
max-height: 92dvh;
box-sizing: border-box !important;
}
.modal .modal-body textarea { min-height: 200px; }
#cfgModal .modal {
min-width: 0 !important;
width: calc(100vw - 16px) !important;
max-width: calc(100vw - 16px) !important;
resize: none !important;
}
.dir-modal, .ssh-host-modal, .cmd-modal {
max-width: calc(100vw - 24px) !important;
width: 100% !important;
}
}
@media (max-width: 480px) {
.mob-chip { padding: 8px 14px; font-size: 13px; }
Expand Down Expand Up @@ -15225,6 +15241,8 @@ <h2 id="dlgModalTitle" data-i18n="dlg.title">Delegate to Agent</h2>
if (ddWrap && ddName) {
if (activeProj) {
ddName.textContent = activeProj.name;
ddName.title = activeProj.name;
$i('projDdBtn')?.setAttribute('title', activeProj.name);
ddWrap.classList.add('visible');
} else {
ddWrap.classList.remove('visible');
Expand Down
23 changes: 16 additions & 7 deletions public/kanban.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
.card-desc{ font-size:12px; color:var(--muted); line-height:1.5; margin-bottom:8px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; word-break:break-word; }
.card-foot{ display:flex; align-items:center; gap:5px; flex-wrap:wrap; }
.badge{ font-size:11px; padding:2px 7px; border-radius:4px; white-space:nowrap; font-weight:500; }
.badge-muted{ background:var(--s3); color:var(--muted); }
.badge-muted{ background:var(--s3); color:var(--muted); max-width:140px; overflow:hidden; text-overflow:ellipsis; }
.badge-accent{ background:var(--abg); color:var(--accent2); border:1px solid rgba(124,106,239,.2); max-width:140px; overflow:hidden; text-overflow:ellipsis; }
.badge-green{ background:rgba(63,185,80,.12); color:var(--green); border:1px solid rgba(63,185,80,.2); }
.badge-orange{ background:rgba(229,164,53,.12); color:var(--orange); border:1px solid rgba(229,164,53,.2); }
Expand All @@ -188,7 +188,7 @@
.sched-info{ display:flex; align-items:center; gap:7px; background:rgba(56,139,253,.07); border:1px solid rgba(56,139,253,.2); color:#58a6ff; border-radius:6px; padding:8px 12px; font-size:12px; line-height:1.4; }
.sched-info strong{ color:#79b8ff; font-weight:600; }
.card-time{ font-size:11px; color:var(--muted); margin-left:auto; }
.card-actions{ display:none; gap:3px; margin-left:auto; }
.card-actions{ display:none; gap:3px; margin-left:auto; flex-wrap:wrap; }
.card:hover .card-actions{ display:flex; }
.card:hover .card-time{ display:none; }
.cbtn{ background:none; border:none; cursor:pointer; color:var(--muted); padding:2px 5px; border-radius:var(--r-sm); font-size:12px; line-height:1; transition:color .1s,background .1s; }
Expand Down Expand Up @@ -287,12 +287,13 @@
.hb { padding: 5px 8px; min-height: 40px; }
.nav-sw-btn { padding: 4px 8px; font-size: 12px; }
.nav-sw-btn span { display: none; }
.proj-dd-btn { max-width: 120px; padding: 5px 10px; font-size: 12px; }
.proj-dd-btn { max-width: 170px; min-width: 0; padding: 5px 10px; font-size: 12px; }
.board { padding: 10px 8px 16px; gap: 8px; }
.col { width: 240px; }
.modal { max-width: 95vw; max-height: 92vh; max-height: 92dvh; }
.ov { padding: 8px; }
.modal { max-width: calc(100vw - 16px) !important; max-height: 92vh; max-height: 92dvh; box-sizing: border-box !important; }
.grid2 { grid-template-columns: 1fr; }
.card-actions { opacity: 1; }
.card-actions { opacity: 1; flex-wrap: wrap; justify-content: flex-end; gap: 2px; }
/* Prevent iOS auto-zoom on focus (requires ≥ 16px) */
.inp, .sel, .proj-dd-search input { font-size: 16px; }
}
Expand All @@ -302,6 +303,8 @@
.col { scroll-snap-align: start; }
.hdr-meta { display: none; }
.kb-progress { display: none; }
.ov { padding: 0; align-items: flex-end; }
.modal { max-width: 100vw !important; width: 100% !important; border-radius: 14px 14px 0 0; max-height: 94vh; max-height: 94dvh; }
}

/* ─── Chain (group) card ─── */
Expand Down Expand Up @@ -915,10 +918,16 @@
if (ddWrap && ddName) {
if (curWorkdir) {
const proj = projects.find(p => p.workdir === curWorkdir || p.id === curWorkdir);
ddName.textContent = proj?.name || curWorkdir;
const fullName = proj?.name || curWorkdir;
ddName.textContent = fullName;
ddName.title = fullName;
$i('projDdBtn')?.setAttribute('title', fullName);
ddWrap.classList.add('visible');
} else {
ddName.textContent = t('proj.all').replace('— ', '');
const allText = t('proj.all').replace('— ', '');
ddName.textContent = allText;
ddName.title = allText;
$i('projDdBtn')?.setAttribute('title', allText);
ddWrap.classList.add('visible');
}
}
Expand Down
51 changes: 51 additions & 0 deletions test/responsive-ui.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');

let failed = 0;
function check(name, fn) {
try { fn(); console.log(` ✓ ${name}`); }
catch (e) { failed++; console.log(` ✗ ${name}\n ${e.message}`); }
}

console.log('Responsive UI improvements (#106)');

const idxPath = path.join(__dirname, '..', 'public', 'index.html');
const idxSrc = fs.readFileSync(idxPath, 'utf8');

const kbPath = path.join(__dirname, '..', 'public', 'kanban.html');
const kbSrc = fs.readFileSync(kbPath, 'utf8');

check('index.html constrains dir-modal and ssh-host-modal with max-width', () => {
assert.ok(/\.dir-modal\s*\{[^}]*max-width:\s*calc\(100vw\s*-\s*24px\)/.test(idxSrc), 'dir-modal lacks max-width constraint');
assert.ok(/\.ssh-host-modal\s*\{[^}]*max-width:\s*calc\(100vw\s*-\s*24px\)/.test(idxSrc), 'ssh-host-modal lacks max-width constraint');
});

check('index.html mobile media query overrides modal and cfgModal constraints', () => {
const mediaBlock = idxSrc.slice(idxSrc.indexOf('@media (max-width: 800px)'));
assert.ok(/\.modal\s*\{[\s\S]*?max-width:\s*calc\(100vw\s*-\s*24px\)\s*!important/.test(mediaBlock), 'missing modal max-width in mobile media query');
assert.ok(/#cfgModal\s+\.modal\s*\{[\s\S]*?min-width:\s*0\s*!important/.test(mediaBlock), 'missing cfgModal min-width:0 override in mobile media query');
});

check('kanban.html constrains modal width on tablet and mobile', () => {
const media800 = kbSrc.slice(kbSrc.indexOf('@media (max-width: 800px)'));
assert.ok(/\.modal\s*\{[\s\S]*?max-width:\s*calc\(100vw\s*-\s*16px\)\s*!important/.test(media800), 'missing modal max-width in kanban 800px query');

const media480 = kbSrc.slice(kbSrc.indexOf('@media (max-width: 480px)'));
assert.ok(/\.modal\s*\{[\s\S]*?max-width:\s*100vw\s*!important/.test(media480), 'missing bottom-sheet modal in kanban 480px query');
assert.ok(/\.ov\s*\{[\s\S]*?align-items:\s*flex-end/.test(media480), 'missing bottom-sheet alignment in kanban 480px query');
});

check('kanban.html card actions and badges wrap gracefully on narrow viewports', () => {
assert.ok(/\.card-actions\{[^}]*flex-wrap:\s*wrap/.test(kbSrc), 'card-actions should wrap');
assert.ok(/\.badge-muted\{[^}]*text-overflow:\s*ellipsis/.test(kbSrc), 'badge-muted should truncate with ellipsis');
});

check('project dropdown buttons set title attribute for full name tooltips', () => {
assert.ok(/ddName\.title\s*=\s*activeProj\.name/.test(idxSrc), 'index.html should set title on project name');
assert.ok(/ddName\.title\s*=\s*fullName/.test(kbSrc), 'kanban.html should set title on project name');
});

if (failed) { console.log(`\n${failed} test(s) failed`); process.exit(1); }
console.log('\nAll responsive-ui tests passed');
Loading