Commit Graph
38 Commits
Author SHA1 Message Date
M57andM57 c44f4c3068 test(#291): add coverage for 3 critical untested components (#300)
* test(#291): add coverage for 3 critical untested components

- NavigationController dedup guard (line 45): 14 pure unit tests for
  navigateTo, resetTo, goBack, primary-screen clearing, and null guards.
- NotificationReplyReceiver: 11 MockK-based tests covering the full
  WS+Room+notification flow: valid reply sends WS message, persists to
  Room with correct entity shape, shows replied confirmation notification,
  finishes PendingResult, and gracefully handles null/blank inputs and
  Room failures.
- ChatNotificationService isAppInForeground flag: 5 tests verifying
  companion object toggle, volatile modifier (thread safety), default
  false state, and idempotent calls.
- Fix: add @Volatile to isAppInForeground (was a plain var read from
  background coroutine and written from UI thread)

Closes #291

* fix(#291): fix MockK type errors in test compilation

* fix(#291): fix test assertions and goAsync mock for CI

* fix(#291): wrap final BroadcastReceiver.goAsync() with open Kotlin wrapper

* chore: trigger CI on latest fix commit

* fix(#291): make NotificationReplyReceiver open so tests can subclass it

* fix(#291): extract buildReplyNotification() to avoid 'not mocked' framework error

NotificationCompat.Builder.build() internally calls android.app.Notification.Builder
methods (e.g. setWhen) that throw RuntimeException with 'Method setWhen in
android.app.Notification.Builder not mocked' in unit tests — the standard Android
unit test environment does not provide real framework implementations.

Extracting notification creation into an open method (same pattern as goAsyncCompat())
lets tests override it to return a mock Notification, avoiding the framework call
entirely.

* style: fix ktlint blank-line-before-declaration in test

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 06:49:19 +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
M57 f68841c2a7 fix(#214): update test assertion to match full success message 2026-06-21 17:42:49 +03:00
M57 262578528b fix: stabilize flaky testAutoReconnect with polling assertion 2026-06-21 16:26:07 +03:00
M57 35c608b2bf fix(#201): fix platforms endpoint response shape mismatch
GET /api/messaging/platforms returns a JSON object with a platforms
array inside, but the app expected a bare array. Wraps the response
in a new MessagingPlatformResponse model.

- Adds MessagingPlatformResponse.kt wrapper model
- Updates HermesApiService.kt return type + adds profile query param
- Updates ChannelsViewModel.kt to unwrap .platforms
- Updates E2eIntegrationTest.kt mock to match new response shape
2026-06-21 16:03:36 +03:00
M57 00c6e75f50 feat(connect): saved connection profiles support (#107) (#191)
* feat(#107): implement saved connection profiles support

* fix(connect): fallback profiles resolution and stubbing in tests
2026-06-21 00:18:35 +03:00
M57 4547861c71 test: Add HermesWsClient test (#187) 2026-06-20 23:56:04 +03:00
M57 2b7facb7f4 Add NetworkResult Test for SafeApiCall and Mapping (#185)
* Add tests for NetworkResult and safeApiCall

* Fix ktlint errors formatting
2026-06-20 23:52:23 +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 81b2ff3aa5 Add error path coverage for CronExpressionFormatter (#182)
* test: add error path coverage for CronExpressionFormatter

Added a new test case to cover the fallback code path when cron parsing throws an exception. This test ensures the formatting correctly defaults to returning "Raw: <schedule>" when an invalid string like '0 12 1 15 *' or '0 9 * * 8' is passed.

It also introduces mockk integration to mock android.util.Log which prevents RuntimeExceptions from being thrown during local JUnit tests when logging errors in the catch block.

* test: add error path coverage for CronExpressionFormatter and fix ktlint issues

Added a new test case to cover the fallback code path when cron parsing throws an exception. This test ensures the formatting correctly defaults to returning "Raw: <schedule>" when an invalid string like '0 12 1 15 *' or '0 9 * * 8' is passed.

It also introduces mockk integration to mock android.util.Log which prevents RuntimeExceptions from being thrown during local JUnit tests when logging errors in the catch block.

Also removed trailing whitespace and an empty line at the start of the class to fix ktlint checking in CI.
2026-06-20 23:43:46 +03:00
M57 3eb9e09f86 test: Add tests for malformed Base64 decoding in ConnectViewModel (#183) 2026-06-20 23:41:31 +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 a50628696f test(#100): add unit test to verify upsert for completion-only message (#170) 2026-06-20 17:38:07 +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 03cfcf8504 feat(#125): fix clarify input pre-filling and parse/route clarify_id (#156)
* feat(#125): fix clarification input pre-filling and parse/route clarify_id

* fix(#156): fix ktlint violations in ChatViewModelTest.kt

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 16:35:58 +03:00
M57andM57 0ec70beb84 refactor(#104): prune unused ChatMessageDao APIs and stale test stubs (#160)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 13:57:51 +03:00
Hy4ri cbd743a5c9 feat(#125): pre-fill clarification input and route response via clarify.respond 2026-06-20 12:51:44 +03:00
Hy4ri 8d31e37baa feat(#125): add free-form text input and option tap/long-press behavior to ClarifyDialog 2026-06-20 12:28:14 +03:00
Hy4ri 98b8588d41 fix(#146): finalize active streaming message before tool execution or new message start 2026-06-20 12:16:11 +03:00
Hy4ri 58ee32cbd1 fix(chat): remove extra closing brace in ChatViewModel.kt 2026-06-20 12:04:42 +03:00
Hy4ri d0eedf053f refactor(chat): stabilize streaming, session loading, and connection status
Optimize streaming message updates, correct session load merging, and derive connection state reactively. Add reconnection UI banner.
2026-06-20 11:04:56 +03:00
M57 0fabad820d fix(chat): fix old session resume losing server session ID
* fix(chat): reset agent typing status when resuming old sessions

* fix(chat): fix race condition in websocket request registration

* test(chat): update mocks for new send/sendMessage onSent callback

* style(chat): use named onSent param instead of trailing lambda

* style(chat): fix import ordering for ConcurrentHashMap

* test(chat): fix remaining 2-arg verify calls for send()
2026-06-20 00:40:16 +03:00
Hy4ri 508aea8bfa fix(#79): centralize retrofit error handling with NetworkResult 2026-06-19 22:58:33 +03:00
Hy4ri 055b3381b1 Implement human-readable cron expression formatter on CronJobsScreen 2026-06-19 18:49:41 +03:00
M57 d90e36974c feat: Material You design system rewrite — Phases 1–4
Complete UI/UX rewrite of hermes-mobile with Material You design system.

Phase 1: Color/Theme/Type/Shapes/Spacing/Motion tokens
Phase 2: HermesScaffold, StateViews (skeleton/empty/error), SearchBar, StatusBadge, ToggleRow
Phase 3: Bottom nav + drawer navigation with NavigationController
Phase 4: All 17 screens rewritten with HermesScaffold and Material You patterns

Closes PR #3.
2026-06-18 16:34:44 +03:00
M57andM57 5130c2e308 feat(chat): message persistence + background notifications (#2)
* feat(chat): message persistence + background notifications

Two critical gaps for daily-driver use:

1. Message persistence (Room database):
   - New ChatMessageEntity / ChatMessageDao / HermesDatabase layer
   - Every user message, streaming token, tool result, and assistant
     reply is written to a local Room DB as it arrives
   - switchSession loads cached messages instantly from Room before
     the REST call returns — no more blank screen while loading
   - Session resume also loads from cache first
   - Messages survive app kills, process death, and configuration
     changes
   - ChatViewModel changed from ViewModel() to AndroidViewModel(app)
     to access the database context

2. Background notifications (foreground service):
   - ChatNotificationService keeps the WebSocket alive while the app
     is backgrounded and posts a high-priority notification when a
     MessageComplete or ClarifyRequest event arrives while the app
     is not in the foreground
   - Lifecycle wired via LifecycleEventObserver in ChatScreen:
     ON_STOP starts the service, ON_START stops it
   - Tapping the notification returns to the chat
   - New permissions: POST_NOTIFICATIONS, FOREGROUND_SERVICE,
     FOREGROUND_SERVICE_DATA_SYNC

Build changes:
   - Added Room 2.7.1 + KSP to version catalog
   - Added WorkManager 2.10.0 (reserved for future polling fallback)
   - Updated ChatViewModelTest to mock HermesDatabase + use
     AndroidViewModel constructor

* fix(build): use KSP 2.3.9 (new versioning scheme for Kotlin 2.3.x)

* fix(build): add Room schemaDirectory (required by Room Gradle plugin)

* fix(compile): add else branch to WsEvent when expression (exhaustiveness)

* ci(release): add release workflow + R8 minification + proguard rules

New .github/workflows/release.yml:
- Triggers on tag pushes (v*) and manual workflow_dispatch
- Runs full CI (ktlint + lint + test) before building
- Builds assembleRelease with version injection from tag
- Creates a GitHub Release with auto-generated changelog
- Attaches the release APK to the release

Build changes:
- Release builds now use R8 minification (isMinifyEnabled=true)
  and resource shrinking (isShrinkResources=true) for smaller APKs
- versionCode/versionName injectable via -PversionCode / -PversionName
  Gradle properties (passed by CI from the git tag)
- Local dev builds default to versionCode=1, versionName=1.0-dev

ProGuard rules:
- Keep all data model classes (Gson reflection)
- Keep Room entities and RoomDatabase subclasses
- Keep OkHttp/Retrofit interface methods
- Suppress warnings for Compose, Security Crypto, Coroutines

* feat(ci): release workflow + proguard rules + test fix

Release workflow (.github/workflows/release.yml):
  - Triggers on tag push (v*) or manual workflow_dispatch
  - Two jobs: ci (ktlint+lint+test) → build-release
  - Builds a minified, shrunk release APK with version from tag
  - Creates a GitHub Release with auto-generated release notes
  - Attaches the APK as a release asset

Build config:
  - versionCode/versionName now injected from CI via -P flags
  - Release build enables isMinifyEnabled + isShrinkResources
  - proguard-rules.pro: keep rules for Gson models, Room entities,
    OkHttp/Retrofit interfaces, Coroutines, Compose, BuildConfig

Test fix:
  - ChatViewModelTest: use coEvery for suspend DAO methods instead
    of every (compile error — suspend functions need coEvery)
  - Added stubs for all suspend DAO methods used by the ViewModel

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 03:23:38 +03:00
M57 a28d53127f test: update switchSession asserts for B3 isLoading fix
loadSessionMessages now resets isLoading=false on its error branch
(was previously stuck at true). Test was implicitly relying on the bug.
2026-06-18 02:25:45 +03:00
Hy4ri d4328d0fb2 feat: add pairing / connection string parsing & verify with unit tests 2026-06-16 23:13:04 +03:00
Hy4ri bfa959ed05 test: add ModelViewModel success path test 2026-06-16 18:17:56 +03:00
Hy4ri f355814842 fix: plugin status toggling & fix tests compilation 2026-06-16 17:38:12 +03:00
M57 a994c7d5db test: fix malformed JSON in HermesApiServiceTest cron job stub 2026-06-16 01:29:39 +03:00
M57 8b4f5f91e0 style: fix ktlint violations (unused imports + line length) 2026-06-16 01:22:48 +03:00
Hy4ri 8ca4599c6a fixes 2026-06-16 00:52:36 +03:00
Hy4ri 03916014c0 Implement Logs, Plugins, Channels, Keys, System, and Kanban screens with full integration tests and code formatting checks 2026-06-16 00:13:18 +03:00
Hy4ri d6ff3de594 Initial commit of HermesControl Android app 2026-06-16 00:00:36 +03:00