From 34dbc09bd838c5afbed3b99f54a5c1fcb001af9a Mon Sep 17 00:00:00 2001 From: Ashish Singh Bora Date: Sat, 12 Sep 2026 12:32:06 +0530 Subject: [PATCH 1/5] fix(ui): eliminate race condition between refreshDevices and selectDevice --- .../flashcore/ui/FlasherViewModel.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt index fbeb066..41737cf 100644 --- a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt +++ b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt @@ -207,21 +207,21 @@ class FlasherViewModel( } } - val currentSelected = _uiState.value.selectedDevice - val selected = when { - autoSelectDevice != null -> diskList.find { it.device == autoSelectDevice } ?: diskList.firstOrNull() - currentSelected != null -> diskList.find { it.serialNumber == currentSelected.serialNumber } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() - else -> diskList.firstOrNull() - } + _uiState.update { current -> + val currentSelected = current.selectedDevice + val selected = when { + autoSelectDevice != null -> diskList.find { it.device == autoSelectDevice } ?: diskList.firstOrNull() + currentSelected != null -> diskList.find { it.serialNumber == currentSelected.serialNumber } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() + else -> diskList.firstOrNull() + } - if (diskList.isNotEmpty() && selected != null) { - fsm.transition(FlasherEvent.DevicesUpdated(diskList, selected)) - } else { - fsm.transition(FlasherEvent.ResetToIdle) - } + if (diskList.isNotEmpty() && selected != null) { + fsm.transition(FlasherEvent.DevicesUpdated(diskList, selected)) + } else if (selected == null && currentSelected == null) { + fsm.transition(FlasherEvent.ResetToIdle) + } - _uiState.update { - it.copy( + current.copy( connectedDevices = diskList, selectedDevice = selected, fsmState = fsm.state.value @@ -617,7 +617,7 @@ class FlasherViewModel( FlashForegroundService.complete("Flash Cancelled", "Operation cancelled by user", false) } - override fun onCleared() { + public override fun onCleared() { super.onCleared() try { getApplication().unregisterReceiver(usbReceiver) From 31503f4c062d7491b41217d75cbe56b4b60fe81d Mon Sep 17 00:00:00 2001 From: Ashish Singh Bora Date: Sat, 12 Sep 2026 12:36:27 +0530 Subject: [PATCH 2/5] refactor(ui): keep _uiState.update pure and restore onCleared visibility --- .../flashcore/ui/FlasherViewModel.kt | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt index 41737cf..af19514 100644 --- a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt +++ b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt @@ -207,23 +207,31 @@ class FlasherViewModel( } } - _uiState.update { current -> - val currentSelected = current.selectedDevice - val selected = when { - autoSelectDevice != null -> diskList.find { it.device == autoSelectDevice } ?: diskList.firstOrNull() - currentSelected != null -> diskList.find { it.serialNumber == currentSelected.serialNumber } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() - else -> diskList.firstOrNull() - } + val currentSelected = _uiState.value.selectedDevice + val selected = when { + autoSelectDevice != null -> diskList.find { it.device == autoSelectDevice } ?: diskList.firstOrNull() + currentSelected != null -> diskList.find { it.serialNumber == currentSelected.serialNumber } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() + else -> diskList.firstOrNull() + } - if (diskList.isNotEmpty() && selected != null) { - fsm.transition(FlasherEvent.DevicesUpdated(diskList, selected)) - } else if (selected == null && currentSelected == null) { - fsm.transition(FlasherEvent.ResetToIdle) + if (diskList.isNotEmpty() && selected != null) { + fsm.transition(FlasherEvent.DevicesUpdated(diskList, selected)) + } else if (currentSelected?.device != null) { + fsm.transition(FlasherEvent.ResetToIdle) + } + + _uiState.update { current -> + val activeSelected = when { + current.selectedDevice != null && current.selectedDevice?.device == null -> current.selectedDevice + current.selectedDevice != null && diskList.any { it.serialNumber == current.selectedDevice?.serialNumber } -> { + diskList.firstOrNull { it.serialNumber == current.selectedDevice?.serialNumber } ?: current.selectedDevice + } + else -> selected } current.copy( connectedDevices = diskList, - selectedDevice = selected, + selectedDevice = activeSelected, fsmState = fsm.state.value ) } @@ -617,7 +625,7 @@ class FlasherViewModel( FlashForegroundService.complete("Flash Cancelled", "Operation cancelled by user", false) } - public override fun onCleared() { + override fun onCleared() { super.onCleared() try { getApplication().unregisterReceiver(usbReceiver) From bd13dbe0ec20b28765c89ad30f66b1f134e4bfa4 Mon Sep 17 00:00:00 2001 From: Ashish Singh Bora Date: Sat, 12 Sep 2026 12:38:14 +0530 Subject: [PATCH 3/5] fix(ui): use isSameDevice helper to avoid matching null serials and handle device IDs --- .../flashcore/ui/FlasherViewModel.kt | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt index af19514..2bb1242 100644 --- a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt +++ b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt @@ -207,10 +207,22 @@ class FlasherViewModel( } } + fun isSameDevice(a: UsbDiskInfo, b: UsbDiskInfo): Boolean { + val serialA = a.serialNumber + val serialB = b.serialNumber + return if (!serialA.isNullOrBlank() && !serialB.isNullOrBlank()) { + serialA == serialB + } else if (a.device != null && b.device != null) { + a.device.deviceId == b.device.deviceId || a.device.deviceName == b.device.deviceName + } else { + a == b + } + } + val currentSelected = _uiState.value.selectedDevice val selected = when { autoSelectDevice != null -> diskList.find { it.device == autoSelectDevice } ?: diskList.firstOrNull() - currentSelected != null -> diskList.find { it.serialNumber == currentSelected.serialNumber } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() + currentSelected != null -> diskList.find { isSameDevice(it, currentSelected) } ?: if (currentSelected.device == null) currentSelected else diskList.firstOrNull() else -> diskList.firstOrNull() } @@ -221,11 +233,10 @@ class FlasherViewModel( } _uiState.update { current -> + val currentDev = current.selectedDevice val activeSelected = when { - current.selectedDevice != null && current.selectedDevice?.device == null -> current.selectedDevice - current.selectedDevice != null && diskList.any { it.serialNumber == current.selectedDevice?.serialNumber } -> { - diskList.firstOrNull { it.serialNumber == current.selectedDevice?.serialNumber } ?: current.selectedDevice - } + currentDev != null && currentDev.device == null -> currentDev + currentDev != null -> diskList.firstOrNull { isSameDevice(it, currentDev) } ?: selected else -> selected } From a88bd25d4cb19a49551b3824f234df98d16298ab Mon Sep 17 00:00:00 2001 From: Ashish Singh Bora Date: Sat, 12 Sep 2026 12:40:36 +0530 Subject: [PATCH 4/5] fix(ui): require strict conjunction of deviceId and deviceName in isSameDevice --- .../ashishsinghbora/flashcore/ui/FlasherViewModel.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt index 2bb1242..b60a6aa 100644 --- a/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt +++ b/app/src/main/java/com/ashishsinghbora/flashcore/ui/FlasherViewModel.kt @@ -210,12 +210,12 @@ class FlasherViewModel( fun isSameDevice(a: UsbDiskInfo, b: UsbDiskInfo): Boolean { val serialA = a.serialNumber val serialB = b.serialNumber - return if (!serialA.isNullOrBlank() && !serialB.isNullOrBlank()) { - serialA == serialB - } else if (a.device != null && b.device != null) { - a.device.deviceId == b.device.deviceId || a.device.deviceName == b.device.deviceName - } else { - a == b + return when { + !serialA.isNullOrBlank() && !serialB.isNullOrBlank() -> serialA == serialB + a.device != null && b.device != null -> { + a.device.deviceId == b.device.deviceId && a.device.deviceName == b.device.deviceName + } + else -> a == b } } From f56ff09deb1e150f4b07da2295e11525255086c0 Mon Sep 17 00:00:00 2001 From: Ashish Singh Bora Date: Sat, 12 Sep 2026 12:42:47 +0530 Subject: [PATCH 5/5] fix(ai): configure gemini-3.6-flash and harden retry backoff for PR reviewer and auditor --- .github/scripts/pr_reviewer.py | 14 +++++++++----- .github/scripts/repo_auditor.py | 12 ++++++++---- .github/workflows/ai-pr-review.yml | 1 + .github/workflows/autonomous-audit.yml | 1 + 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/scripts/pr_reviewer.py b/.github/scripts/pr_reviewer.py index 527d1cf..2fd043d 100755 --- a/.github/scripts/pr_reviewer.py +++ b/.github/scripts/pr_reviewer.py @@ -164,14 +164,16 @@ def generate_review(client: "genai.Client", primary_model: str, pr_info: dict, d Please execute your autonomous review following the specified criteria and formatting. """ - candidate_models = [primary_model] - for m in ["gemini-3.6-flash", "gemini-2.5-flash", "gemini-2.0-flash"]: + candidate_models = [] + if primary_model and primary_model not in candidate_models: + candidate_models.append(primary_model) + for m in ["gemini-3.6-flash", "gemini-2.5-flash"]: if m not in candidate_models: candidate_models.append(m) last_error = None for model_name in candidate_models: - for attempt in range(3): + for attempt in range(4): try: print(f"[*] Calling Gemini model '{model_name}' (attempt {attempt + 1})...") response = client.models.generate_content( @@ -190,10 +192,12 @@ def generate_review(client: "genai.Client", primary_model: str, pr_info: dict, d last_error = e print(f"[!] Model '{model_name}' (attempt {attempt + 1}) failed: {err_str}") if "503" in err_str or "UNAVAILABLE" in err_str or "429" in err_str: - time.sleep(2 * (attempt + 1)) + sleep_time = (2 ** attempt) * 2 + 1 + print(f"[*] Retrying in {sleep_time}s due to service load...") + time.sleep(sleep_time) continue else: - break # Non-transient error (e.g. 404), move to next candidate model + break # Non-transient error (e.g. 404), try next model return f"Error executing Gemini review: {last_error}", primary_model diff --git a/.github/scripts/repo_auditor.py b/.github/scripts/repo_auditor.py index 05e9701..aa2ba4d 100755 --- a/.github/scripts/repo_auditor.py +++ b/.github/scripts/repo_auditor.py @@ -165,14 +165,16 @@ def run_gemini_audit(client: "genai.Client", primary_model: str, subsystem_key: Please execute your thorough security, concurrency, memory, correctness, and architectural review now. """ - candidate_models = [primary_model] - for m in ["gemini-3.6-flash", "gemini-2.5-flash", "gemini-2.0-flash"]: + candidate_models = [] + if primary_model and primary_model not in candidate_models: + candidate_models.append(primary_model) + for m in ["gemini-3.6-flash", "gemini-2.5-flash"]: if m not in candidate_models: candidate_models.append(m) last_error = None for model_name in candidate_models: - for attempt in range(3): + for attempt in range(4): try: response = client.models.generate_content( model=model_name, @@ -190,7 +192,9 @@ def run_gemini_audit(client: "genai.Client", primary_model: str, subsystem_key: last_error = e print(f"[!] Model '{model_name}' (attempt {attempt + 1}) audit failed for subsystem {subsystem_key}: {err_str}") if "503" in err_str or "UNAVAILABLE" in err_str or "429" in err_str: - time.sleep(2 * (attempt + 1)) + sleep_time = (2 ** attempt) * 2 + 1 + print(f"[*] Retrying subsystem {subsystem_key} in {sleep_time}s due to service load...") + time.sleep(sleep_time) continue else: break diff --git a/.github/workflows/ai-pr-review.yml b/.github/workflows/ai-pr-review.yml index 00fb229..861c8cd 100644 --- a/.github/workflows/ai-pr-review.yml +++ b/.github/workflows/ai-pr-review.yml @@ -42,5 +42,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_EVENT_PATH: ${{ github.event_path }} + GEMINI_MODEL: "gemini-3.6-flash" run: | python3 .github/scripts/pr_reviewer.py diff --git a/.github/workflows/autonomous-audit.yml b/.github/workflows/autonomous-audit.yml index 08fdb30..17da869 100644 --- a/.github/workflows/autonomous-audit.yml +++ b/.github/workflows/autonomous-audit.yml @@ -55,6 +55,7 @@ jobs: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} + GEMINI_MODEL: "gemini-3.6-flash" INPUT_SUBSYSTEMS: ${{ github.event.inputs.subsystems || 'all' }} INPUT_CREATE_ISSUE: ${{ github.event.inputs.create_issue }} INPUT_FAIL_ON_CRITICAL: ${{ github.event.inputs.fail_on_critical }}