Commit Graph
50 Commits
Author SHA1 Message Date
M57 71463a5a9a feat(ui): add New Chat to History (#1124) 2026-09-15 11:06:24 +03:00
M57 4246ae196d feat(settings): restore last session on startup (#1102) (#1105) 2026-09-13 16:45:04 +03:00
M57 7e8af8db01 feat(sessions): show live status for active sessions in History (#1101) (#1104) 2026-09-13 14:45:33 +03:00
M57 5dc3896237 feat(sessions): branch tree flattening with group recency and collapsible pinned section (#1087) 2026-09-12 01:41:24 +03:00
Hy4ri 3d711c5a51 refactor(sessions): decompose SessionsScreen into focused components
Extract SessionCard, SearchResultCard, SessionActionMenu,
SessionsStatsRow, SessionsBulkActionBar, SessionsDialogs, and AutomationGroupList.
2026-09-11 19:28:52 +03:00
Hy4ri 6876db0393 refactor(sessions): extract search formatting and paging logic
Extract SessionSearchFormatting and SessionsPaging helpers.
Deduplicate paging calculation in SessionsViewModel and add unit tests.
2026-09-11 17:53:02 +03:00
M57 114654867f fix(sessions): use AutoMirrored KeyboardArrowRight to fix Compose deprecation 2026-09-05 23:19:16 +03:00
M57 1135f05011 refactor(sessions): show job name in automation groups and preserve action labels 2026-09-05 23:02:46 +03:00
AlexDev_ acc9c7ce9a feat(sessions): separate conversation and automation history 2026-09-05 19:55:57 +02:00
M57 2d0b6ae1a9 fix(history): resolve session resume profile scoping and history pagination (#1040) 2026-09-02 00:01:19 +03:00
M57andM57 908255f5ad feat: support session-level hidden flag in SessionListResponse and SessionsScreen (#1033)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-09-01 16:56:28 +03:00
M57andM57 7a2c453f58 fix(history): guard fluid pagination against infinite network loop on small session lists (#1002)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 19:44:28 +03:00
M57 fad49b7b66 feat(chat): compact sticky task progress chip (#942) (#943)
* feat(chat): compact sticky task progress chip (#942)

Re-add a glanceable progress strip above the chat timeline while work is
active. Distinct from the inline TodoTaskCard (permanent timeline record):
this chip is the temporary "what is happening right now" surface.

- Shows only while todos are incomplete or a subagent is running.
- Bounds to the same hydrated state.todos / subagentIndicators the card
  uses -- NOT the old broken state.todos gate removed in #811.
- Label: "Tasks 2/5 · <in-progress task> · N agent" (agent count when relevant).
- Tap opens SubagentInspectionSheet + resumes bottom-follow.
- Auto-hides when all work completes/cancels; rebuilds on session resume.
- i18n strings added (en/zh/ko); ktlint clean; compileDebugKotlin + unit
  tests green.

* refactor(chat): drop inline TodoTaskCard, keep single sticky progress chip

Per follow-up to #942: the inline todo card in the chat view is removed. The
sticky TaskProgressChip above the timeline is now the only glanceable surface;
the tool message itself remains the permanent record.

- Remove inline TodoTaskCard early-return in ToolBubble.kt (todo tool output
  now renders like any other tool bubble).
- Delete TodoTaskCard.kt + TodoTaskCardTest.kt (SecurityRiskChip lives in
  ToolBubble.kt, so risk display is unaffected).
- Redesign chip: replace the ElectricBolt icon with a real
  CircularProgressIndicator (no emoji), keep ChevronRight affordance.
- Label handles the no-todos case (subagents only): 'N agent running'.
- Drop dead todo_task_count string; add task_progress_agent_running (en/zh/ko).

ktlint clean; compileDebugKotlin + chat unit tests green.

* chore: clear pre-existing compile warnings

- HermesWsClient.kt: drop unnecessary safe call on non-null ResponseBody
  (resp.body.string()) and the now-dead 'body != null' guard it implied.
- SessionsScreen.kt: replace deprecated Icons.Filled.CallSplit with the
  AutoMirrored variant (match existing Send usage in this file).

ktlint clean; compileDebugKotlin SUCCESSFUL with no warnings.

* chore(chat): replace emoji status markers with Material icons in inspection sheet

The sticky progress chip opens SubagentInspectionSheet on tap; that sheet used
emoji Text glyphs for status. Replaced with real Material icons:

- Todo rows: completed -> CheckCircle (success), in-progress -> Autorenew
  (primary), cancelled -> Cancel (warning), pending -> RadioButtonUnchecked
  (muted).
- Subagent rows: complete -> CheckCircle (success), failed -> Cancel (error),
  running -> Autorenew (primary).
- Section headers: ElectricBolt -> FormatListBulleted (agent plan) + Groups
  (subagents).

Icons tinted via LocalHermesStatusColors. No emoji remain in the chip's tap
surface. ktlint clean; compileDebugKotlin SUCCESSFUL.

* fix(lint): drop orphaned todo_task_count translation in ko

Removing the string from the default (en) locale left a stale Korean
translation, which tripped Lint's ExtraTranslation error and aborted
lintDebug. Removing the orphan so default + translations stay in sync.

android-lint now BUILD SUCCESSFUL (0 errors).

* fix(chat): address #943 review - progress count, resume hydration, chip tests, plurals

- TaskProgressChip: current-task number is now the first unfinished,
  non-cancelled todo's 1-based position (active index + 1), so a completed
  task 1 + in-progress task 2 reads as 2/5 instead of 1/5 (review point 2).
- Deterministic resume hydration: hydrate todos directly from cached Room
  messages and from the merged REST transcript inside ChatViewModel load
  paths, in addition to the WS reducer, so a resumed session rebuilds the
  chip without waiting for a follow-up event (review point 3).
- Extracted computeChipDisplay / shouldShowProgressChip pure functions and
  added TaskProgressChipTest covering active-task progress, completed/cancelled
  hide, subagent-only state, and transcript-resume hydration (review point 4).
- agent count strings are now Android plurals (task_progress_agents /
  task_progress_agent_running) in en/zh/ko to avoid '2 agent' (review point 5).

Co-Authored-By trailer intentionally omitted per repo convention.

* fix(chat): derive chip number and content from the same selected task

computeChipDisplay selected the task number from the first unfinished/
non-cancelled todo but the content independently from the first in_progress
todo. With a pending task 1 + in_progress task 2 this produced 'Tasks 1/N ·
<task2 content>'. Now both values come from one selected item (prefer
in_progress, fall back to first pending/non-terminal).

Adds two focused tests: same-item selection, and pending-only fallback.
2026-08-21 00:03:50 +03:00
M57andM57 82e2d787af fix(sessions): show fork icon + order number on nested forked sessions (#934)
* fix(sessions): fork icon + depth number, no indentation

Multiply-nested forked sessions rendered with deep indentation that crushed
the card off-screen and made titles unreadable, and the per-sibling order
number was uninformative (mostly "1").

- SessionTreeItem: replace forkIndex (sibling order) with forkDepth (nesting
  level: 1 = child of a root, 2 = fork of a fork, ...); flattenSessionTree
  populates it.
- SessionCard: render CallSplit fork icon + the depth number (primary tint).
  Removed the depth-based start padding so names stay full-width readable —
  the icon+number now carries the structure instead of indentation.
- strings: sessions_fork_indicator (en/zh/ko).
- SessionTreeTest: assert isFork/forkDepth across nested fixtures.

No backend change — backend already returns parent_session_id per SessionInfo.

* test(ws): make testAutoReconnect tolerate transient RECONNECTING state

The test gated on connectionStatus.first { it == RECONNECTING } with a
15s timeout. With the test backoff forced to 0ms the RECONNECTING state is
emitted + consumed before the collector attaches, so the wait missed it and
timed out (kotlinx.coroutines.TimeoutCancellationException) — the flake CI
saw under parallel load.

- Replace the brittle RECONNECTING .first{} wait with a tolerant check that
  accepts RECONNECTING-or-later (the reconnect clearly fired).
- Keep connect2Latch (second socket opening) as the definitive proof + add a
  final CONNECTED assertion.
- Reset the test backoff in a finally so later tests never inherit 0ms.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-18 17:06:51 +03:00
M57andM57 685b870cad feat(history): pin sessions to the top of the history tab (#886)
- Hold-menu Pin/Unpin action on session cards (PATCH /api/sessions/{id}
  with {pinned}, verified against the backend SessionRename model)
- Pinned sessions sort to the top on every load (client-side stable sort
  — the backend back-fills pins into page 1 but does not lift them)
- Pin indicator on pinned cards; search-result cards omit the pin action
- Tests: pin-first sort on load, toggle success (moves to top), failure
  (order kept + toast)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-12 14:32:48 +03:00
M57andM57 95178d3438 feat(history): fluid infinite scroll + bigger pages for the sessions list (#885)
- Page size 20 -> 50 (matches the desktop sidebar; backend caps at 100)
- Auto-load the next page when scrolling within 6 items of the end,
  driven by real scroll position (layoutInfo-derived, not frozen state)
- Guard against offset-pagination churn: dedupe appended ids so a new
  session landing on top between page loads can't crash LazyColumn on a
  duplicate key
- Tests: append+dedupe, in-flight loadMore no-op guard

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-12 14:03:19 +03:00
M57andM57 57dc4cdf5c fix(sessions): Messages stat card + slim prune button (#855)
* fix(sessions): show Messages stat + slim prune button

The Active stat card always rendered 0: the mobile model declared
'val active: Int = 0', a key GET /api/sessions/stats never sends
(backend returns total/active_store/archived/messages/by_source), and
with ignoreUnknownKeys = true deserialization silently fell back to 0.

Rework the stats row instead of just key-mapping:
- Messages card replaces Active, reading the 'messages' key
  (db.message_count(), web_routers/sessions.py:523-550)
- Prune becomes a slim 52dp icon-only card (contentDescription keeps it
  accessible) so the Messages card keeps width for large counts

* feat(sessions): compact stat counts (100k / 1.23m) for Total & Messages

formatCompactCount renders counts as max 3 digits + unit letter:
987 -> '987', 100987 -> '100k', 1234567 -> '1.23m', 100000000 -> '100m'.
Applied to both the Total and Messages stat cards so large numbers fit
the row. Covered by SessionsStatsFormatTest (5 test cases).

* fix(sessions): label prune + empty-cleanup buttons, drop top select toggle

- Prune card now sizes to its label (icon + 'Prune' text) instead of a
  52dp icon-only square, staying compact via wrap-content width.
- Empty-session cleanup becomes a labeled FilledTonalButton (icon +
  'Delete empty sessions', count badge kept) instead of a bare icon.
- Removed the top-bar select toggle (SelectAll icon); selection mode
  remains reachable via long-press > Select on any session row.
- Dropped now-orphaned content_desc_enter/exit_selection strings (en/ko).

* fix(sessions): prune card wrap-content width + shorter cleanup label

The prune card dropped its fixed 52dp width but kept fillMaxSize on the
inner Box — as an unweighted Row child that expands to the full row
width, starving the weighted Total/Messages cards (reported broken on
device). The Box now fills height only, so the Card wraps to its
content (icon + 'Prune').

Empty-session cleanup button label shortened to 'Delete empty'.

* fix(sessions): stack prune label under icon, cleanup button reads 'Empty'

- Prune card: trash icon above the 'Prune' label (column layout),
  still wrap-content width so Total/Messages keep their space.
- Empty-session cleanup button label shortened to 'Empty'.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-09 15:35:42 +03:00
M57andM57 f20b50b48f feat(sessions): empty-session cleanup + latest-descendant resume + fresh-session 404 fix (#787, #840) (#841)
* feat(sessions): empty-session cleanup button + latest-descendant resume + fresh-session 404 fix (issues #787, #840)

- Sessions top bar: Delete-empty icon with count badge, grey/disabled when 0
  (GET /api/sessions/empty/count + DELETE /api/sessions/empty, confirm dialog)
- Resume flow resolves GET /api/sessions/{id}/latest-descendant first —
  branched sessions continue in the newest child leaf, not the stale parent
  (best-effort; falls back to the requested id)
- syncCurrentSession guarded by sessionHasServerPresence — a freshly created
  (never prompted) session has no server row yet; the REST transcript 404s
  and the resume retry spun forever (logcat 404 evidence)

* fix(sessions): clamp message-count fallback to backend limit cap (422)

fetchServerMessageCount requested limit=500 but the backend caps
session list page size at 100 (sessions.py Query le=100) — every
fallback scan 422'd (seen in device logcat after a branch). Sessions
are ordered recent, so the target is always in the top page.

* test(slash): make dispatch-capture assertions order-independent (CI flake)

The capture-race flake (SlashCommandDispatchRpcTest, hit 3x across CI and
local runs 2026-08-07): tests asserted on methodSlot.captured — the LAST
request captured — but the create flow's session.usage/context_breakdown
requests can land after the slash-dispatch one depending on suite order
(the class's own comment documents this cross-wiring). Converted the
three capture-based tests to capture ALL requests and find the target
record by method, with dispatch-before-fallback ordering for the 4018
test. Same assertions, no dependence on request interleaving.

* test(slash): convert remaining capture-race assertions (flake round 2)

The capture race moved to the focus/init tests (:424) after the first
three were fixed — every request-capture test in the class used the
same last-captured assumption. Converted the remaining five (init,
init+arg, focus, focus on, type=send) to the find-record-by-method
pattern; the type=send answers block reads methodCalls.last() since the
capture precedes the answer. Zero request() slot captures remain.

* test(slash): blocklist assertion counts command RPCs only (flake round 3)

The blocklisted-command test counted ALL request() calls and asserted
zero — the create flow's session.usage/context_breakdown noise can land
in the window after the stub registers (the same capture race that hit
the dispatch tests). Asserted instead that no COMMAND_DISPATCH or
SLASH_EXEC fires, which is the actual blocklist contract. E2E's static
Dispatchers mock stays (E2E VMs need IO routed to the test dispatcher —
removing it broke 20 E2E tests).

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-07 21:36:23 +03:00
M57andM57 9d0baa78fd fix(sessions): rename via PATCH /api/sessions/{id} + move actions to hold menu (issue #785) (#838)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-07 14:20:21 +03:00
M57andM57 f34bfa7cf8 feat(ws): consume gateway change events (issue #784) (#837)
* feat(ws): consume gateway change events (cron/sessions/platforms/pairing) for targeted refresh (issue #784)

* test(chat): complete catch-all request stub — kill frozen-collector capture race (slash-focus flake, 4645a2b CI)

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-07 13:48:17 +03:00
AlexDev_ 356ccc98b5 fix(notification): resume stored session before reply (#805) 2026-08-04 19:54:49 +03:00
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
M57 426f14550e feat(ui): redesign history prune button and compact search bar (#702) 2026-07-24 00:39:41 +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
M57andM57 e12a23befe fix: correct bulk session delete endpoint + surface deleted count (#653)
* fix: correct bulk session delete endpoint + surface deleted count

- Fix Retrofit @POST path from 'api/sessions/delete' to 'api/sessions/bulk-delete'
- Change response type from Response<Unit> to Response<BulkDeleteResponse>
- Add BulkDeleteResponse model (ok, deleted) matching backend contract
- Surface backend 'deleted' count in toast message instead of using client-side count

Backend contract (web_server.py:10750):
  POST /api/sessions/bulk-delete
  Body: { "ids": [...], "delete_all": false }
  Response: { "ok": true, "deleted": <count> }

Fixes #651

* fix: replace client-side session list filter with full refresh after bulk delete

- Call loadSessions() + loadStats() after successful bulk delete instead of
  filtering sessions/searchResults locally via 'ids' — fixes state desync
  when backend deletes fewer sessions than requested (partial success) and
  is correct for delete_all=true where ids may be empty
- Show 'No sessions were deleted' toast when deletedCount == 0
- Extract toastMsg to separate val to satisfy ktlint lint rules

Addresses review findings: UI state desync, delete_all handling, hardcoded
pluralizing, and zero-deletion UX.

---------

Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
2026-07-21 12:43:06 +03:00
6a421d7a84 feat(ux): add CTAs to EmptyState across screens (issue #618) (#627)
Turn dead-end empty states into on-ramps by wiring the existing
EmptyState actionLabel/onAction params to real next actions.

Screens wired (real, existing actions):
- Skills (installed): Browse skill hub -> setViewMode(HUB)
- Skills (hub): Retry search -> searchHub(hubQuery)
- Cron Jobs: Create your first job -> openNewJobDialog()
- MCP servers: Add a server -> toggleAddForm() (literal moved to strings.xml)
- Webhooks: Create subscription -> showCreateDialog() (literal moved to strings.xml)
- Channels: Connect a Telegram channel -> startTelegramOnboarding()
- Model: Configure model -> navigateTo(SettingsConnection)
- Sessions (history): Start a new chat -> navigateTo(ChatScreen)
- Analytics: Explore Skills or Cron jobs -> navigateTo(SkillsScreen)
- Keys (no match): Clear search -> query = ""

All new labels use strings.xml (empty_action_* / screenname_empty_*).

Deviations from issue (documented honestly):
- Toolsets, Profiles, Plugins keep Refresh. No in-app create path exists
  (toolsets are server-defined toggles; profiles/plugins have no create
  dialog wired in UI). Refresh is the correct recovery there, not a fake
  'create' CTA. Flagged for follow-up if create flows get built.
- Skills: edited the real private InstalledSkillsView in SkillsScreen.kt;
  the duplicate internal one in InstalledSkillsTab.kt is dead code.

Verified: ktlintCheck clean, assembleDebug green.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Co-authored-by: Ronia <ronia@hermes>
2026-07-18 16:42:32 +03:00
M57andHy4ri 9661246497 audit(#622): sweep hardcoded colors + add checkColorLiterals guard (#626)
Removed docs/color-hardcoded-audit.md per request. Replaced 2 hardcoded
Color literals with theme tokens; added CI guard blocking new literals.

Co-authored-by: Hy4ri <hy4ri@users.noreply.github.com>
2026-07-18 16:15:45 +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
M57 d1fc477bcd fix: resolve all build compilation and style check warnings (#597)
* fix: resolve all build compilation and style check warnings

* fix: remove unused import in SystemViewModel
2026-07-14 21:55:37 +03:00
M57andM57 c543eb6470 feat(#530): server-backed session search (FTS5) (#555)
* feat(#530): add SessionSearch models + searchSessions endpoint + contract tests

* feat(#530): wire server-backed search into Sessions screen (debounce + render)

* style(#530): ktlint import ordering on HermesApiService

* fix(#530): ktlint-clean overlap wrap + map search snippet to title

* fix(#530): map search snippet to title so results show + highlight

* fix(#530): render search hits as honest result cards instead of faking title

Backend SessionSearchResult carries no session name (only snippet + metadata),
so mapping snippet->title made the card show raw excerpt as the title. Instead,
render a dedicated SearchResultCard: 'Match' label + session id, highlighted
snippet as body, and source/model/played-at chips. Added SessionInfo.model and
a friendly relative-time formatter. ktlint clean, compileDebugKotlin + unit
tests pass.

* fix(#530): strip JSON from search snippet + tighten result card padding

* fix(#530): strip FTS highlight markers from search snippet on client

agy's backend change re-wraps matched terms in >>>term<<< markers inside the
clean snippet, but the mobile highlights by raw query term and never consumed
those markers — so literal '>>>'/'<<<' would render in result text. Strip the
markers before passing the snippet to highlightText so the query highlight
still works on clean text.

* fix(#530): integrate tree-flattening hierarchy with search modes in SessionsScreen

* fix(#530): fix test assertion and mock ApiClient in SessionsViewModelTest

* fix(#530): resolve compose PaddingValues compile error and fix slash-preservation test path

* fix(#530): clean search snippet from JSON payload and add unit test

---------

Co-authored-by: M57 <m57@hermes-control.app>
2026-07-14 12:47:59 +03:00
M57andM57 29b630a2ec fix(#586): route hardcoded colors through theme tokens (Material You / presets) (#592)
Co-authored-by: M57 <m57@hermes-control.app>
2026-07-14 00:57:12 +03:00
Genesi599 3052db7d65 feat: paginate, sync, and display branched sessions (#563)
* mobile: 以 Desktop Sessions 作为聊天历史源

* mobile: 修复快速连接崩溃与无标题会话隐藏

* mobile: 对齐 Desktop 标题并恢复完整历史

* mobile: 分离 Desktop 存储 ID 与运行 Session ID

* mobile: 将新对话持久化为 Desktop Session

* mobile: 渲染 Desktop 本地媒体

* feat: 分页加载并同步当前会话

* fix: 显示分支会话名称与层级

* fix: align PR with upstream app contract

* fix: preserve upstream session RPC lifecycle
2026-07-12 12:53:23 +03:00
M57andHermes Agent d181851670 feat(#441): add profile cloning and description editing (#463)
* feat(#441): add profile cloning and description editing

* feat(#441): add profile builder wizard and API enhancements

Introduce 5-step Profile Builder wizard into ProfilesScreen and add support for create/clone endpoints.

* fix(#441): restore imports and add ProfileInfo constructor defaults

* style: run ktlint format on codebase to fix styling errors

* fix(#441): address PR review comments including provider slug bug, validation, and strings

* fix(#441): remove unused McpServerConfigInput import

---------

Co-authored-by: Hermes Agent <hermes@agent.local>
2026-07-03 22:24:42 +03:00
M57andM57 e2349eca22 feat(#439): enrich sessions tab with dashboard features (#460)
* feat(#439): enrich sessions tab with dashboard features

Add session stats (total/active counts), per-session source icons,
active session badges, inline rename, bulk selection with toolbar,
prune old sessions dialog with configurable days threshold,
session prompt copy, single-session delete, and search highlighting.

- SessionInfo model: add source field
- New models: SessionStatsResponse, SessionRenameRequest,
  BulkDeleteRequest, PruneRequest, SessionPromptResponse
- HermesApiService: add stats, rename, bulk delete, prune, prompt endpoints
- SessionsViewModel: stats loading, bulk select, rename, delete, prune
- SessionsScreen: stats row, source icons, active border, rename inline,
  bulk mode with animated toolbar, prune dialog, search highlight
- strings.xml: new resources for all sessions UI features

* fix(#439): resolve CI compile errors

- Add explicit icon imports for Code, Language, Terminal
- Add missing clickable import
- Make highlightText non-composable by passing color params
- Fix combinedClickable usage on SessionCard

* fix(#439): address review findings

- Remove unused background import
- Add dedicated DELETE /api/sessions/{id} endpoint
- Refactor deleteSession to use dedicated endpoint
- Add confirmation dialogs for both single-session and bulk delete
- Surface statsError in the UI when stats load fails
- Add string resources for delete confirmation dialogs

* fix(#439): fix delegated property smart cast errors

- Capture statsError, errorMessage, sessionToDeleteConfirm in local vals
  before null checks to avoid 'Smart cast impossible' on delegated props

* fix(#439): remove copy and rename buttons, keep delete only

* fix(#439): restore CheckCircle import

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-03 17:55:26 +03:00
M57 85c4662537 feat: optimize network stack (#404)
* feat: optimize network stack and connection management

Refactor OkHttp connection pools, consolidate Gson serialization, add network-aware WebSocket reconnects, and deduplicate concurrent requests.

* test: update mapping assertions for AuthExpired

* refactor: address PR review feedback on network stack

Simplify health check, improve authenticator cookie matching robustness, resolve nit imports, and add unit tests.
2026-06-27 00:45:48 +03:00
M57andM57 4ba74c2efa fix(#219): add pagination to sessions history tab (#357)
* 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>
2026-06-25 00:58:52 +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 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 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
M57 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
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
M57 abe9e0e284 fix(#218): fallback to Untitled for empty or blank session titles (#244) 2026-06-21 19:13:09 +03:00
Hy4ri dcb23ba0a3 refactor(#78): extract UI strings for logs, system, model, and sessions/history screens 2026-06-21 11:41:41 +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
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
Hy4ri 402664b6f2 fix: fix compilation errors in SessionsScreen.kt 2026-06-19 23:09:55 +03:00
ronia 4327389b32 feat: dedicated Sessions browser screen (Issue #40) 2026-06-19 16:48:16 +03:00