Commit Graph
677 Commits
Author SHA1 Message Date
Hy4ri 45f78443ab feat(#60): format tool output JSON for readability 2026-06-19 23:28:16 +03:00
Hy4ri 8755338636 Revert "Merge pull request #42 from Hy4ri/feat/sessions-screen"
This reverts commit 7d50b5acd7, reversing
changes made to ee97be50cb.
2026-06-19 23:18:26 +03:00
M57 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
Hy4ri 402664b6f2 fix: fix compilation errors in SessionsScreen.kt 2026-06-19 23:09:55 +03:00
Hy4ri 508aea8bfa fix(#79): centralize retrofit error handling with NetworkResult 2026-06-19 22:58:33 +03:00
M57andM57 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
M57andM57 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
M57andM57 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
M57andM57 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
M57andM57 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
M57andM57 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
11d97a2d5f fix(#75): replace non-null assertion on simpleName with safe mapNotNull
* ci: prevent concurrency cancellation on rapid main merges

The concurrency group used github.ref which is static for main
(refs/heads/main). When multiple PRs merged rapidly (~2 min apart),
each merge commit cancelled the previous CI run before it could finish.

Fix: use github.sha for push events to main (unique per commit) while
keeping the ref-based group for PR branches (cancels on new commits to
the same PR branch).

* refactor(#82): remove dead SkeletonList/SkeletonCard composables

* fix(#72): echo server's WebSocket close code instead of hardcoded 1000

* fix(#75): replace non-null assertion on simpleName with safe mapNotNull

---------

Co-authored-by: ronia <ronia@m57>
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 20:48:48 +03:00
16e5973dc9 fix(#72): echo server's WebSocket close code instead of hardcoded 1000
* ci: prevent concurrency cancellation on rapid main merges

The concurrency group used github.ref which is static for main
(refs/heads/main). When multiple PRs merged rapidly (~2 min apart),
each merge commit cancelled the previous CI run before it could finish.

Fix: use github.sha for push events to main (unique per commit) while
keeping the ref-based group for PR branches (cancels on new commits to
the same PR branch).

* refactor(#82): remove dead SkeletonList/SkeletonCard composables

* fix(#72): echo server's WebSocket close code instead of hardcoded 1000

---------

Co-authored-by: ronia <ronia@m57>
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 20:45:42 +03:00
24847cf0d6 refactor(#82): remove dead SkeletonList/SkeletonCard composables
* ci: prevent concurrency cancellation on rapid main merges

The concurrency group used github.ref which is static for main
(refs/heads/main). When multiple PRs merged rapidly (~2 min apart),
each merge commit cancelled the previous CI run before it could finish.

Fix: use github.sha for push events to main (unique per commit) while
keeping the ref-based group for PR branches (cancels on new commits to
the same PR branch).

* refactor(#82): remove dead SkeletonList/SkeletonCard composables

---------

Co-authored-by: ronia <ronia@m57>
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 20:45:37 +03:00
Hy4ri 0525398700 Refactor: Delete dead CommandPanel component (#81) 2026-06-19 20:20:52 +03:00
Hy4ri 23ff263641 Fix WebSocket connection stuck on subsequent launches (Issue #45) 2026-06-19 19:56:50 +03:00
Hy4ri 4ecde51e2c Consume window insets in NavDisplay to eliminate keyboard gaps (Issue #59) 2026-06-19 19:47:49 +03:00
Hy4ri 80578b93d6 Render SearchBarRow inside the main Column container below the top app bar 2026-06-19 19:36:20 +03:00
Hy4ri 3734cb361f Use height-constrained CompactSearchInput inside SearchBarRow to prevent topbar height dynamic expansion 2026-06-19 19:17:55 +03:00
Hy4ri 11c5da260d Pin top app bar in ChatScreen and support pinTopBar parameter in HermesScaffold 2026-06-19 19:09:04 +03:00
Hy4ri f7132a76cd Fix chat search breaking vertical scrolling and layout (Issue #44) 2026-06-19 18:59:43 +03:00
Hy4ri 055b3381b1 Implement human-readable cron expression formatter on CronJobsScreen 2026-06-19 18:49:41 +03:00
M57 3e093aaa72 Merge pull request #53 from Hy4ri/fix/issue-47-reorder-nav-items
feat: add bottom nav reordering in settings (closes #47)
2026-06-19 18:46:09 +03:00
Hy4ri 2fce6ad97a feat(settings): add up/down arrow buttons to reorder bottom nav items 2026-06-19 18:41:02 +03:00
Hy4ri 54ebe8714e fix(chat): remove double padding and zero out nested Scaffold window insets 2026-06-19 18:37:48 +03:00
Hy4ri 0cdf8fd779 fix(navigation): show bottom bar on all screens except ConnectScreen 2026-06-19 18:31:37 +03:00
Hy4ri 3a610c7c80 fix(ci): fix trailing spaces and revert visibility/padding changes 2026-06-19 18:23:17 +03:00
Hy4ri 9e3a7ab339 fix: always show bottom bar, resolve chat input double padding, and format codebase 2026-06-19 18:14:41 +03:00
Hy4ri 91ad09b5f2 fix(navigation): dynamically update bottom navigation items 2026-06-19 18:02:49 +03:00
ronia 4327389b32 feat: dedicated Sessions browser screen (Issue #40) 2026-06-19 16:48:16 +03:00
M57andronia 59e0d4f817 feat: search within chat history (Issue #31) (#39)
- Add search icon toggle in top bar with animated search field
- Real-time message filtering on search query match
- Match counter with previous/next navigation arrows
- Case-insensitive text highlighting in user and assistant bubbles
- Dismiss search restores full message list
- Empty state for no search results

Co-authored-by: ronia <ronia@m57>
2026-06-19 16:35:07 +03:00
M57 fb605d1bc0 feat: clickable URLs and long-press copy in chat bubbles (Issues #29, #30) (#36)
Add clickable URL detection with primary color + underline styling and browser launch via UriHandler. Add long-press to copy raw message text to clipboard for both user and assistant chat bubbles.

Fixes #29, Fixes #30
2026-06-19 14:16:56 +03:00
ronia 5d4f211f17 fix: bottom navigation bar customization not reflected in NavigationBar (Issue #37) 2026-06-19 13:35:57 +03:00
M57 66da56ca24 feat: connection status indicator in drawer (Issue #28)
feat: connection status indicator in drawer (Issue #28)
2026-06-19 12:40:32 +03:00
M57 27e513f0a3 feat: show app version in Settings screen (Issue #32)
feat: show app version in Settings screen (Issue #32)
2026-06-19 12:39:20 +03:00
ronia 71d3b975af style: fix ktlint import ordering (LaunchedEffect before collectAsState) 2026-06-19 12:31:43 +03:00
ronia 1db55c57c6 style: fix ktlint import ordering in Navigation.kt 2026-06-19 12:21:20 +03:00
ronia a2c9bb638a style: fix ktlint errors in Navigation.kt 2026-06-19 12:19:29 +03:00
M57 0038606c05 feat: add connection status indicator (Issue #28)
Adds a colored dot next to the '⚡ Hermes' brand header in the
navigation drawer:
  - Green: connected
  - Yellow: connecting / reconnecting
  - Red: disconnected

Uses a StateFlow on HermesWsClient that tracks the WebSocket
connection lifecycle and is collected in the drawer header.
2026-06-19 12:07:44 +03:00
M57 12c60dce16 feat: show app version in Settings screen (Issue #32)
Displays version name and build code at the bottom of the Settings
screen using BuildConfig.VERSION_NAME and VERSION_CODE.
2026-06-19 12:03:37 +03:00
M57 1c1961426a fix: migrate deprecated APIs (Issues #25, #26, #27)
- Icons.Filled.ArrowBack/ListAlt → Icons.AutoMirrored.Filled (Issue #25)
- ScrollableTabRow → PrimaryScrollableTabRow (Issue #26)
- fallbackToDestructiveMigration() → fallbackToDestructiveMigration(false) (Issue #27)
2026-06-19 12:02:07 +03:00
M57 34df198c4d feat: configurable bottom navigation bar (Issue #23)
## What
Adds a new Navigation Bar section to Settings where users can customize which items appear in the bottom navigation bar.

## Features
- Pick from any of the 19 drawer screens (max 5 items)
- Remove items with a close/delete button
- Items persist via SharedPreferences (CSV format)
- Invalid names are silently dropped
- Existing screens and auth-aware filtering respected

Closes #23
2026-06-18 21:16:21 +03:00
M57andM57 837678c590 fix(#21): remove double spacing above top bar and below chat input (#22)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 20:36:56 +03:00
M57andM57 4892572bcc feat(#13): add pull-to-refresh to HermesScaffold (#14)
- HermesScaffold: added isRefreshing param, wraps content in PullToRefreshBox when onRefresh is set
- All 16 data screens: pass isRefreshing = state.isLoading
- Initial loading only shows full-screen LoadingState when data is empty (no spinner over existing content)
- AchievementsScreen already had the correct pattern, just needed isRefreshing param

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 18:58:08 +03:00
M57 d37de1d223 feat(#11): make HermesScaffold title a @Composable slot, convert ChatScreen (#12)
- HermesScaffold: title now @Composable () -> Unit (M3-idiomatic)
- HermesScaffold: added modifier and snackbarHost parameters
- Updated all 18 callers from title="X" to title={Text("X")}
- ChatScreen: converted from raw Scaffold to HermesScaffold
- Removed unused Menu icon import (ktlint fix)
2026-06-18 18:43:45 +03:00
M57andM57 7c27ca624c refactor(#5): convert SettingsScreen to use HermesScaffold
* refactor(#5): convert SettingsScreen to use HermesScaffold

Replace raw Material3 Scaffold + manual TopAppBar with the shared
HermesScaffold wrapper. Removes ~30 lines of duplicated boilerplate
and gives SettingsScreen scroll-aware top bar behavior.

Closes #5

* fix(#5): restore ExperimentalMaterial3Api import for SegmentedButton

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 18:00:51 +03:00
M57andM57 a36be6e432 fix(#4): replace private EmptyState with shared component in ChatScreen
Remove ~30 lines of duplicated code by using the shared EmptyState
from ui/common/StateViews.kt instead of the private version.

Closes #4

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 17:51:52 +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
ronia f833005bec fix(settings): route topbar Settings through NavigationController + Clear Token button
B7 (Jun 18 2026): user reported Settings page unresponsive when
reached via the ChatScreen topbar gear icon, but worked fine via
the drawer sidebar. Two fixes:

1. Route topbar gear through NavigationController.navigateTo()
   instead of raw backStack.add(). The NavigationController path
   (used by the drawer) deduplicates against the current top
   entry and matches the drawer's navigation flow exactly. The
   bypass path could stack duplicate SettingsScreen entries on
   rapid taps, which likely caused the unresponsive symptom.

2. Add 'Clear Token' OutlinedButton below the Auth Token field in
   Settings. One-tap wipes a corrupted / rejected token and saves
   immediately, without having to select-and-delete the masked
   password field character by character. Recovers from the
   'I gave you a bad token' scenario in seconds.
2026-06-18 03:56:04 +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