* 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>
* feat: add Activity feed showing cron jobs that produced output
- New ActivityItem data model for unified notification feed
- SessionInfo extended with preview/started_at fields
- GET /api/sessions?source=cron endpoint added with source param
- ActivityViewModel fetches cron run sessions, filters for output
- ActivityScreen shows chronological feed with status icons
- Wired into navigation (AUTOMATE drawer section)
- Only shows runs with message_count > 0 (signal-driven)
Closes#402
* fix: add @Composable to statusColor helper (MaterialTheme access)
* fix: Double type for started_at (backend returns float timestamps)
* fix: use GET /api/cron/jobs for Activity feed (no-agent jobs don't create sessions)
Root cause: no-agent cron jobs (jellyfin, battery monitor, etc.) run
scripts directly and NEVER create DB sessions. The previous approach
using GET /api/sessions?source=cron only showed 3 old agent-driven
sessions.
Fix: use GET /api/cron/jobs which tracks last_run_at + last_status for
ALL job types. Filter last_status == "ok" (job ran and delivered).
* fix: match codebase patterns for ViewModel, Scaffold, and NavIcon
- safeApiCall returns NetworkResult, use when(is) not onSuccess/onFailure
- HermesScaffold navigationIcon expects NavIcon.Menu/Back sealed class
- Screen composable param must be onOpenDrawer (capital O)
* fix: correct ActivityScreen API mismatches
- NavIcon.Menu param is onOpen, not onClick
- EmptyState param is subtitle, not description
- Add missing viewModel import
* fix: padding, clickable cards, and navigation on Activity screen
- Fix double padding by using listContentPadding + listItemSpacing
matching the codebase pattern
- Add clickable cards that navigate to CronJobsScreen
- Remove unused scroll and spacer workarounds
* refactor: replace separate Activity feed with delivered-only filter in Cron tab
- Remove ActivityScreen, ActivityViewModel, ActivityItem
- Remove ActivityScreen from navigation keys and registry
- Add showDeliveredOnly toggle to CronJobsUiState
- Add TaskAlt icon button in cron top bar toggles filter
- Jobs with lastRunStatus != "ok" hidden when filter is on
* feat: clickable cron job cards show run-details dialog
- Clicking a job card opens an AlertDialog with last run info
- Shows status, last run time, schedule, error (if any), script
- RunDetailRow helper composable for label/value pairs
* fix: add missing CronJob import in CronJobsScreen
* fix: use snake_case field names for CronJob model
- lastRunAt -> last_run_at, lastError -> last_error
- These are data class constructor params, not computed properties
* refactor: remove delivered-only filter toggle, keep card-click dialog
- Remove TaskAlt filter icon button from Cron tab top bar
- Remove showDeliveredOnly from state and toggleDeliveredFilter() method
- Simplify cron list back to direct state.jobs with no filter layer
* chore: remove dead activity_empty strings
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Implements a full-screen cron job editor dialog following the skills editor pattern:
- Create/edit/delete cron jobs with all backend fields
- CronJobRepeat data class matching backend object shape
- Fixed Gson crash on cron tab load
* feat(#195): add chat attachment support for files and images
- Add Attachment data model with computed properties (isImage, formattedSize, fileExtension)
- Extend ChatMessage with optional attachments list
- Add Coil 2.7.0 for async image loading in Compose
- Extend ChatViewModel with pending attachment state, addAttachment/removeAttachment/clearAttachments
- Build attachment-prefixed message text (data URIs for images, file refs for others)
- Add paperclip button and file picker launcher (ActivityResultContracts.GetContent) to ChatScreen
- Add animated attachment preview chips with thumbnails before sending
- Render attachments inline in UserBubble (AsyncImage for images, file cards for others)
- Add string resources for attachment UI features
* fix(#195): rewrite attachment upload protocol to match Hermes backend RPC methods
BREAKS: removes old buildAttachmentText() that embedded base64 data URIs in prompt text.
Changes:
- Replace inline data-URI encoding with proper backend RPC flow:
- image.attach_bytes for images (fire-and-forget, auto-queues to session)
- file.attach for non-image files (awaited, returns @file: ref text)
- Add CompletableDeferred to PendingRequest so sendMessage can await RPC responses
- Add readContentUriBytes() to read file content from content:// URIs
- Add sendRpcAndAwait() suspend function for sequential RPC calls
- Add RpcCallException for awaited RPC error handling
- Suppress error UI for awaited RPCs (caller handles them directly)
- Keep ChatScreen/ChatBubble UI layer unchanged (file picker, previews, inline images)
* feat(#195): add camera capture support via FileProvider + TakePicture contract
- Creates temporary photo files in cacheDir via FileProvider
- Launches system camera with ActivityResultContracts.TakePicture
- Adds camera icon button (📷) next to the paperclip in ChatInputBar
- Captured photos added as image/jpeg attachments via same addAttachment flow
- FileProvider configured with cache-root path in res/xml/file_paths.xml
- Error handling for launch failures and capture failures with snackbar
* refactor(#195): group camera/image/file into single attach dropdown
- Replaces separate 📷 and 📎 buttons with a single 📎 button
- DropdownMenu opens with three options:
- 📷 Camera → TakePicture via FileProvider temp URI
- 🖼️ Image → GetContent("image/*")
- 📄 File → GetContent("*/*")
- Removes onAttachClick, adds onImageTap/onFileTap callbacks
- Input bar stays clean with one attachment affordance
* fix(#195): add missing imports for CI compilation
ChatScreen.kt:
- android.util.Log
- androidx.compose.animation.core.* (infiniteRepeatable, tween, LinearEasing, RepeatMode, rememberInfiniteTransition)
- androidx.compose.foundation.layout.widthIn
- androidx.compose.material.icons.filled.AttachFile
- androidx.compose.material.icons.filled.InsertDriveFile
- androidx.compose.ui.layout.ContentScale
- coil.compose.AsyncImage
ChatBubble.kt:
- androidx.compose.material.icons.filled.InsertDriveFile
* fix(#195): resolve android-lint LocalContextGetResourceValueCall error
Replace context.getString(R.string.chat_camera_error) with a
composable-level cameraErrorMsg captured via stringResource()
to avoid querying resources via LocalContext.current in a callback.
* fix(#195): migrate InsertDriveFile to AutoMirrored, fix chip double-remove
- Replace deprecated Icons.Filled.InsertDriveFile with
Icons.AutoMirrored.Filled.InsertDriveFile in ChatBubble.kt
and ChatScreen.kt
- Remove onClick from AttachmentChip Surface so only the
dedicated close button triggers removal, not accidental taps
on the chip body
---------
Co-authored-by: Hermes Agent <hermes@agent.local>
* feat(#369): rewrite channels tab with full backend integration
- Add state, error, gateway_running, description to model
- Add test endpoint to API service
- Rewrite ViewModel with toggle, restart, test actions
- Rewrite screen with state badges, restart banner, test button
- Fix string resources and E2E test
* fix(#417): add missing import and fix ButtonSize API
- Add MessagingPlatformTestResult import to HermesApiService.kt
- Replace ButtonDefaults.ButtonSize.SMALL with ButtonDefaults.SmallButtonSize (correct M3 API)
* fix(#417): mark platformStateStyle as @Composable, remove Button size param
- @Composable annotation needed for MaterialTheme.colorScheme access
- Button/OutlinedButton size parameter not available in project's M3 version
- ktlint removed unused ButtonDefaults import
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
* feat(#365): add pin support for models with pinned section at top
* fix(#365): stub Pinned Models methods in E2eIntegrationTest
* fix(#365): use state flow for pin/unpin, add max cap, add ViewModel tests
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
* fix(#219): add pagination to sessions history tab
The /api/sessions endpoint was called without pagination params, so the server returned only its default limit of 20 sessions.
- SessionListResponse: added total/limit/offset fields from server response
- HermesApiService: added @Query params (limit, offset, order) to getSessions()
- SessionsViewModel: added loadMore() that appends pages, hasMore derived from total
- SessionsScreen: added 'Load more sessions' button + loading spinner at bottom of list
* chore: remove plan file from git
* fix: compilation errors in sessions pagination
* chore: gitignore plan files
* fix: remove duplicate brace
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
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