Commit Graph
24 Commits
Author SHA1 Message Date
M57 5c1492a80e feat(chat): add animated GIF rendering with tap-to-play animation (#721) (#731)
* feat(chat): add animated GIF rendering with tap-to-play animation

Closes #721

* fix(#721): add missing GifImageThumbnail component and format ktlint

* fix(analytics): resolve ktlint indentation error in itemsIndexed

* feat(chat): add interactive play/pause toggle for animated GIFs

* fix(chat): control GIF playback via Animatable start and stop
2026-07-27 18:32:45 +03:00
M57andM57 2e97ee9a84 refactor: remove verified dead code across data, UI, and resources (#680)
* refactor: remove unused dead code across data, UI, and resources

* fix: resolve dangling references left by dead-code removal

Restore empty object Motion (braces required) and remove the 12 stale
test usages that still called the now-deleted production symbols:
- ServerStoreTest: removeServer/switchToServer tests
- ServerEndpointTest: isCleartext assertions
- ConnectViewModelTest: onHostChange/onPortChange tests + 3 dropped R.string refs
- SettingsViewModelTest: renameProfile tests + clearNavigateToLogin
- E2eIntegrationTest: previewSkill tests + onHostChange/onPortChange calls

Verified locally: compileDebugUnitTestKotlin + 48 tests pass.

* fix: remove unused imports flagged by ktlint

Drops 4 no-longer-referenced imports left by the dead-code removal:
- HermesApiService: SkillScanResponse (scanHubSkill removed)
- ServerEndpointTest: assertFalse, assertTrue (isCleartext assertions dropped)
- SettingsViewModelTest: assertFalse (rename tests removed)

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-22 15:51:07 +03:00
M57andHy4ri 0018e5d172 feat(#617): skeleton loaders instead of bare spinner on list screens (#625)
Adds SkeletonListState to StateViews.kt — an animated shimmer
placeholder list whose geometry matches a typical list row (avatar circle
+ title line + subtitle line). The shimmer colors derive from
MaterialTheme.colorScheme.surfaceVariant (theme-aware, works on
light/dark/AMOLED/Material You). Sweeps horizontally via an
infiniteTransition tween — no new dependencies, pure Compose foundation
animations.

Wired SkeletonListState into 22 list-loading call sites across 22 screens:
skills, cron, plugins, mcp, toolsets, webhooks, system, sessions (x2),
config (x2 — list + YAMLEditor), model, profiles, keys, channels,
achievements, gateways, kanban, logs, analytics, providers,
processes, hub skills tab, installed skills tab (list branch).

Preserved LoadingState() (centered spinner) where the issue explicitly
calls for it:
- PairingScreen (one-shot auth flow)
- SkillEditorDialog / ModelPickerDialog / CronJobEditorDialog interiors
- SkillPreviewDialog and SkillEditorDialog inline-loading branches in
  SkillsScreen.kt (lines 790, 914)
- InstalledSkillsTab dialog-interior branch (line 393)

SkeletonListState testTag: "skeleton_list_state".

Verified: ktlintCheck, assembleDebug, testDebugUnitTest all green locally.

Co-authored-by: Hy4ri <hy4ri@users.noreply.github.com>
2026-07-18 15:12:21 +03:00
M57andM57 dc1d5f2c7c fix(#614): implement ToastHost on Achievements/Sessions/Processes ViewModels (#615)
Add ToastHost interface + override keyword to the 3 ViewModels that
declared clearToast() without implementing the interface. This aligns
them with the other 17 ViewModels and enables polymorphic substitution
where ToastHost is expected.

Fixes #614

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-17 16:59:05 +03:00
M57andM57 d875c8a040 feat: richer Webhooks tab — create, toggle, delete subscriptions (#454)
* feat: richer Webhooks tab — create, toggle, delete subscriptions

Adds full webhook subscription management matching the dashboard UI:
- Create subscription dialog (name, description, events, deliver, secret)
- Per-subscription enable/disable toggle via PUT endpoint
- Delete with confirmation dialog
- Enhanced subscription cards: description, event chips, delivery badge,
  secret indicator, URL display
- Search filtering across name, description, and events
- Updated Webhook.kt model with full API shape
- New HermesApiService endpoints for CRUD operations

Closes #447

* fix: remove double top padding on webhooks screen LazyColumn

* fix: use string resources in webhooks screen (sweep hardcoded strings)

* fix: resolve unresolved merge conflict in AchievementsScreen

Leftover <<<<<<< markers from a main merge broke compilation on the
webhooks-full-parity branch, cascading through ktlint/lint/tests.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-03 00:52:49 +03:00
M57andM57 f887146056 feat(#433): achievements tab — full dashboard parity (#450)
* feat(#433): achievements 1:1 parity with dashboard

- Expanded model: AchievementsResponse now carries header stats
  (unlockedCount, discoveredCount, secretCount, totalCount, isStale,
  scanMeta/ScanStatus) and per-achievement fields (unlockedAt, secret, criteria)
- Added API endpoints: scan-status, rescan, reset-state, recent-unlocks
- Created AchievementIcon emoji mapper for all Lucide icon names
- Rewrote ViewModel with full state: scan tracking, recent unlocks,
  category/state filtering, rescan/reset actions with loading states
- Rewrote Screen: header stats row, scan status chip, stale indicator,
  category + state filter chips, recent unlocks section, expandable
  criteria, icon emoji, unlocked glow border, overflow menu with
  rescan/reset, reset confirmation dialog
- Added 30 string resources for all new UI elements

* fix: resolve CI compilation errors in achievements module

- Replace progress_pct references with correct Kotlin field progressPct (Achievement model uses @SerializedName for snake_case, Kotlin field is camelCase)

- Add missing onStart/onError parameters to safeLaunchLoad calls in loadScanStatus() and loadRecentUnlocks()

* fix: remove top padding on achievements tab content

* fix: remove redundant paddingValues on achievement LazyColumn

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-02 23:48:44 +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 740b4fe601 Refactor: Consolidate navigation items into ScreenRegistry (#310)
* Refactor: Consolidate navigation items into ScreenRegistry

* fix(#310): remove unused imports, fix ktlint import ordering

* feat(#289): add ScreenRegistry for centralized screen configuration

* fix(#310): fix ktlint when-condition blank lines in Navigation.kt

* fix(#289): derive availableNavItems from ScreenRegistry

* fix(#310): use addEntryProvider instead of reified entry for dynamic screen registration

* fix(#310): remove double top padding from kanban, channels, achievements screens

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-24 18:19:38 +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 558a66a1de fix(#253): remove excessive top padding from screens (#341)
The Achievement, Kanban, and Channels screens were applying double
padding at the top because `HermesScaffold` already handles the container
padding internally. This change removes the redundant `paddingValues`
from the inner UI components on these screens.

Closes #253
2026-06-23 12:45:44 +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 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
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
Hy4ri 44e2006434 refactor(#78): extract UI strings for remaining batch 2.3 screens 2026-06-21 11:55:18 +03:00
M57 46b24593f9 feat(#58): implement skill markdown/yaml editor UI (#121) 2026-06-19 23:58:34 +03:00
Hy4ri 508aea8bfa fix(#79): centralize retrofit error handling with NetworkResult 2026-06-19 22:58:33 +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
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
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
M57 8b4f5f91e0 style: fix ktlint violations (unused imports + line length) 2026-06-16 01:22:48 +03:00
Hy4ri d6ff3de594 Initial commit of HermesControl Android app 2026-06-16 00:00:36 +03:00