fix(welcome): gate CLI welcome buttons on actual availability - #200
Conversation
Follow-up to the welcome-screen gating (Ark0N#200): the run-mode dropdown (gear menu next to Run) had the same problem — Claude/Opencode/Codex/ Gemini entries were always shown regardless of whether the CLI is actually installed, so picking one could spawn a session that immediately errors out. - Add _refreshRunModeAvailability() (session-ui.js), called each time the dropdown opens; hides entries whose /api/<cli>/status reports unavailable. - Shell is intentionally never gated (no external CLI dependency). Depends on isClaudeAvailable()/GET /api/claude/status, which don't exist on upstream/master yet — duplicated here from Ark0N#200 so this PR is self-contained and independently mergeable. Once Ark0N#200 lands this branch should be rebased onto master, which will collapse the duplicate cleanly.
Follow-up to the welcome-screen gating (Ark0N#200): the run-mode dropdown (gear menu next to Run) had the same problem — Claude/Opencode/Codex/ Gemini entries were always shown regardless of whether the CLI is actually installed, so picking one could spawn a session that immediately errors out. - Add _refreshRunModeAvailability() (session-ui.js), called each time the dropdown opens; hides entries whose /api/<cli>/status reports unavailable. - Shell is intentionally never gated (no external CLI dependency). Depends on isClaudeAvailable()/GET /api/claude/status, which don't exist on upstream/master yet — duplicated here from Ark0N#200 so this PR is self-contained and independently mergeable. Once Ark0N#200 lands this branch should be rebased onto master, which will collapse the duplicate cleanly.
…tunnel button - Remove the always-visible Cloudflare Tunnel welcome button and QR widget; offering it regardless of whether cloudflared is installed is a bad default. - Hide the "Run Gemini" welcome button by default and only show it when /api/gemini/status reports available:true, via new loadGeminiAvailability() called from showWelcome().
Extends the Gemini gating from bb7fb9e to the other welcome-screen buttons that had the same problem: shown unconditionally even when the underlying CLI isn't installed. - Add isClaudeAvailable() (claude-cli-resolver.ts) and GET /api/claude/status, mirroring the existing opencode/codex/gemini resolvers and status endpoints. - Opencode already had a working /api/opencode/status the welcome screen just wasn't checking; wire it up the same way. - Refactor loadGeminiAvailability() into a shared _loadCliAvailability(buttonId, statusUrl) helper instead of duplicating the fetch/try-catch three times. Run-mode dropdown entries (Opencode/Codex) are intentionally left unconditional here — follow-up PR.
7be7f55 to
3ea1ea2Compare…over antigravity Follow-up to #200 and #201, which gate the welcome buttons and the run-mode dropdown on whether the CLI is actually installed. Four corrections: 1. #200 also DELETED the Cloudflare Tunnel welcome button and the QR widget outright. Its rationale is right (offering a tunnel where cloudflared is not installed is a bad default) but the conclusion overshoots: the welcome QR is the whole scan-to-connect-from-your-phone flow, and deleting it left a large block of live tunnel code in settings-ui.js driving elements that no longer existed. Both are restored and the button is gated on cloudflared, which is what the stated rationale actually asks for. New cloudflared-resolver.ts mirrors the CLI resolvers, and TunnelManager now shares its search path so the button and the spawn can never disagree about where cloudflared lives. 2. Antigravity was missing from the run-mode gating, the one run mode LEAST likely to be installed. It slipped past because #201 predates it. Covered now, plus a static test that fails if a sixth mode reaches the dropdown without being gated, so the next one cannot slip the same way. 3. The per-surface fetches are replaced by the injected availability object already used for the Codex settings tab, so the codebase has one mechanism rather than two. The status routes buy nothing as a gating source: every resolver memoizes its PATH probe server-side, so a fetch is exactly as stale as an injected value while costing a round trip every time the dropdown opens and leaving the welcome buttons to flicker in after paint. The routes themselves stay, including the /api/claude/status that #200 adds. 4. Unknown availability now reads as AVAILABLE for run buttons. Both PRs hid the button on a failed fetch, so a blip left a working install with nothing to click; a genuinely missing CLI only ever produced an error toast. The Codex settings TAB keeps the opposite default, since hiding it costs nothing. The dropdown query is also scoped to the menu: `.run-mode-option` is the class the saved-dashboard and history rows use too, and a document-wide querySelector would have found whichever came first in the DOM. Fixes a latent environment-sensitivity in 816d900 while here: the index-title test asserted the template was untouched apart from the title, which held only on a machine with no codex installed. Verified end-to-end against a real server on an isolated instance+socket, with Playwright: gemini/codex hidden and claude/opencode/antigravity/shell shown, matching this host, tunnel button back, Codex settings tab still hidden, no console errors. Full test:ci sweep green (3902 tests). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ark0N
commented
Aug 4, 2026
Merged, thank you! Gating these is right, and adding Follow-up in The Cloudflare Tunnel button and QR widget are back, gated instead of deleted. Your rationale is right, offering a tunnel where Availability now comes from an injected object rather than a fetch per surface. Master already had this pattern from the Codex settings tab ( Unknown availability now reads as available. The Verified with Playwright against a real server: on this host Gemini is hidden and Claude/OpenCode/tunnel show, matching what is actually installed. Thanks again, both of these were worth doing. |
Summary
cloudflaredis installed is a bad default./api/claude/status,/api/opencode/status,/api/gemini/status) instead of showing them unconditionally and letting the click fail.isClaudeAvailable()/GET /api/claude/status, mirroring the existing opencode/codex/gemini CLI resolvers — Claude previously had no availability check at all._loadCliAvailability(buttonId, statusUrl)helper instead of duplicating the fetch/try-catch per tool.Run-mode dropdown entries (Opencode/Codex) are intentionally left unconditional here — same class of issue, but a separate follow-up to keep this PR small.
Test plan
tsc --noEmitcleaneslintclean on touched.tsfilescheck:frontend-syntaxandcheck:public-assetspass/api/claude/status,/api/opencode/status,/api/gemini/statusvia curl against a throwaway dev server