Fix Medium severity performance findings (PERF-17 to PERF-21) (#325)

* Fix Medium severity performance findings (PERF-17 to PERF-21)

This commit addresses the following performance findings:
- PERF-17: Implemented a message queue in `HermesWsClient` using `ConcurrentLinkedQueue`. If the WebSocket is disconnected, messages are queued and flushed upon a successful connection (`onOpen`).
- PERF-18: Added exponential backoff to `safeApiCall` for 5xx and IO exceptions, reducing the load on a struggling server.
- PERF-19: Optimized Kanban task filtering by performing `groupBy` upfront inside a `remember` block and passing item keys to `LazyColumn`, avoiding O(C×T) calculation per recomposition.
- PERF-20: Updated `getLogs` API in `HermesApiService` to accept `lines` and `limit` query params to truncate logs early, avoiding potential JSON parsing OOMs with large log files.
- PERF-21: Optimized drawer entry generation in `Navigation.kt` by grouping navigation items inside a `remember` block.

Fixes #282

* Fix Medium severity performance findings (PERF-17 to PERF-21)

This commit addresses the following performance findings:
- PERF-17: Implemented a message queue in `HermesWsClient` using `ConcurrentLinkedQueue`. If the WebSocket is disconnected, messages are queued and flushed upon a successful connection (`onOpen`).
- PERF-18: Added exponential backoff to `safeApiCall` for 5xx and IO exceptions, reducing the load on a struggling server.
- PERF-19: Optimized Kanban task filtering by performing `groupBy` upfront inside a `remember` block and passing item keys to `LazyColumn`, avoiding O(C×T) calculation per recomposition.
- PERF-20: Updated `getLogs` API in `HermesApiService` to accept `lines` and `limit` query params to truncate logs early, avoiding potential JSON parsing OOMs with large log files.
- PERF-21: Optimized drawer entry generation in `Navigation.kt` by grouping navigation items inside a `remember` block.

Fixes #282

* Fix Medium severity performance findings (PERF-17 to PERF-21)

This commit addresses the following performance findings:
- PERF-17: Implemented a message queue in `HermesWsClient` using `ConcurrentLinkedQueue`. If the WebSocket is disconnected, messages are queued and flushed upon a successful connection (`onOpen`).
- PERF-18: Added exponential backoff to `safeApiCall` for 5xx and IO exceptions, reducing the load on a struggling server.
- PERF-19: Optimized Kanban task filtering by performing `groupBy` upfront inside a `remember` block and passing item keys to `LazyColumn`, avoiding O(C×T) calculation per recomposition.
- PERF-20: Updated `getLogs` API in `HermesApiService` to accept `lines` and `limit` query params to truncate logs early, avoiding potential JSON parsing OOMs with large log files.
- PERF-21: Optimized drawer entry generation in `Navigation.kt` by grouping navigation items inside a `remember` block.

Fixes #282

* fix: resolve ktlint formatting issues in KanbanScreen.kt and Navigation.kt

* fix: fix ktlint import ordering in HermesWsClient.kt

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-06-23 22:23:26 +03:00
1 parent 42fcf2a14b
commit d616c56176
7 files changed
+71 -42

No files matched your search

@@ -28,6 +28,7 @@ import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
@@ -64,6 +65,7 @@ object HermesWsClient {
private val requestId = AtomicInteger(0)
private val connected = AtomicBoolean(false)
private val intentionalClose = AtomicBoolean(false)
private val messageQueue = ConcurrentLinkedQueue<String>()
@Volatile
private var webSocket: WebSocket? = null
@@ -170,7 +172,13 @@ object HermesWsClient {
// prompts (PromptSubmit params include the `text` field) — never
// stream to logcat in release builds.
if (BuildConfig.DEBUG) Log.d(TAG, "→ $json")
webSocket?.send(json) ?: Log.w(TAG, "send() called while disconnected")
val ws = webSocket
if (ws != null && connected.get()) {
ws.send(json)
} else {
Log.d(TAG, "WS disconnected — queuing message")
messageQueue.add(json)
}
return id
}
@@ -243,17 +251,25 @@ object HermesWsClient {
private class WsListenerImpl : WebSocketListener() {
override fun onOpen(
ws: WebSocket,
webSocket: WebSocket,
response: Response,
) {
Log.i(TAG, "WebSocket opened")
connected.set(true)
_connectionStatus.value = ConnectionStatus.CONNECTED
currentBackoff = INITIAL_BACKOFF_MS
while (messageQueue.isNotEmpty()) {
val msg = messageQueue.poll()
if (msg != null) {
if (BuildConfig.DEBUG) Log.d(TAG, "→ (queued) $msg")
webSocket.send(msg)
}
}
}
override fun onMessage(
ws: WebSocket,
webSocket: WebSocket,
text: String,
) {
// B2 (Jun 18 2026, kanban t_8884db16): incoming WS text contains
@@ -264,16 +280,16 @@ object HermesWsClient {
}
override fun onClosing(
ws: WebSocket,
webSocket: WebSocket,
code: Int,
reason: String,
) {
Log.d(TAG, "WebSocket closing: $code $reason")
ws.close(code, reason)
webSocket.close(code, reason)
}
override fun onClosed(
ws: WebSocket,
webSocket: WebSocket,
code: Int,
reason: String,
) {
@@ -284,7 +300,7 @@ object HermesWsClient {
}
override fun onFailure(
ws: WebSocket,
webSocket: WebSocket,
t: Throwable,
response: Response?,
) {