Commit Graph
96 Commits
Author SHA1 Message Date
M57andM57 2d8d3ac57a fix(#366): skip redundant REST call on tab switch-back (#411)
Prevents the 404 error banner that briefly flashes when switching back
to the Chat tab. The WebSocket keeps the UI current, so the REST call
is only needed on initial session load.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-27 21:26:49 +03:00
M57andM57 a998b98aa2 feat: add read_terminal and computer_use custom parsers (#409)
* feat: add read_terminal and computer_use custom parsers

* fix: add missing else branch to if expression in computer_use multimodal text extraction

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-27 11:30:28 +03:00
M57andM57 a0954e2654 feat: clean display for all 37 Hermes tools in chat bubbles (#408)
* feat: clean todo tool display in chat bubbles

Add todo-specific parsing in parseToolOutput that extracts the
structured todos array and summary counts from the gateway payload,
building a clean one-line summary and formatted item list with
status markers ([x], [>], [~], [ ]).

Also register 'todo' in ToolSchemaRegistry with 📋 emoji.

Previously the todo tool fell back to generic rendering, showing the
raw todos/summary JSON as ugly key-value pairs in the expanded view.

* feat: clean fact_store tool display in chat bubbles

Add fact_store-specific parsing in parseToolOutput that handles all
action types: probe/search/reason/related/contradict (results[] array),
list (facts[] array), and simple CRUD status (add/update/remove).

Each fact is rendered as a compact block:
  #id  content text
  [category]  trust: 0.50  🏷️ tags

Summary line shows count + action context (e.g. '4 facts (probe: entity)').

Also register 'fact_store' in ToolSchemaRegistry with 🧠 emoji.

* feat: clean session_search tool display in chat bubbles

Add session_search-specific parsing in parseToolOutput that handles all 4
calling modes: discover (results[] with title/snippet/bookends), scroll
(messages[] with anchor marker), read (messages[] with session dump and
truncation flag), and browse (results[] with recent session metadata).

- Registered in ToolSchemaRegistry with U+1F50D (magnifying glass) emoji
- Summary adapts per mode: "U+1F50D 3 sessions: query" / "U+1F4DC N messages (scroll)" / "U+1F4D6 N messages" / "U+1F4CB N recent sessions"
- Results show date, source, title, model, matched role, snippet
- Messages show role emoji (U+1F464/U+1F916/U+1F527), id, tool name, content
- Anchor message gets U+2B05 marker in scroll mode
- Content capped at 300 chars, snippets at 200, newlines flattened
- Existing todo and fact_store formatting unchanged

* feat: add registry entries + custom parsing for all remaining tools

Registry entries (emoji+summaryArgKey) for 22 tools:
cronjob, memory, fact_feedback, process, skill_manage,
web_extract, browser_type, browser_cdp, browser_dialog,
vision_analyze, tts, video_generate, image_generate,
x_search, send_message, skills_list, skill_view,
tool_search, tool_describe, tool_call,
read_terminal, computer_use

Custom formatting blocks (10 tools):
memory (modes+status), cronjob (list+CRUD),
web_search (results with title/url/desc),
skills_list (skill cards), skill_view (content+files),
process (list+poll), x_search (answer+citations+degraded),
vision_analyze (description), web_extract (content),
tool_search (matches)

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-27 10:57:42 +03:00
M57 8f14aca647 perf: optimize startup, database, and ui rendering (#405)
* perf: optimize startup, database, and ui rendering

* fix(#405): address code review feedback

* test: wait for async prefs initialization to prevent coroutine leaks

* test: stub mockViewModel.streamingState in ChatScreenTest to prevent ClassCastException on Android
2026-06-27 01:38:44 +03:00
M57andM57 96e754dd3b feat(#216): dynamic command autocomplete from server catalog, remove stop button (#403)
* feat(#216): dynamic command autocomplete from server catalog, remove stop button

- Replace hardcoded 8-command autocomplete with dynamic catalog from server's commands.catalog RPC
- Show ALL commands including skills via commandCatalog.pairs
- Remove physical Stop button (use /stop or /interrupt slash commands instead)
- Remove onInterrupt callback from ChatInputBar

* fix(#216): hide /stop and /interrupt from slash suggestions, remove stale strings

- Filters /stop and /interrupt from autocomplete popup while keeping
  them functional when manually typed
- Drops 7 unused command description string resources (now from server
  catalog) plus unused interrupt string resource

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-27 00:46:09 +03:00
M57 85c4662537 feat: optimize network stack (#404)
* feat: optimize network stack and connection management

Refactor OkHttp connection pools, consolidate Gson serialization, add network-aware WebSocket reconnects, and deduplicate concurrent requests.

* test: update mapping assertions for AuthExpired

* refactor: address PR review feedback on network stack

Simplify health check, improve authenticator cookie matching robustness, resolve nit imports, and add unit tests.
2026-06-27 00:45:48 +03:00
M57andM57 979877eed3 fix: prevent typing effect replay on finalized messages (blink fix) (#398)
Root cause: when MessageComplete fires, the reducer atomically moves the
streaming message to the messages list and clears streamingMessage. The
messages list's typing effect condition (lastAnimatedMessageId check)
didn't account for the fact that this message was already rendered with
a typing effect in the streaming slot. The animation replayed from
scratch on the already-complete message, causing a visible blink.

Fix: add message.isStreaming to the condition. Messages finalized via
MessageComplete/MessageDone always have isStreaming=false, so they skip
the replay naturally. The streaming slot's StreamingBubbleWithTypingEffect
continues to handle the animation during active streaming.

Closes #374

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-26 17:58:26 +03:00
M57andM57 cac15147f4 feat(#396): native approval UI for dangerous command, memory, and skill write requests (#397)
* feat(ws): add ApprovalRequest event type and parser

* feat(model): add ApprovalInfo field to ChatMessage

* feat(viewmodel): handle approval.request events and respondToApproval

* feat(ui): render Approve/Deny buttons for approval request messages

* test: add approval request event and respond RPC tests

* fix: add ApprovalRequest branch to ChatWsEventReducer exhaustive when

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-26 15:53:11 +03:00
M57andM57 fafba3231b feat(#216): dynamic commands catalog + mid-turn slash command support (#395)
* feat(ws): add commands.catalog RPC constants and data models

* feat(catalog): fetch commands.catalog on WS gateway ready, store in state

* feat(commands): rewrite dispatcher to forward non-local commands via WS command.dispatch

* feat(input): always-visible send button + compact stop icon, allow / commands mid-turn

* test(commands): update tests for WS-based command dispatch

* fix: add missing CommandCatalog + JsonRpcError imports

* fix(test): assert errorMessage instead of messages for unknown command

* chore: remove unused CommandDispatchResponse data class

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-26 14:14:51 +03:00
M57andM57 ef4c14e550 fix(#373): compact full-width chat input bar (#394)
* fix(#373): compact full-width chat input bar

* fix(#373): replace contentPadding with Modifier.padding for backward API compat

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-26 13:04:01 +03:00
M57andM57 8d3d8a11e8 fix(#391): resolve 5 deprecation warnings (#392)
* fix(#391): resolve deprecation warnings

- Icon: Icons.Filled.ArrowForward → Icons.AutoMirrored.Filled.ArrowForward (KanbanScreen)
- Room: fallbackToDestructiveMigration() → fallbackToDestructiveMigration(false) (HermesDatabase)
- Tab: ScrollableTabRow → PrimaryScrollableTabRow (SettingsScreen)
- Clipboard: LocalClipboardManager → LocalClipboard + coroutine-scoped setText (ChatBubble: UserBubble, AssistantBubble, ToolBubble, CopyButton)
- ClickableText: pushStringAnnotation → pushLink(LinkAnnotation.Url), ClickableText → Text with native link handling (ChatBubble: RichText)

* fix(#392): replace deprecated setText with setClipEntry for new Clipboard API

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-26 05:25:01 +03:00
M57andM57 ecd223df64 fix(#370): respect device 12h/24h time format for chat timestamps (#375)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-25 22:23:44 +03:00
M57andM57 3399880621 perf(#281): fix PERF-13/14/15/16 — main-thread settings, 100Hz polling, leaked scope, WS disconnect (#359)
* perf(#281): fix PERF-13/14/15/16 — main-thread settings load, 100Hz polling, leaked scope, WS disconnect

* fix(#281): fix SettingsViewModelTest failures from async init dispatcher

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-25 20:39:52 +03:00
M57andM57 9297413b2a refactor(#285): extract section composables from SettingsScreen and ChatScreen (#358)
* refactor(#285): extract ConnectionSection from SettingsScreen

* refactor(#285): extract TestResultCard, SaveIndicator, SettingsActionButtons

* refactor(#285): extract BehaviorSection from SettingsScreen

* refactor(#285): extract AppearanceSection from SettingsScreen

* refactor(#285): extract ChatSection from SettingsScreen

* refactor(#285): extract NavBarSection from SettingsScreen

* refactor(#285): extract AboutSection from SettingsScreen

* refactor(#285): extract ChatLifecycleEffects from ChatScreen

* refactor(#285): extract ChatTopBanner from ChatScreen

* refactor(#285): extract ChatMessageList, ChatLoadingOverlay, ChatScrollToBottomFab

* fix(#285): use fully-qualified ButtonDefaults, BoxScope receiver for ChatScrollToBottomFab

* fix(#285): add BoxScope import

* fix(#285): pass isLoading as param instead of accessing StateFlow.value

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-25 20:36:21 +03:00
M57andM57 067f4500aa feat(#210): schema-driven tool bubble redesign with summary line and copy support (#356)
* feat(#210): add ToolSchemaRegistry with display config for each tool

* feat(#210): three-state ToolBubble with summary line, clean expanded view, copy support

* test(#210): add ToolBubbleParsingTest for schema-aware parsing

* fix(#210): CopyButton align modifier moved to call sites

* fix(#210): null-safety on durationSec in test

* fix(#210): backward compat for old session tool data formats

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-25 00:27:49 +03:00
M57andM57 1bd56270b3 fix(#206): read clarify.request question/choices from gateway (#355)
* fix(#206): read clarify.request question/choices from gateway

The Hermes TUI gateway sends clarify.request payloads with "question" and "choices" fields, but the Android parser was looking for "text" and "options" — so the text was always null and the dialog showed only the input box.

Fix reads "question"/"choices" first, falls back to "text"/"options" for backward compat.

Test added for the new field names; existing test for legacy names still passes.

* fix(#206): clear default text in clarify input field

The input box was pre-filled with the clarify question text because the initial state was set to clarify.text instead of empty string.

* fix: use JsonRpcResponse instead of undefined WsResponse in EventParserTest

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-24 23:54:47 +03:00
M57andM57 e7b2973c1c refactor(#288): split ChatViewModel god object (CODE-34) (#354)
* refactor(#288): extract ChatPersistenceRepository from ChatViewModel

* refactor(#288): extract ChatWsEventReducer pure state reducer

* refactor(#288): extract SlashCommandDispatcher

* refactor(#288): wire SlashCommandDispatcher into ViewModel

* refactor(#288): extract ChatSearchController

* refactor(#288): final cleanup — remove unused import, ktlint pass

* fix: compile & test fixes — GatewayReady handler, Gson import, MessageStart id, reducer args

* fix: align reducer handlers with old behavior — onMessageComplete onMessageDone onMessageStart

* chore: remove plan file from PR

* chore: remove unrelated Room schema from PR

* fix: add session filtering to reducer, fix onToolStart streaming state

* chore: remove schema file again

* fix: pass currentSessionId to reducer, align onMessageStart and onThinkingDelta state

* chore: remove schema

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-24 22:19:25 +03:00
M57andM57 f190b67ee6 fix(#290): code health — magic colors, flag params, silent catch (#353)
* fix(#290): use LocalHermesStatusColors instead of magic colors (CODE-33)

* fix(#290): replace boolean-flag params with sealed NavIcon class (CODE-37)

* fix(#290): resolve compilation errors — Color import + nullable NavIcon params

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-24 21:26:32 +03:00
M57andM57 42fcf2a14b fix: implement in-memory token caching to avoid redundant prefs decryptions (#320)
* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.
- Fixed merge conflicts and ensured code still tests well.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 22:19:35 +03:00
M57andM57 eb71ae3a46 Fix WebSocket events being silently dropped on buffer overflow (#313)
* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix ktlint formatting issues in HermesWsClient.kt

The formatting changes adhere to ktlint rules required by the CI pipeline:
- Removed a trailing comma in MutableSharedFlow initialization to avoid multiline expression wrapping errors.
- Sorted imports to be in lexicographical order.

These formatting changes fix the failing CI check.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 22:13:55 +03:00
M57andM57 2c6e437a5b Fix: Prevent AuthManager state pollution on connection verification (#348)
* Fix: Prevent AuthManager state pollution on connection verification

Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests.

Fixes #260

* Fix: Prevent AuthManager state pollution on connection verification

Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests.

Fixes #260

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 20:49:58 +03:00
M57 858f4dce1d fix: optimize autoscroll on message stream (resolves #274) (#314)
Previously, the auto-scroll LaunchedEffect was keyed to `state.streamingMessage?.content?.length`, which resulted in the scroll animation coroutine being cancelled and restarted for every single token received during message generation. This caused a heavy performance hit and interrupted smooth scrolling.

This commit updates the `LaunchedEffect` key to use `state.streamingMessage != null` instead. The scroll-to-bottom logic now appropriately initiates when a message *begins* streaming (or finishes thinking), while avoiding continuous job restarts as the payload streams in.

Resolves #274
2026-06-23 12:35:29 +03:00
M57 4faa3a156f fix(#283): apply minor performance optimizations for Chat (#315)
* fix(#283): apply minor performance optimizations for Chat

- Optimized `ChatScreen.kt` background gradient to use `remember` block correctly keyed by individual colors to avoid reallocation on recomposition.
- Removed redundant `luminance()` calculation caching inside `ChatBubble.kt` to preserve dynamic theme changes correctly while keeping code concise.
- Refactored `pendingRequests` timeout loop in `ChatViewModel.kt` to use `iterator.remove()` instead of generating a new filtered list.
- Converted `SideEffect` for `initialSessionId` assignment in `ChatScreen` to `remember` block to guarantee synchronous setup before WebSocket processes it.
- Initialized an explicit OkHttp `ConnectionPool` in `ApiClient` to survive client rebuilds.

These changes resolve Low-severity performance findings (PERF-22 to PERF-26) mentioned in issue #283 while ensuring maintainability and preserving dynamic theming.

* fix(#283): apply minor performance optimizations for Chat

- Optimized `ChatScreen.kt` background gradient to use `remember` block correctly keyed by individual colors to avoid reallocation on recomposition.
- Removed redundant `luminance()` calculation caching inside `ChatBubble.kt` to preserve dynamic theme changes correctly while keeping code concise.
- Refactored `pendingRequests` timeout loop in `ChatViewModel.kt` to use `iterator.remove()` instead of generating a new filtered list.
- Converted `SideEffect` for `initialSessionId` assignment in `ChatScreen` to `remember` block to guarantee synchronous setup before WebSocket processes it.
- Initialized an explicit OkHttp `ConnectionPool` in `ApiClient` to survive client rebuilds.

These changes resolve Low-severity performance findings (PERF-22 to PERF-26) mentioned in issue #283 while ensuring maintainability and preserving dynamic theming.
2026-06-23 12:34:43 +03:00
M57 fe46cb913c Fix: Conditionally apply animateContentSize on AssistantBubble (#317)
Resolves #272

Changes made:
- Modified the modifier chain in `AssistantBubble` inside `ChatBubble.kt`.
- Applied `animateContentSize()` conditionally via `let { if (!message.isStreaming) it.animateContentSize() else it }`.

Reason:
- Unconditionally using `animateContentSize()` on a streaming chat bubble caused Jetpack Compose to trigger a layout animation for every single token appended. This wasted GPU cycles and contributed to streaming jank.

Impact:
- Eliminates performance issues (PERF-04) during active message streaming in the chat screen. The layout animation will now only occur once when the final (non-streaming) message state is reached. No regressions expected as this modifier change applies exclusively to the `isStreaming` state toggle.
2026-06-23 12:30:20 +03:00
M57 8ad3efecf4 fix(#273): optimize gesture detector to prevent thrashing during streaming (#319)
- Replaced `.combinedClickable` with `.pointerInput` keyed on the message `id`.
- Removed `onClick = {}` interception, allowing tap events to naturally pass through to internal components like Markdown hyperlinks.
- Removed unused `@OptIn(ExperimentalFoundationApi::class)`.
2026-06-23 12:29:13 +03:00
M57 4d47db263f perf: Add stable keys to Lazy list items for performance (#323)
Added stable `key` parameters to `items(...)` and `itemsIndexed(...)` calls across 13 screens. This prevents Compose from treating all list mutations as full item replacements and eliminates unnecessary full list recompositions during state updates.

Modifications applied to:
- KeysScreen.kt, SharedComponents.kt, AchievementsScreen.kt, PluginsScreen.kt
- SessionsScreen.kt, CronJobsScreen.kt, McpServersScreen.kt, PairingScreen.kt
- ToolsetsScreen.kt, ChatScreen.kt, ModelScreen.kt, SkillsScreen.kt
- KanbanScreen.kt, ProfilesScreen.kt, WebhooksScreen.kt

Note: LogsScreen.kt was left without a key because logs are string lines that can be duplicated, and using `.hashCode()` would cause a "Key was already used" crash. Positional indexing is safer there.

Fixes #279
2026-06-23 12:17:45 +03:00
M57 d9c7471350 fix(#271): extract Gson instance in ChatViewModel (#324)
Extracted the `Gson()` instantiation in `ChatViewModel.kt` to a companion object to avoid creating a new `Gson` instance on every `ToolStart` and `ToolComplete` event handler call. This change reduces garbage collection pressure by caching reflection setup, type adapters, and factories.

Impact:
- Reduces memory allocations and CPU cycles during multi-step tool chains and streaming.
- Safely maintains existing parsing behavior while addressing the `PERF-03` review finding.

Closes #271
2026-06-23 12:12:56 +03:00
M57 586f463c9c Fix performance issue by caching typing effect settings in ChatUiState (#329)
* Fix performance issue by caching typing effect settings in ChatUiState

- Moved `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()` out of `ChatScreen.kt` recomposition loops.
- Stored `typingEffectEnabled` and `typingEffectDelayMs` in `ChatUiState`.
- Added `refreshSettings()` to `ChatViewModel` to fetch settings upon initialization and on returning to the chat screen (ON_START).
- Prevents expensive EncryptedSharedPreferences XML decryption occurring on every message per token stream update.

Fixes #269

* Fix performance issue by caching typing effect settings in ChatUiState

- Moved `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()` out of `ChatScreen.kt` recomposition loops.
- Stored `typingEffectEnabled` and `typingEffectDelayMs` in `ChatUiState`.
- Added `refreshSettings()` to `ChatViewModel` to fetch settings upon initialization and on returning to the chat screen (ON_START).
- Prevents expensive EncryptedSharedPreferences XML decryption occurring on every message per token stream update.
- Fixed `ChatViewModelTest` missing mocks for `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()`.
- Fixed ktlint unused import and max line length violations.

Fixes #269
2026-06-23 12:06:06 +03:00
M57 45650d3fc8 Fix O(N²) string concatenation in streaming handlers (#334)
Replaced direct string concatenation with `StringBuilder` accumulation
in `handleMessageToken` and `handleThinkingDelta` inside `ChatViewModel.kt`.
Throttled UI state updates to a maximum of ~30fps (every 33ms) to
prevent UI thread blocking and high memory allocation during long
streaming responses. Test execution logic is bypassed correctly to
ensure accurate unit test performance.

Fixes #270
2026-06-23 11:36:03 +03:00
M57 8e040543de Fix(#296): accessibility issues and add test tags to UI elements (#309)
This commit addresses the accessibility issues described in Issue 296:
1. Replaced `pointerInput` gestures with `combinedClickable(role = Role.Button)` in `ChatBubble.kt` and `ChatScreen.kt` to ensure screen readers announce interactive text elements and chat bubbles correctly.
2. Added `role = Role.Button` to the clickable modifiers in `SessionsScreen.kt`.
3. Introduced proper `testTag` modifiers throughout interactive components like `session_card`, `chat_bubble_user`, `chat_bubble_assistant`, and `clarify_option` to facilitate UI testing.
2026-06-22 21:36:46 +03:00
M57andM57 60bf6e8a33 feat: UI Changes
* fix(#220): implement edit soul and set model dialogs

* fix(#211): make UI more compact and information dense

* fix(#211): make navigation drawer sidebar more compact

* feat(#252): move settings item to end of navigation drawer

* feat(#250): add bottom navigation bar display mode settings

* feat(#225): add search bar filtering to remaining screens

* fix: make bottom navigation bar display modes more compact

* fix: resolve compilation error in KanbanScreen by referencing assignedTo

* feat: add search to achievements screen and top padding for icon-only navigation bar

* fix: adjust icon-only bottom bar height and remove padding

* fix(#249): remove unused PaddingValues imports in 6 screens

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-21 20:42:23 +03:00
M57 4715c85185 fix(#204): dynamically reset and reload chat title when creating or switching sessions (#247)
* fix(#204): dynamically reset and reload chat title when creating or switching sessions

* fix(#204): refresh session list on message completion to catch auto-generated titles
2026-06-21 19:45:08 +03:00
M57 0c192eddad fix(#205): fix typing effect by checking actual streamed word counts and default it to enabled (#246)
* fix(#205): fix typing effect by checking actual streamed word counts and default it to enabled

* fix: correct import path of rememberSaveable to resolve build error

* fix: make typingDelayMs reactive using rememberUpdatedState in StreamingBubbleWithTypingEffect

* fix(#205): support message.delta event type to enable streaming text

* feat(#205): increase typing effect delay slider maximum to 1000ms

* fix(#205): continue animating completed streaming messages until all words are revealed

* fix(#205): revert slider delay maximum value back to 100ms
2026-06-21 19:38:51 +03:00
M57 2f5a31d3cb fix(#213): scroll-to-bottom FAB now targets absolute list bottom (#245) 2026-06-21 19:16:32 +03:00
M57 bfef3613fa fix(#240): parse session_id from params root (#242)
* fix(#240): parse session_id from params root

Allows correct session mapping when opening app via notification.

* fix(#240): set MainActivity launchMode to singleTop

Prevents activity destruction and WebSocket reconnection when tapping notifications.

* fix(#240): save inline notification replies to Room database

* fix(#240): refresh current session when app comes to foreground

* fix(#240): use uppercase role string to match Mapper mapping expectations

* style: format NotificationReplyReceiver.kt to satisfy ktlint
2026-06-21 19:04:54 +03:00
M57andM57 d7ca7b084e fix(#240): notification tap race condition opens new chat instead of correct session (#241)
Two-pronged fix for the race between GatewayReady auto-creating a session
and ChatScreen LaunchedEffect calling switchSession:

1. ChatViewModel: added `initialSessionId` field. GatewayReady now checks
   this before auto-creating a session — if set, it calls switchSession
   instead. The field is set synchronously by ChatScreen via SideEffect
   during composition, before any WS event can be processed.

2. ChatScreen: LaunchedEffect now keys on state.connectionStatus and
   waits for CONNECTED before calling switchSession. This prevents the
   SESSION_RESUME message from being dropped (webSocket is null before
   onOpen, so send() is a silent no-op).

Fixes #240

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-21 18:32:29 +03:00
Hy4ri 315e1097b2 fix(#207): dynamic contrast adjustments for chat bubbles legibility 2026-06-21 12:43:15 +03:00
Hy4ri cdad31b555 refactor(#78): extract UI strings for chat, settings, connect, kanban, and skills screens 2026-06-21 11:38:33 +03:00
Hy4ri 19d44c077b chore: format codebase with ktlint 2026-06-21 10:57:18 +03:00
M57andM57 a5c070b353 ux: swap positions of new chat and search buttons in top bar (Issue #212) (#226)
Swapped the order of the Search toggle and New Chat icon buttons
in the ChatScreen top bar actions slot. Search is now on the left
(right-side outer position) which matches user expectations —
the new chat button was accidentally tapped when reaching for search.

Fixes #212

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-21 10:24:32 +03:00
M57 5e90a4d1a7 fix(chat): conditional auto-scroll on new messages (#131) (#193) 2026-06-21 00:29:26 +03:00
M57 60c4096e9c Performance improvement for chat search filtering (#188) 2026-06-21 00:00:36 +03:00
M57 045932ca8b Optimize collection creation in Navigation.kt (#180)
* ⚡ Optimize collection creation in Navigation.kt

Replaced uses of `.map { ... }.toSet()` with `.mapTo(mutableSetOf()) { ... }` in Navigation.kt. This avoids the creation of intermediate ArrayList objects, saving unnecessary memory allocations.

* ⚡ Optimize collection creation in Navigation.kt

Replaced uses of `.map { ... }.toSet()` with `.mapTo(mutableSetOf()) { ... }` in Navigation.kt. This avoids the creation of intermediate ArrayList objects, saving unnecessary memory allocations. Fixed ktlint formatting issues causing CI failure.
2026-06-20 23:44:39 +03:00
M57 838b888f9b ⚡ Optimize Set creation in ChatViewModel (#181)
Refactored `chatMessages.map { it.id }.toSet()` to `chatMessages.mapTo(HashSet(chatMessages.size)) { it.id }` to avoid creating and discarding an unnecessary intermediate ArrayList.

Benchmark results show a ~31% performance improvement on 5000 iterations for typical sizes.
2026-06-20 23:38:36 +03:00
M57 64a047f095 feat: move session history to a dedicated history screen (#175)
* feat: move session history to a dedicated history screen

Closes #159.

* fix(ci): fix ktlint style issues and unresolved NavigationController reference
2026-06-20 22:27:54 +03:00
M57 8763271056 fix(#101): collect events once in init to prevent duplicate collectors on reconnect (#169) 2026-06-20 17:32:24 +03:00
M57 74714fc1df fix(#99): gate tool and clarify events by session id to prevent leaks (#168) 2026-06-20 17:27:34 +03:00
M57 6cb4e17882 fix(#127): serialize tool event data to JSON for pretty formatting (#167)
* fix(#127): serialize tool event data to JSON for pretty formatting

* fix(test): format tool start test case to satisfy ktlint
2026-06-20 17:22:28 +03:00
M57andM57 3c5fcf504c feat(#154): add word-by-word typing effect toggle and speed slider in settings (#165)
* feat(#154): add word-by-word typing effect toggle and speed slider in settings

* fix(settings): import Slider to fix build failure

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 17:09:42 +03:00
M57andM57 ff4649b601 fix(#130): fix compilation errors in scroll-to-bottom FAB (#166)
* fix(#130): fix compilation errors in scroll-to-bottom FAB

* fix(#130): add missing R import, context, and string resources

* fix(#130): use stringResource() instead of context.getString() for Compose lint

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 16:45:05 +03:00