diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 1d390a0..424d07d 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -1402,8 +1402,10 @@ function showFabSessionInput() { /** * Create a new tmux session via POST /api/sessions. - * Shows a toast with the created session name, calls pollSessions() to refresh, - * and if ss.auto_open_created !== false, calls openSession(data.name) after 500ms. + * Shows a toast, then polls _currentSessions until the session name appears + * (or times out after 30s) before calling openSession — this handles commands + * that take time to create the tmux session (e.g. cloning repos, setup scripts). + * If auto_open_created is false in server settings, skips the auto-open. * @param {string} name - The session name to create. * @returns {Promise} */ @@ -1411,12 +1413,34 @@ async function createNewSession(name) { try { const res = await api('POST', '/api/sessions', { name }); const data = await res.json(); - showToast('Created: ' + (data.name || name)); - await pollSessions(); + const sessionName = data.name || name; + showToast('Creating session \'' + sessionName + '\'…'); + const ss = _serverSettings || {}; - if (ss.auto_open_created !== false) { - setTimeout(() => openSession(data.name || name), 500); + if (ss.auto_open_created === false) { + // Auto-open disabled — just do one refresh + await pollSessions(); + return; } + + // Poll until the session appears in _currentSessions (max 30s, every 2s) + var attempts = 0; + var maxAttempts = 15; + var pollForSession = setInterval(async function() { + attempts++; + await pollSessions(); + var found = _currentSessions && _currentSessions.find(function(s) { + return s.name === sessionName; + }); + if (found) { + clearInterval(pollForSession); + showToast('Session \'' + sessionName + '\' ready'); + openSession(sessionName); + } else if (attempts >= maxAttempts) { + clearInterval(pollForSession); + showToast('Session \'' + sessionName + '\' is taking longer than expected'); + } + }, 2000); } catch (err) { showToast(err.message || 'Failed to create session'); } diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 8e4f7a5..5cc069c 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2382,4 +2382,28 @@ test('app.js source uses 500ms debounce for template input and references new_se assert.ok(source.includes('new_session_template'), 'must reference new_session_template setting key'); }); +test('createNewSession polls for session before auto-opening (not immediate setTimeout openSession)', () => { + // The old behavior was: setTimeout(() => openSession(...), 500) immediately after POST. + // The new behavior must use a polling interval to wait for the session to appear in + // _currentSessions before calling openSession — so the immediate pattern must be gone. + const source = fs.readFileSync( + new URL('../app.js', import.meta.url), 'utf8' + ); + // Extract the createNewSession function body + const start = source.indexOf('async function createNewSession('); + assert.ok(start !== -1, 'createNewSession function must exist'); + // Find the end of the function (next function declaration at same indent level) + const snippet = source.slice(start, start + 2000); + // Must NOT contain the old immediate-open pattern inside createNewSession + assert.ok( + !snippet.includes("setTimeout(() => openSession"), + 'createNewSession must not use immediate setTimeout(() => openSession) — should poll instead' + ); + // Must contain a polling mechanism (setInterval) + assert.ok( + snippet.includes('setInterval'), + 'createNewSession must use setInterval to poll for session readiness' + ); +}); + diff --git a/muxplex/tests/test_frontend_js.py b/muxplex/tests/test_frontend_js.py index 7207640..8a13cf3 100644 --- a/muxplex/tests/test_frontend_js.py +++ b/muxplex/tests/test_frontend_js.py @@ -1721,8 +1721,8 @@ def test_create_new_session_auto_opens_session() -> None: ) -def test_create_new_session_auto_open_delay() -> None: - """createNewSession must use a 500ms delay before calling openSession.""" +def test_create_new_session_polls_before_open() -> None: + """createNewSession must poll for the session to appear before calling openSession (not immediate setTimeout).""" match = re.search( r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )", _JS, @@ -1730,7 +1730,14 @@ def test_create_new_session_auto_open_delay() -> None: ) assert match, "createNewSession function not found" body = match.group(1) - assert "500" in body, "createNewSession must use 500ms delay before openSession" + # Old immediate pattern must be gone + assert "setTimeout(() => openSession" not in body, ( + "createNewSession must not use immediate setTimeout(() => openSession) — should poll instead" + ) + # New polling pattern must be present + assert "setInterval" in body, ( + "createNewSession must use setInterval to poll for session readiness" + ) def test_bind_static_event_listeners_binds_new_session_btn() -> None: