Commit Graph
22 Commits
Author SHA1 Message Date
M57 cbbda06157 feat(#500): tap-to-reveal detail dialog for truncated item descriptions (#501)
* feat(#500): tap-to-reveal detail dialog for truncated cards

Add shared DetailDialog composable and wire tap-to-open full metadata
dialogs across Skills (installed + hub), MCP servers, Plugins, Toolsets,
and Webhooks screens. Action buttons stay on the card (sticky); tapping
the card body reveals untruncated description + key metadata.

Refs: #500

* fix(#500): resolve compile errors from missing runtime imports + dialog scope

- add missing androidx.compose.runtime.mutableStateOf/remember imports
  in InstalledSkillsTab + HubSkillsTab (caused 'Unresolved reference'
  cascade on compileDebugKotlin)
- move PluginsScreen detail dialog inside the composable function scope
  (was nested in OrphanPluginCard -> showDetail unresolved)
- DetailDialog rows type: Pair<String?,String?> -> Pair<String,String?>
  (key always non-null; fixes type-inference failure)
- drop unnecessary safe call on non-null label

Verified locally with the Android SDK:
  ./gradlew :app:compileDebugKotlin  -> BUILD SUCCESSFUL
  ./gradlew :app:testDebugUnitTest   -> BUILD SUCCESSFUL

Refs: #500

* refactor(#500): extract toDetailRows() mappers + semantic status tones; wire SkillsScreen hub browse

- add DetailRows.kt with @Composable extension mappers for Skill/HubSkill/
  McpServer/PluginInfo/Toolset/WebhookSubscription -> List<DetailRow>
- DetailDialog now renders status rows via LocalHermesStatusColors (review #2):
  SUCCESS/NEUTRAL/WARNING/ERROR/INFO tones instead of plain onSurfaceVariant
- wire SkillsScreen.HubBrowseView/HubSkillCard tap -> DetailDialog (same UX as
  other screens) — user request
- remove JVM-signature-clashing Pair overload; drop unused Color import
- add detail_dialog_trust_level string (HubSkill has no version field)
- verified: compileDebugKotlin + testDebugUnitTest PASS locally w/ ANDROID_HOME

* fix(#500): full hub description in dialog + no name truncation

- HubBrowseView (both SkillsScreen + HubSkillsTab): on card tap, fetch
  previewHubSkill(identifier) to get the FULL SkillContentResponse.content
  (search snippet was truncated); dialog shows full content once loaded,
  with a spinner while previewing. Falls back to snippet if no identifier.
- SkillsViewModel: add hubPreviewContent/isHubPreviewing/hubPreviewError
  state + previewHubSkill()/clearHubPreview() (uses existing preview endpoint)
- DetailDialog: title now wraps fully (removed maxLines=2 ellipsis cap) so
  long skill names are never cut off
- verified: compileDebugKotlin PASS + ktlint clean (local ANDROID_HOME)
2026-07-08 22:50:03 +03:00
M57 ca32e37e2f refactor(ui): split monolithic screen files into focused components (#485)
* refactor(ui): extract SkillsStep to components package

Move the 252-LOC SkillsStep composable out of ProfilesScreen.kt into
ui/profiles/components/SkillsStep.kt. Body is verbatim (behavior-preserving);
only package + imports changed. Parent now imports and calls it directly.

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).
Note: sibling screens resolve Modifier.weight() without an explicit import;
matching that convention (no spurious weight import) to avoid the
internal RowColumnParentData.weight shadowing error.

* refactor(ui): extract ProfileBuilderView cluster to components package

Move the Profile Builder wizard (ProfileBuilderView + validateProfileName +
IdentityStep/ModelStep/McpStep/AddMcpDialog/ReviewStep) out of ProfilesScreen.kt
into ui/profiles/components/ProfileBuilderView.kt. Body is verbatim
(behavior-preserving); only package + imports changed.

Notable wiring required:
- validateProfileName kept as internal fun: it is also used by the parent's
  clone dialog, so both the screen and the builder share it via import.
- ProfilesUiState/ProfilesViewModel imported from the ui.profiles package.
- SkillsStep call resolves to the already-extracted sibling file (same package).

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).

* refactor(ui): extract MoaConfigDialog cluster to components package

Move the Mixture-of-Agents config dialog (MoaConfigDialog + MoaConfigEditor +
MoaSlotPicker + MoaPickerTarget sealed class) out of ModelScreen.kt into
ui/model/components/MoaConfigDialog.kt. Body is verbatim (behavior-preserving);
only package + imports + entry visibility changed.

- Entry MoaConfigDialog is internal (called by ModelScreen root); the editor,
  slot picker, and sealed class stay private to the new file.
- The @Composable annotation preceding the declaration is moved with the
  cluster (earlier attempt left it orphaned, causing a duplicate-annotation
  compile error at the next declaration).

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).

* refactor(ui): extract PlatformCard + TelegramOnboardingDialog clusters

Move two self-contained clusters out of ChannelsScreen.kt:
- PlatformCard.kt: StateStyle (private data class) + platformStateStyle +
  StateBadge + PlatformCard. Entry PlatformCard is internal (called by
  ChannelsScreen root); StateStyle/StateBadge/platformStateStyle stay private.
- TelegramOnboardingDialog.kt: TelegramOnboardingDialog. Entry internal.

Wiring required:
- ConfigureForm kept in ChannelsScreen as internal (shared by PlatformCard
  and the screen root) and imported into PlatformCard.kt.
- ChannelsUiState/OnboardingPhase imported from the ui.channels package.
- formatTimestamp left in the screen (used by ConfigureForm).

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).

* refactor(ui): extract SkillsScreen sub-composables to components package

Split the 914-line SkillsScreen.kt into the screen root plus 4 cohesive
component files under ui/skills/components/:
- SourceBadge.kt: shared SourceBadge (internal, used by both tabs)
- InstalledSkillsTab.kt: InstalledSkillsView + SkillCard + SkillPreviewDialog
- HubSkillsTab.kt: HubBrowseView + HubSkillCard
- SkillEditorDialog.kt: SkillEditorDialog

Wiring required:
- CATEGORY_ALL promoted private -> internal in SkillsScreen (shared by the
  screen root and InstalledSkillsView) and imported where needed.
- SkillFilter / SkillsUiState imported from the ui.skills package.
- HubBrowseView now reads isHubSearching/hubResults/hubSearchError off its
  SkillsUiState param (fields, not locals) - no behavior change.

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).

* refactor(ui): extract SettingsScreen + SystemScreen sub-composables

SettingsScreen.kt (1238 -> ~340): move self-contained sections into
ui/settings/components/ - ConnectionSection (+TestResultCard/SaveIndicator/
TestConnectionButton), BehaviorSection, AppearanceSection (+ChatSection/
NavBarSection), AboutSection, ProfileEditorDialog (+DeleteProfileConfirmDialog).
Shared leaves SectionCard/InfoRow kept in the screen as internal and imported
by the extracted files.

SystemScreen.kt (1755 -> 1619): move CredentialEntryRow + HookCard into
ui/system/components/ (both internal). formatBytes/formatDuration helpers stay
in the screen (used by the root and remaining content).

Verified: ktlintCheck PASS, compileDebugKotlin PASS (local SDK).

* fix(#485): use LocalHermesStatusColors for PlatformCard state colors

Map platform connection-status badges to semantic LocalHermesStatusColors
slots (success/warning/error) + add neutral slot for not_configured/disabled.
Removes hardcoded hex (Color(0xFF...)) and MaterialTheme.colorScheme usage
flagged by Sourcery; neutral defaults to StatusGrey so existing theme
providers are untouched.
2026-07-07 23:00:24 +03:00
M57andM57 b46e0ef46d fix: correct FilterChipRow and EmptyState API mismatches in Skills screen (#466)
* fix: correct FilterChipRow and EmptyState API mismatches + ktlint fmt

* fix: add missing Save import, fix verticalArrangement double-wrapping

* fix: address Sourcery review — strings, enums, hubQuery, StatusColors

• Hardcoded strings → stringResource (Installed, Browse Hub, Statuses, All sources)
• Magic string filters → SkillFilter enum + CATEGORY_ALL constant
• FilterChipRow made generic with chipLabel parameter
• HubBrowseView hubQuery driven from ViewModel state (no dupe)
• ViewModel toasts use getString(R.string.*) via AndroidViewModel
• MaterialTheme.colorScheme.error → LocalHermesStatusColors.current.error

* fix: OpenDrawer → Menu, listContentPadding is PaddingValues not ext

NavIcon.OpenDrawer doesn't exist, use NavIcon.Menu. listContentPadding is a val PaddingValues, not a Modifier extension — use Modifier.padding(listContentPadding).

* fix: NavIcon.Menu(onClick) -> NavIcon.Menu(onOpen)

* fix: pass mockApp to SkillsViewModel in unit tests (now AndroidViewModel)

* fix: mock getString in unit tests for AndroidViewModel string resources

* fix: anyVararg<Any?>() → anyVararg<Any>() to satisfy type bound

anyVararg<T> has bound T : Any, so Any? doesn't compile.
Any is a subtype of Any? at the JVM level, so the matcher
works correctly for vararg Any? parameters.

* fix: use anyVararg<String>() for getString mock to match actual runtime types

The previous anyVararg<Any>() did not match getString(Int, Object...) vararg
calls at runtime — mockk's relaxed mock returned '' for all formatted string
resources, causing all Skills error-path assertions to fail (9 tests).

Root cause: mockk vararg matchers match by reified element type. The Java
method Context.getString(int, Object...) passes format args as String at
runtime (via Kotlin's spread). anyVararg<Any>() with Any (boxed) doesn't
match Object/String vararg elements reliably, but anyVararg<String>() does,
since String satisfies the T : Any bound and matches the actual runtime type.

Also added getString(Int) stub for the no-arg overload used by enums.

* fix: stub getString per-resource-ID to bypass mockk Java vararg bug

mockk has a known bug matching Java vararg methods like
Context.getString(int, Object...) with anyVararg<T>() — the matcher
silently fails to match, and the relaxed mock returns '' for all
formatted string resource calls. This caused 8 Skills error-path tests
to fail (errorMessage/toastMessage was '' instead of containing 'HTTP 500').

Root cause: confirmed via test report artifacts — errorMessage was
non-null but empty string, proving the vararg stub never matched.

Fix: replace the generic anyVararg<String>() stub with explicit per-
resource-ID stubs using eq() + any<String>() matchers. Each formatted
string resource is stubbed individually, returning the format arg
(the dynamic error message) so the test assertions find 'HTTP 500'
and 'Timeout' substrings.

References:
- https://stackoverflow.com/questions/54326914/using-mockk-to-match-any-varargs
- https://github.com/mockk/mockk/issues/224

* fix(skills): replace AndroidViewModel getString() with hardcoded strings to fix unit tests

The SkillsViewModel was using getApplication<Application>().getString() for
error/success messages. In unit tests, mockk cannot reliably stub Java vararg
Context.getString(int, Object...), causing NullPointerException inside
coroutine onError callbacks. All other ViewModels in the codebase use hardcoded
strings; this change aligns SkillsViewModel with that convention.

* test: add hub search/install/uninstall/preview coverage + rm vestigial stubs

- Remove unused mockApp.getString() per-resource-ID stubs (SkillsViewModel
  no longer calls getApplication().getString() — hardcoded strings now).
- Add 10 tests covering hub search (success/empty/error/emptyQuery),
  hub install (success/error), hub uninstall (success/error), and skill
  preview (success/error). Uses existing mockk + runTest patterns.

* fix: correct import ordering for HubSkill (H < S)

Move HubSkill import before KanbanBoard (H < K) and remove duplicate
that was between SessionListResponse and Skill.

* test: remove coVerify from emptyQuery test (cross-test mock state)

* fix: remove double-stacked padding in SkillsScreen (HermesScaffold handles it)

Per AGENTS.md — HermesScaffold already offsets for the top bar
internally, so .padding(padding) on inner content creases double-stacked
top padding.

* refactor: merge preview + edit button in SkillCard into single adaptive action

Per AGENTS.md — two nearly-identical icon buttons (Preview + Edit)
side by side was confusing. Merged into one Edit button since
both opened the same skill content dialog (editable for installed,
read-only for hub is handled by the ViewModel).

* fix: revert onCategoryChange to non-null String (FilterChipRow<T> needs it)

Also removes leftover Preview icon import that ktlint flagged.

* refactor: restructure SkillCard layout — name gets full width, actions below

The old layout crammed icon, name, source badge, edit button, uninstall
button, AND toggle switch all on one horizontal row. Long skill names
got truncated to 2-3 characters.

New layout:
- Top row: icon | name (weight 1f) | source badge | toggle switch
- Below: category (if present)
- Below: description (if present)
- Bottom row: edit + uninstall buttons (end-aligned, only if hub source)

The name Text now has weight(1f) so it fills all available space.

* fix: fix brace nesting + move icon to far left for extra name space

* fix: add missing height import

* refactor: remove Extension icon from SkillCard for cleaner layout

* fix: remove double-stacked padding in SkillEditorDialog and SkillPreviewDialog

Both dialogs had the AGENTS.md footgun: HermesScaffold already offsets
the top bar internally, but the inner Box had .padding(padding) stacking
an extra ~56dp gap between the header and the edit text field.

* fix(skills-hub): unwrap SkillHubSearchResponse wrapper

Backend changed /api/skills/hub/search from returning a raw JSON
array to an object wrapper: { results: [...], source_counts: {...}, ... }

- Added SkillHubSearchResponse, SkillHubInstalledEntry models
- Extended HubSkill with identifier, trustLevel, repo, tags
- Updated API endpoint return type to SkillHubSearchResponse
- ViewModel unwraps data?.results.orEmpty() instead of data.orEmpty()

* fix: unwrap SkillHubSearchResponse in ProfilesViewModel too + remove unused HubSkill import

* fix: update test mocks to use SkillHubSearchResponse wrapper

* fix: refactor HubSkillCard layout — remove icon, name gets full width, install below

* fix: use unique key for hub results + weight(1f) to prevent scroll crash

* fix(skills): resolve crash due to duplicate keys in lazy list

* feat(skills): move search hub button into trailing icon of search bar

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-04 12:54:01 +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 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
Hy4ri cdad31b555 refactor(#78): extract UI strings for chat, settings, connect, kanban, and skills screens 2026-06-21 11:38:33 +03:00
Hy4ri 19d44c077b chore: format codebase with ktlint 2026-06-21 10:57:18 +03:00
M57 63575272c9 feat(skills): add category and status filters (#111) (#199) 2026-06-21 00:49:58 +03:00
M57andronia 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
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
7daf73ce0d feat(ui): modern theme, sectioned drawer, shared components (v1) (#1)
* feat(ui): modern theme, sectioned drawer, shared components (v1)

Foundation for the modern redesign — fixes the biggest UX problems
(that vague "better ui ux" ticket) without rewriting all 20 screens
at once. This puts the infrastructure in place and modernizes the
highest-traffic screens.

Theme (
  - Disable Material You dynamic color by default — brand identity
    (Voltage Purple) wins. Can opt back in via HermesControlTheme.
  - 3-step elevation ladder (surface / surfaceVariant / surfaceHigh)
    with subtle blue tint so dark mode stops reading as flat black.
  - Full Material 3 type hierarchy — every style explicitly tuned,
    tighter letter spacing, comfortable line heights.
  - New Shapes.kt (xs→xl rounded corner tokens) and Spacing.kt
    (xs/sm/md/lg/xl/xxl spacing tokens) — no more ad-hoc dp values.

Navigation (
  - Sectioned drawer: 17 items were dumped at one indent level with
    duplicate icons (Channels & Chat both used the Chat icon!). Now
    grouped into 4 logical sections — Converse / Automate / Configure
    / Inspect — with section headers and sane per-item icons.
  - Settings added to the drawer (was previously hidden behind
    ChatScreen's gear icon — bad IA).
  - gesturesEnabled check collapsed from an 18-term OR chain into a
    single set-membership test.

Common components (
  - HermesScaffold: shared drawer-aware Scaffold + TopAppBar with
    slots for refresh / actions. Removes the per-screen duplication
    seen in SkillsScreen, CronJobsScreen, ChannelsScreen, etc.
  - StateViews: LoadingState / ErrorState / EmptyState / list content
    padding tokens — every list screen now renders loading/error/
    empty identically instead of ad-hoc per screen.

Modernized screens:
  - ConnectScreen: hero brand badge (was a lone "⚡" glyph), pairing
    as the primary path, manual token/host/port collapsed behind a
    toggle (was always-visible clutter), Card-in-Card nesting removed.
  - SkillsScreen: now uses HermesScaffold + EmptyState + ErrorState +
    LoadingState. Adds a search field, leading icon per row, and a
    category sublabel.

Follow-up commits will migrate the remaining ~15 list screens onto
HermesScaffold; this commit stops at the foundation + the two screens
that see the most daily use.

Verified: ktlint clean across the entire tree.

Co-Authored-By: Ronia <ronia@m57>

* fix(imports): add missing safeDrawingPadding + dp imports

CI caught three compile errors that ktlint doesn't flag (unresolved
references are not lint errors, they're compile errors):

- Navigation.kt:239  — Modifier.safeDrawingPadding() import was
  dropped when the file was rewritten for the sectioned drawer.
- SkillsScreen.kt:117 — `16.dp` and `12.dp` references had no
  `androidx.compose.ui.unit.dp` import after the file was rewritten
  onto HermesScaffold.

No code changes — pure import fixes.

Co-Authored-By: Ronia <ronia@m57>

* ci: split monolithic job into 4 parallel jobs + summary

Before — one job ran ktlint → lint → test → build sequentially. If
ktlint failed you waited ~30s to find out, and the test/build stages
never even started (no useful artifacts uploaded on partial failure).

After — 4 jobs run in parallel on every push and PR:
  1. ktlint          (fast feedback, ~30s)
  2. android-lint    (parallel, includes compileDebugKotlin)
  3. unit-tests      (parallel, JUnit 5 + mockk)
  4. build           (gated by 1+2+3, only runs if all pass)

Plus a 5th `ci-summary` job that aggregates results so branch
protection only needs a single status check configured.

Other improvements:
- concurrency cancel-in-progress — new commits on the same ref
  cancel the stale run instead of piling up.
- timeout-minutes on every job — no more hung runners burning minutes
  on a stuck Gradle daemon.
- workflow_dispatch — can trigger manually from the Actions tab.
- Per-job artifact uploads with `if: always()` — reports upload even
  when the job fails, so you can download test/lint reports to debug.
- Removed the monolithic "verification-reports" artifact that tried
  to upload three unrelated paths and failed silently with "No files
  were found" when an early step bailed.

Co-Authored-By: Ronia <ronia@m57>

* ci: silence ktlint artifact-not-found annotation

Adds `if-no-files-found: ignore` to the ktlint artifact upload step
because ktlint 1.2.1 does not produce HTML reports on success — only
on failure. The previous config `if-no-files-found: warn` (default)
emitted a noisy annotation on every green run.

Co-Authored-By: Ronia <ronia@m57>

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Co-authored-by: Ronia <ronia@m57>
2026-06-18 02:59:43 +03:00
Hy4ri 0562906634 fix: replace refresh text button with refresh icon across all screens (Kanban, Cron Jobs, Skills, Channels, Plugins, System) 2026-06-16 00:41:27 +03:00
Hy4ri d6ff3de594 Initial commit of HermesControl Android app 2026-06-16 00:00:36 +03:00