fix(launcher): button state hygiene across all 3 modes

Fix 1 (stale error_detail):
  open_file_error_detail is now cleared at the start of every mode's
  branch (native, onsite, default) alongside open_file_clicked=true.
  Previously it was cleared mid-way through the native flow (Step 1),
  so a stale error from a previous failed run could flash briefly.

Fix 2 (stuck 'Opening...' during post_script sleep):
  After the Step 4 open call returns (run_cmd or open_local_file_v2),
  update the status message immediately to 'App opened — running setup...'
  so the button doesn't appear frozen for the full post_delay_ms wait.

Fix 3 (safety valve for hanging native calls):
  A 60s setTimeout is registered at the start of the native branch.
  If any native IPC call hangs indefinitely and the existing per-path
  reset timeouts never fire, this force-resets open_file_clicked after
  60s. All normal paths complete within ~8s so this only triggers on
  true hangs. ERR_NETWORK_CHANGED cannot re-enter the download path
  because the open_file_clicked guard blocks re-entry.
This commit is contained in:
Scott Idem
2026-05-12 13:37:11 -04:00
parent 2c1e9d294e
commit f8fe4ac5a2

View File

@@ -277,6 +277,17 @@ async function handle_open_file() {
open_file_clicked = true;
open_file_status = 'checking_cache';
open_file_status_message = 'Checking local cache...';
open_file_error_detail = null; // Fix 1: clear stale error from any previous attempt
// Fix 2: safety valve — if a native call hangs and no path resets the button,
// force-release it after 60s. All normal paths reset within ~8s so this is last resort.
setTimeout(() => {
if (open_file_clicked) {
open_file_clicked = false;
open_file_status = 'error';
open_file_status_message = 'Timed out — please try again';
}
}, 60_000);
const exists = await native.check_hash_file_cache({
cache_root,
@@ -364,6 +375,14 @@ async function handle_open_file() {
}
}
// Fix 3: update the status message as soon as the open call returns so "Opening..." doesn't
// appear stuck for the entire post_script sleep. OS has the request; we're just waiting now.
if (open_ok) {
open_file_status_message = profile.post_script
? `${profile.app} opened — running setup...`
: `${profile.app} opened`;
}
// --- Step 5: Wait for app to load before running post-script ---
// Only delay if there is actually a post_script to run — no point waiting for nothing.
if (open_ok && profile.post_script) {
@@ -426,6 +445,7 @@ async function handle_open_file() {
open_file_clicked = true;
open_file_status = 'downloading_onsite';
open_file_status_message = 'Downloading (Onsite Mode)...';
open_file_error_detail = null;
let filename = event_file_obj.filename;
if (
@@ -457,6 +477,7 @@ async function handle_open_file() {
open_file_clicked = true;
open_file_status = 'downloading_default';
open_file_status_message = 'Downloading...';
open_file_error_detail = null;
const dl_promise = api.get_object({
api_cfg: $ae_api,