Commit Graph
100 Commits
Author SHA1 Message Date
efba360d8a fix(ci): add release-compile safeguard to catch debugImplementation regressions (#336)
- Reverts okhttp.logging dependency from debugImplementation back to
  implementation so it's on the release classpath
- Adds compileReleaseKotlin CI job to catch release-variant compilation
  errors on every PR
- Fixes signing config to allow compileReleaseKotlin without keystore env
  vars (graceful fallback instead of hard require())
- Updates AGENTS.md with dependency scoping pitfall and CI docs

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 05:46:11 +03:00
M57andGitHub 98c02879b4 Refactor: Extract boilerplate: safeLaunchLoad, clearToast, ToastEffect (CODE-21/22/23) (#312)
- Extracted `safeLaunchLoad` inline extension function to `ViewModelExt.kt` to encapsulate API call success/failure handling logic and replaced usages across 18 viewmodels.
- Extracted `ToastHost` interface to standardize `clearToast()` behavior across all ViewModels displaying toasts.
- Extracted `ToastEffect` composable to `SharedComponents.kt` and applied it to 16 screens, reducing repetitive side effect code blocks.

Fixes #286
2026-06-22 23:54:42 +03:00
M57andGitHub 6abfabdac0 fix(#287): fix empty-state subtitles and content description on Keys and Profiles screens (#311)
* fix(#287): fix empty-state subtitles and content description on Keys and Profiles screens

- Added `keys_empty_desc` and `profiles_empty_desc` string resources.
- Updated `KeysScreen.kt` and `ProfilesScreen.kt` to use correct empty-state subtitles instead of the copy-pasted `toolsets_empty_desc`.
- Added `content_desc_edit` string resource and applied it to the edit icon in `KeysScreen.kt` for accessibility.

* fix(#287): correct empty-state descriptions and accessibility content descriptions

- Replaced incorrect `toolsets_empty_desc` with newly added `keys_empty_desc` on the Keys screen.
- Replaced incorrect `toolsets_empty_desc` with newly added `profiles_empty_desc` on the Profiles screen.
- Updated the content description of the edit icon on the Keys screen to use newly added `content_desc_edit` instead of `nav_drawer_section_converse`.
2026-06-22 22:33:09 +03:00
e8025efcde fix(#298): internationalize ConnectViewModel strings, fix null-assert crash on profile selector (#307)
* fix(#298): internationalize ConnectViewModel strings, fix null-assert crash on profile selector

E1: Add all connect error resources to strings.xml for i18n readiness
E2: Migrate ConnectViewModel to AndroidViewModel, replace all hardcoded
    user-facing strings with getString(R.string.*) calls
F1: Replace state.selectedProfile!! with .?.let { } pattern in
    ConnectScreen.kt to eliminate NPE risk on mutable state copy

Closes #298

* fix(#298): update test files for AndroidViewModel constructor

Update ConnectViewModelTest.kt and E2eIntegrationTest.kt to pass
mock Application to the new ConnectViewModel(app) constructor.
Add getString() MockK stubs matching the string resource keys.

* fix(#298): add missing LocalContext import and restore connect_pairing_string

Two compilation errors:
- ConnectScreen.kt was missing import for LocalContext
- strings.xml was missing connect_pairing_string resource
  (accidentally removed in earlier patch)

* fix(#298): extract ViewModelFactory to avoid composable-in-non-composable error

LocalContext.current cannot be called inside a viewModel { } factory
lambda because the lambda is not @Composable. Fix by extracting a
ConnectViewModelFactory class and using run { } to capture the
Application value at the composable call site before passing to
the factory constructor.

* fix(#298): fix MockK varargs matching in test stubs

getString(int, Object...) takes varargs. MockK's any<Int>() and
any<String>() didn't match the vararg Object... slot. Changed to
any() (untyped) which matches correctly.

* fix(#298): fix MockK answers — arg indexing was wrong

In getString(int, Object...), firstArg is the resource ID,
secondArg is the format argument. Using firstArg<Int>() in
the answers block was returning the res ID, not 500/etc.
Changed to secondArg<Any>().

* fix(#298): replace getString(int,Object...) with String.format(getString(int),...)

Avoid MockK varargs matching issues by calling app.getString(int)
for the format template and String.format() to apply args.
Simplifies test stubs to pure getString(int) returns.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 21:38:28 +03:00
M57andGitHub 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
M57andGitHub e5f0df9273 chore(#297)
* fix: fix ktlint issues in gradle files

Fixed code style and formatting issues across `build.gradle.kts` and `app/build.gradle.kts` to resolve CI failures during the `ktlint` task.

* fix: fix ktlint and tests for PR 306

Updates dependencies logic to use `MasterKeys` properly based on the `androidx.security:security-crypto` upgrade to 1.0.0, and fixes `ktlint` formatting errors across the build scripts.
2026-06-22 21:35:40 +03:00
fbc4ee2b75 fix(#295): update test count, correct AGENTS.md DAO return type
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 15:19:47 +03:00
f870f96fe0 fix(#294): document KSP standalone versioning, harden ci-summary reporting
- A2: Add inline comment to libs.versions.toml explaining ksp version
  is standalone per KSP 2.0+ guidance (not kotlinVersion-kspVersion)
- A3: Rewrite ci-summary check job to report each dependency's result
  individually with explicit skipped/cancelled/failure handling

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 15:11:14 +03:00
b81a84f801 fix(#284): remove dead code across API, models, WS client, and docs (#299)
Remove six dead-code findings from the automated review:
- CODE-01: updateProfileDescription endpoint + UpdateProfileDescriptionRequest DTO (uncalled)
- CODE-02: getConfig() endpoint (app uses getRawConfig())
- CODE-03: TogglePluginRequest DTO (unreferenced)
- CODE-05: mark isConnected with @VisibleForTesting (test-only usage)
- CODE-06: fix KDoc createdAt -> timestamp to match actual field name

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 14:05:31 +03:00
2acfaabfcb fix(#284): remove legacy WS callbacks (CODE-04), migrate tests to StateFlow/SharedFlow (#303)
- Remove onMessage/onConnected/onDisconnected/onError callback
  properties from HermesWsClient — redundant with events SharedFlow
  and connectionStatus StateFlow
- Rewrite all 10 tests to use connectionStatus.first{} and
  events.first{} instead of callback + CountDownLatch patterns
- Remove polling loop for RECONNECTING status — flow inherently
  waits for the right value

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 14:05:01 +03:00
15a4748634 test(#292): coverage gaps TEST-04 through TEST-14 (#302)
* test(#292): coverage gaps TEST-04 through TEST-14

- TEST-04: ApiClient auth interceptor + BuildConfig.DEBUG gate (6 tests)
- TEST-05: SettingsViewModel profile operations (5 tests)
- TEST-06: ConnectViewModel profile switch edge cases (8 tests)
- TEST-07: EventParser untested subtypes (12 tests)
- TEST-08: AuthManager token routing through profiles (5 tests)
- TEST-09: ChatScreen instrumented compose tests (3 tests)
- TEST-10: WS reconnect state recovery (5 tests)
- TEST-11: Multi-profile token scenarios (6 tests)
- TEST-12: Chat DAO Room operations (11 tests)
- TEST-13: ChatViewModel slash command handling (8 tests)
- TEST-14: Predictive back gesture instrumented test (1 test)

Total: 70 tests across 11 files. CI updated with new
instrumented-tests job via android-emulator-runner.

* fix(ci): resolve ktlint, tests, and emulator failures

* fix(ci): resolve test failures on PR #302

* fix(ci): more ChatMessageDaoTest context mocks, re-read state after advanceUntilIdle in connect test

* fix(ci): remove problematic packageManager mock, decouple instrumented tests from CI gate

* fix(ci): switch emulator to default target, add more cores, memory, and timeout

* fix(ci): remove mockkStatic(Log::class) from ChatMessageDaoTest - fails on Android stubs

* fix(ci): use aosp_atd emulator image + KVM + AVD cache for fast instrumented tests

* fix(ci): remove ApplicationInfo mock - Android stub class breaks MockK

* fix(ci): rewrite ChatMessageDaoTest to use HermesDatabase.setForTest() pattern - Room in-memory DB requires Android SDK not available in JVM tests

* fix(ci): use actions/cache@v4 instead of @v7 (doesn't exist)

* fix(ci): add missing performClick import in StateViewsTest

* fix(ci): mock ChatViewModel in instrumented tests, add mockk to androidTestImplementation

* fix(ci): add asStateFlow import in ChatScreenTest

* fix(ci): exclude duplicate META-INF LICENSE/NOTICE files from androidTest build

JUnit Jupiter 5.8.2 JARs each ship META-INF/LICENSE.md,
causing mergeDebugAndroidTestJavaResource to fail with
'duplicate files found'. Add LICENSE.md, LICENSE, NOTICE,
and NOTICE.md to the packaging exclusion list.

* fix(#302): exclude duplicate META-INF/LICENSE-notice.md from instrumented test APK

JUnit Jupiter 5.x JARs all ship META-INF/LICENSE-notice.md,
causing mergeDebugAndroidTestJavaResource to fail.

* fix(#302): add LICENSE-notice.md as separate exclude entry

The Ant-style glob brace expansion in packaging.resources.excludes
doesn't handle LICENCE-notice.md inside the {}-enclosed pattern.
Adding it as a separate entry instead.

* fix(#302): use mockk-android for instrumented tests

io.mockk:mockk uses Java instrumentation agents not available on
Android. Switch to io.mockk:mockk-android for connectedDebugAndroidTest
which uses an Android-compatible mocking strategy.

* fix(#302): use createAndroidComposeRule for test lifecycle isolation

Standard createComposeRule has a gap where consecutive test methods
can lose the compose hierarchy on the emulator. Switching to
createAndroidComposeRule<MainActivity> gives explicit activity
lifecycle management across test methods, fixing the
'No compose hierarchies found' errors.

* fix(#302): use createComposeRule instead of createAndroidComposeRule<MainActivity>

MainActivity.onCreate() already calls setContent, so the test's
setContent { ChatScreen(...) } crashed with 'has already set content'.
Switch to createComposeRule() which launches a bare ComponentActivity
that doesn't call setContent, allowing the test to compose ChatScreen
in isolation with a mock ChatViewModel.

* fix(#302): use createAndroidComposeRule<ComponentActivity> + unmockkAll for instrumented tests

ChatScreenTest was failing with 'No compose hierarchies found' because
mockk mocks in instrumented tests weren't cleaned up between test runs.
Switch from createComposeRule() to explicit createAndroidComposeRule<ComponentActivity>()
and add @After tearDown() with unmockkAll() to ensure mock state doesn't
leak between test methods.

* fix(#302): wrap ChatScreen in HermesControlTheme in instrumented tests

ChatScreen accesses MaterialTheme.colorScheme during composition (background
gradient). Without a Material3 theme wrapper, the composable silently crashes
and no compose hierarchy is registered — causing 'No compose hierarchies found'
on any subsequent onNodeWithTag() call.

Fix: wrap ChatScreen in HermesControlTheme inside setContent {}, matching how
MainActivity renders the app. Also use createAndroidComposeRule<ComponentActivity>
instead of createComposeRule() for explicit lifecycle management, and add
unmockkAll() in @After to clean up mockk state between tests.

* fix(ci): fix packaging resource paths and use pickFirsts for duplicate META-INF files

Remove leading slashes from META-INF paths (AGP paths are
relative, not absolute). Use pickFirsts instead of excludes
to handle duplicate files from JUnit Jupiter JARs.

* fix(#302): use createAndroidComposeRule<ComponentActivity> for test isolation

createComposeRule() has a known race where the compose hierarchy
is lost between sequential test methods on the emulator.
createAndroidComposeRule<ComponentActivity> provides proper
Activity lifecycle management while using the same lightweight
empty activity that doesn't pre-set content (unlike MainActivity).

* fix(#302): consolidate ChatScreenTest into single test method

The Compose test rule's lifecycle gap between test methods on the
emulator causes 'No compose hierarchies found' on consecutive
setContent calls. Consolidating all three test scenarios into one
invocation eliminates the cross-test teardown issue entirely.

* fix(#302): mock ContextCompat to suppress permission dialog in test

The ChatScreen LaunchedEffect requests POST_NOTIFICATIONS via
ActivityResultContracts.RequestPermission(), which pauses the
activity and disposes the compose hierarchy. Mocking
ContextCompat.checkSelfPermission to return PERMISSION_GRANTED
prevents this from happening during the test.

* fix(#302): set connectionStatus=CONNECTED in ChatScreenTest

The default ChatUiState has connectionStatus=DISCONNECTED, which
makes isConnected=false, in turn disabling the chat input field
(enabled = isConnected) and the send button (canSend checks
isConnected). Set it to CONNECTED so both controls are active.

* fix(#302): add ConnectionStatus import and fix ktlint wrapping

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 13:49:04 +03:00
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
bbe3b00ccb test(#293): add foundational Compose UI test for StateViews (TEST-15) (#301)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 05:25:28 +03:00
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
M57andGitHub 32def80694 fix(#220): implement edit soul and set model dialogs (#248) 2026-06-21 20:03:31 +03:00
M57andGitHub 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
M57andGitHub 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
M57andGitHub 2f5a31d3cb fix(#213): scroll-to-bottom FAB now targets absolute list bottom (#245) 2026-06-21 19:16:32 +03:00
M57andGitHub abe9e0e284 fix(#218): fallback to Untitled for empty or blank session titles (#244) 2026-06-21 19:13:09 +03:00
M57andGitHub 57c3e468df feat(#238): update Model tab icon to Psychology (#243) 2026-06-21 19:09:16 +03:00
M57andGitHub 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
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
M57andGitHub adc23e3540 Merge pull request #239 from Hy4ri/fix/issue-214-notification-tap-navigation
fix(#214): notification tap now navigates to the correct session
2026-06-21 17:47:28 +03:00
M57andGitHub 0828fd8796 Merge pull request #237 from Hy4ri/fix/issue-201-platforms-response-shape
fix(#201): platforms endpoint response shape mismatch
2026-06-21 17:25:10 +03:00
M57andGitHub a89b932a0e Merge pull request #236 from Hy4ri/feat/issue-208-theme-presets
feat(#208): add Monochrome, Gruvbox, Catppuccin and AMOLED theme presets
2026-06-21 13:10:38 +03:00
M57andGitHub f738f33fc5 Merge pull request #235 from Hy4ri/feat/issue-207-chat-bubble-contrast
fix(#207): dynamic contrast adjustments for chat bubbles legibility
2026-06-21 12:46:50 +03:00
M57andGitHub 7d8d2707c6 Merge pull request #234 from Hy4ri/feat/issue-232-dynamic-theme
feat(#232): dynamic theme switch on settings save
2026-06-21 12:39:21 +03:00
M57andGitHub 7ce19cd52c Merge pull request #233 from Hy4ri/feat/issue-232-cron-icons
feat(#232): replace cron action text buttons with compact icons
2026-06-21 12:30:30 +03:00
M57andGitHub d1d5b0937c Merge pull request #231 from Hy4ri/refactor/issue-78-strings-extraction
refactor(#78): extract hardcoded strings to strings.xml
2026-06-21 12:23:10 +03:00
M57andGitHub 20b8eae282 Merge pull request #229 from Hy4ri/fix/issue-template-frontmatter
fix: remove invalid assignees field from issue template frontmatter
2026-06-21 10:36:18 +03:00
M57andGitHub 52bfadcae8 Merge pull request #228 from Hy4ri/feat/issue-templates
docs: add GitHub issue templates (bug report + feature request)
2026-06-21 10:33:39 +03:00
bc3ddd626b docs: add GitHub PR template (#227)
Adds a .github/PULL_REQUEST_TEMPLATE.md with sections for summary,
type of change, screenshots, testing steps, and project-specific
checklist items (HermesScaffold, NavigationController, room schema,
screen dedup check).

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-21 10:29:03 +03:00
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
M57andGitHub 4e8da7b90c fix(security): allow cleartext traffic globally to support LAN IP connections (#200)
* feat(skills): add category and status filters (#111)

* fix(security): allow cleartext traffic globally to support LAN IP connections
2026-06-21 01:09:28 +03:00
M57andGitHub 63575272c9 feat(skills): add category and status filters (#111) (#199) 2026-06-21 00:49:58 +03:00
M57andGitHub 081996d376 refactor: extract entryProvider from MainNavigation (#197)
Extract the overly long `entryProvider` lambda out of `MainNavigation` into a separate `appEntryProvider` function to improve code readability and maintainability.
2026-06-21 00:31:59 +03:00
M57andGitHub f7a334ecfd Refactor CronExpressionFormatter to improve maintainability (#192)
Extract logic from a single long method `cronToHumanReadable` into distinct parser helpers for shortcuts, day of month, days of week, and general time handling. Move repetitive static properties and small format helpers to the object scope.
2026-06-21 00:30:02 +03:00
M57andGitHub 5e90a4d1a7 fix(chat): conditional auto-scroll on new messages (#131) (#193) 2026-06-21 00:29:26 +03:00
M57andGitHub 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
M57andGitHub 0eed12d18e Refactor: Extract hardcoded "todo" to constant in KanbanScreen (#190)
- Extracted the hardcoded string "todo" to a `DEFAULT_COLUMN` constant in `KanbanScreen.kt` to improve code health and readability.
- Replaced the hardcoded string literal in the `viewModel.createTask` call with the new constant.
2026-06-21 00:17:37 +03:00
M57andGitHub 63a927fae2 chore: Update softprops/action-gh-release to v3 to use Node 24 (#189)
Fixes the deprecation warning for Node 20 by bumping the action version.
2026-06-21 00:15:29 +03:00
M57andGitHub 60c4096e9c Performance improvement for chat search filtering (#188) 2026-06-21 00:00:36 +03:00
M57andGitHub 4547861c71 test: Add HermesWsClient test (#187) 2026-06-20 23:56:04 +03:00
M57andGitHub 49fd12ac34 fix(#115): send enabled flag on config save and propagate configuration errors (#186) 2026-06-20 23:54:43 +03:00
M57andGitHub 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
M57andGitHub 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
M57andGitHub 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
M57andGitHub 34af34af6d perf: avoid intermediate allocation in bottomNavKeys (#184)
Replaces `.map { ... }.toSet()` with `.mapTo(mutableSetOf()) { ... }`
when resolving `bottomNavKeys` to avoid creating and discarding an
intermediate `ArrayList` during recomposition.
2026-06-20 23:42:40 +03:00
M57andGitHub 3eb9e09f86 test: Add tests for malformed Base64 decoding in ConnectViewModel (#183) 2026-06-20 23:41:31 +03:00
M57andGitHub 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
M57andGitHub d44df3f339 fix(#102): add EmptyState views for successful empty responses across data screens (#179) 2026-06-20 23:27:17 +03:00
M57andGitHub d1f4e9eb67 fix(#67): restrict cleartext traffic to local subnets and Tailscale range (#178) 2026-06-20 23:17:00 +03:00
M57andGitHub f2e11413cc fix(#66): enable room schema export and configure release migration safe-guards (#177) 2026-06-20 22:57:08 +03:00
M57andGitHub a315c9d3d4 fix(#63): require keystore env variables for release builds (#176)
* fix(#63): require keystore env variables for release builds

* fix(ci): conditionalize signing requirement to release tasks only
2026-06-20 22:47:46 +03:00
M57andGitHub 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
M57andGitHub d862172d78 fix(nav): keep NavKey implementation names in proguard (#174)
Avoid obfuscating screen classes/objects since their simpleNames are matched for bottom nav setup.
2026-06-20 17:54:58 +03:00
M57andGitHub 82f2a5142f ci: update release workflow to Node.js 24 and setup-gradle validation (#173)
Closes #171.
2026-06-20 17:49:41 +03:00
M57andGitHub 54a195bd94 fix(auth): initialize AuthManager in custom application class (#172)
Closes #68. Redundant initialization in MainActivity has been removed.
2026-06-20 17:43:40 +03:00
M57andGitHub a50628696f test(#100): add unit test to verify upsert for completion-only message (#170) 2026-06-20 17:38:07 +03:00
M57andGitHub 8763271056 fix(#101): collect events once in init to prevent duplicate collectors on reconnect (#169) 2026-06-20 17:32:24 +03:00
M57andGitHub 74714fc1df fix(#99): gate tool and clarify events by session id to prevent leaks (#168) 2026-06-20 17:27:34 +03:00
M57andGitHub 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
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
M57andGitHub 8c9c11df23 refactor(#103): centralize navigation mutations through NavigationController.resetTo() (#164)
* refactor(#103): centralize navigation mutations through NavigationController.resetTo()

* fix(chat): fix scroll-to-bottom FAB compilation errors
2026-06-20 17:01:37 +03:00
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
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
4544d88f8d feat(#130): add scroll-to-bottom FAB that appears when user scrolls up (#163)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 14:12:25 +03:00
ca21e22147 ci(#71): change FGS type from dataSync to remoteMessaging for indefinite WebSocket listener (#162)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 14:07:46 +03:00
87392f5e57 fix(#148): prevent chatTitle being overwritten by response handlers (#161)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 14:04:59 +03:00
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
3f19224228 feat(#123): add primary screens to drawer categorically (#158)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 13:46:08 +03:00
c6d7a45905 fix(#135): guard goBack() against empty back stack (#157)
goBack() now uses ChatScreen as a fallback when the stack has
only one item, preventing the Settings back-button crash.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 13:29:19 +03:00
M57andGitHub 62b1115924 Merge pull request #155 from Hy4ri/feat/issue-125-clarification-respond-routing
feat(#125): pre-fill clarification input and route response via clarify.respond
2026-06-20 12:57:31 +03:00
M57andGitHub 399691ed34 Merge pull request #153 from Hy4ri/feat/issue-125-clarification-input
feat(#125): add free-form text input and guidelines to ClarifyDialog
2026-06-20 12:33:16 +03:00
M57andGitHub 42f8cfe509 Merge pull request #152 from Hy4ri/fix/issue-146-lost-replies
fix(#146): finalize active streaming message before tool execution or new message start
2026-06-20 12:20:55 +03:00
M57andGitHub 196883b5c1 Merge pull request #151 from Hy4ri/refactor/chat-system-stability
refactor: stabilize chat system streaming, session loading, and connection state
2026-06-20 12:12:26 +03:00
a2d299e136 feat(#129): remove redundant source archive, add About version card in Settings (#149)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 10:30:18 +03:00
f5edf1569c fix(#141): notification tap opens original session instead of new session (#147)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 10:10:10 +03:00
ae62b05e6d fix(#144): add timestamp to tool/command message bubbles (#145)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 10:01:44 +03:00
7248f66abf feat(#136): visible copy button overlay on long-press of message bubbles (#143)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 09:46:10 +03:00
113467a99d fix(#140): fix skills content API endpoints — path params to query params (#142)
Co-authored-by: ronia <ronia@m57>
2026-06-20 02:43:26 +03:00
261ee6379a feat(#132): dynamic session title with ellipsis truncation (#139)
Co-authored-by: ronia <ronia@m57>
2026-06-20 02:37:55 +03:00
913c6fa878 fix(#134): remove settings button from chat top bar (#138)
Co-authored-by: ronia <ronia@m57>
2026-06-20 02:28:36 +03:00
27ac4d72a4 fix(#133): only show status dot red when offline, hide when connected (#137)
Co-authored-by: ronia <ronia@m57>
2026-06-20 02:25:29 +03:00
M57andGitHub 61d49d2355 fix(chat): don't reload cached msgs on SESSION_RESUME ack 2026-06-20 00:54:16 +03:00
M57andGitHub 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
M57andGitHub 46b24593f9 feat(#58): implement skill markdown/yaml editor UI (#121) 2026-06-19 23:58:34 +03:00
M57andGitHub 022e9de195 Merge pull request #120 from Hy4ri/feat/issue-116-notification-reply
feat(#116): add direct reply action to background notifications
2026-06-19 23:48:55 +03:00
M57andGitHub ad508be0fa Merge pull request #119 from Hy4ri/feat/issue-114-chat-notification
fix(#114): separate notification channels and set service to low priority
2026-06-19 23:41:27 +03:00
M57andGitHub b1c628ee24 Merge pull request #118 from Hy4ri/feat/issue-60-tool-json-parsing
feat(#60): human-readable tool JSON parsing in chat
2026-06-19 23:32:27 +03:00
M57andGitHub 85dc48fcd6 Merge pull request #117 from Hy4ri/revert-pr-42
Revert "Merge pull request #42 from Hy4ri/feat/sessions-screen"
2026-06-19 23:24:36 +03:00
M57andGitHub 7d50b5acd7 Merge pull request #42 from Hy4ri/feat/sessions-screen
feat: dedicated Sessions browser screen (Issue #40)
2026-06-19 23:14:40 +03:00
M57andGitHub ee97be50cb Merge pull request #113 from Hy4ri/refactor/issue-79-retrofit-error-handling
fix(#79): centralize retrofit error handling with NetworkResult
2026-06-19 23:03:21 +03:00
43df3c6e73 fix(#64): call wsClient.disconnect() directly in onCleared() instead of launching on cancelled scope (#94)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:18:04 +03:00
abcec14cb5 feat(#76): add testTag identifiers to composable elements for UI test targeting (#98)
Adds testTag modifiers to key interactive elements across the core
composables, enabling Espresso/Compose UI tests to locate them:

Navigation.kt  — 'nav_*' on bottom bar items, 'drawer_*' on drawer items
HermesScaffold — 'back_button', 'menu_button', 'refresh_button'
StateViews.kt  — 'loading_state', 'loading_spinner', 'error_retry_button',
                 'empty_state_action'
ChatScreen.kt  — 'chat_input', 'send_button', 'interrupt_button'

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:15:53 +03:00
cc52442be3 fix(#65): request POST_NOTIFICATIONS runtime permission on Android 13+ (#97)
* fix(#65): request POST_NOTIFICATIONS runtime permission on Android 13+

On API 33+ the manifest declaration alone is not enough — the app must
request POST_NOTIFICATIONS via ActivityResultContracts.RequestPermission
at runtime before it can show any notification, including the foreground
service notification from ChatNotificationService.

- Added rememberLauncherForActivityResult for POST_NOTIFICATIONS
- Added LaunchedEffect(Unit) that fires the permission request on first
  composition when running on API 33+ and permission is not yet granted
- The next lifecycle event (ON_STOP → NotificationHelper.start) picks
  up the permission if granted

* fix: add missing import android.os.Build for POST_NOTIFICATIONS permission check

* fix: add missing import for rememberLauncherForActivityResult (androidx.activity.compose)

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:15:47 +03:00
27a896c634 docs(#70): sync project structure in README with actual codebase (#96)
- Removed references to HermesControlApp.kt, EmptyState.kt, LoadingState.kt
  (none of these files exist)
- Replaced with StateViews.kt, SharedComponents.kt
- Added data/ws/ (WebSocket TUI Gateway layer)
- Added notification/ (ChatNotificationService foreground service)
- Added util/ (CronExpressionFormatter)
- Added missing theme files: HermesStatusColors.kt, Spacing.kt, Shapes.kt
- Added settings/ and connect/ screen directories

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:15:41 +03:00
451412851f fix(#69): replace mipmap launcher icon with proper notification vector drawable (#95)
Notification smallIcon must use a simple mono vector drawable, not a
launcher mipmap. Launcher icons render broken in the status bar on
many devices.

- Added app/src/main/res/drawable/ic_notification.xml (simple info icon)
- Updated both showReplyNotification and buildForegroundNotification to
  reference R.drawable.ic_notification instead of R.mipmap.ic_launcher

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:15:36 +03:00
607a1b0439 fix(#73): surface clear errors for malformed pairing strings instead of silent fallback
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 21:09:19 +03:00
6375e3fa1d fix(#74): mark unparseable cron expressions instead of silently returning raw
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 20:52:44 +03:00