Commit Graph
14 Commits
Author SHA1 Message Date
M57andM57 ab42e19b7f theme(849): unify presets behind one palette template + bespoke AMOLED status colors (#852)
* refactor(theme): unify all presets behind one palette template

* docs(theme): update THEMES.md and AGENTS.md for the palette template

* fix(theme): restore per-preset error slot contrast, merge StatusColors into HermesStatusColors

- onErrorContainer becomes an explicit per-preset field (was derived from
  onError — same-color pairs in Gruvbox/Catppuccin light made error text
  invisible; 6 screens render this slot)
- Default dark + AMOLED onError -> White (were <3:1 on their error color)
- drop StatusColors + statusColorsOf mapper; PaletteColors.status is typed
  HermesStatusColors directly (identical field lists, pure copy ceremony)
- ThemePalette getters now by lazy; schemeFor/statusFor accessors;
  requireNotNull on the default-theme fallback (DefaultTheme is FULL)
- resolvers internal for testability

* test(theme): contrast, mode-validation, and fallback contract tests

- every shipped mode's onError/error + onErrorContainer/errorContainer
  pair meets >= 3:1 WCAG contrast (kills the error-slot regression class)
- ThemeMode init validation rejects illegal palette combinations
- AMOLED darkOnly contract + light-mode fallback to Default light

* refactor(theme): Catppuccin — named swatches from the official palette spec

Hex values verified identical (zero color drift): repeated colors (Crust,
Surface0/1, Base, Text...) now live once as named swatches matching
catppuccin.com; bespoke light primary container tints stay local.
Mocha/Latte pair unchanged.

* refactor(theme): Gruvbox — authentic bright/faded accent scheme with named swatches

Redesign per gruvbox spec: bright accents on dark bg0-bg4, faded accents
on light bg0-bg4 (correct contrast play for light mode). Named swatches,
single Gray shared across modes. Dark error pair fixed for >= 3:1
(onErrorContainer -> DarkFg1 cream, was bright red on Bg3 = 1.87:1).

* feat(theme): replace default preset with Slate — modern slate blues + teal accents

The brand default (voltage purple/amber) becomes Slate: bright teal/blue
accents on dark bg0-bg4, faded variants on light bg0-bg4. Named swatches,
single neutral gray shared across modes. Slate is also the fallback for
darkOnly/lightOnly presets (AMOLED light mode).

* feat(theme): add Nord preset — Polar Night/Snow Storm surfaces, Frost/Aurora accents

Nord's single accent set is reused across both modes (pastel accents stay
light, so on* colors use Polar Night dark text in both modes — documented
in-file). Surface ladder shares outer tiers (Nord ships 4 dark + 3 light
tones for 5 tiers). Dark error pair fixed for >= 3:1 (onErrorContainer ->
Nord6, was Nord11 on Nord2 = 2.0:1). Wired into ThemePreset, the settings
picker, and docs; default label now reads Default (Slate).

* refactor(theme): Monochrome + AMOLED — pure grayscale ramp, true-black AMOLED variant

Monochrome: every swatch R=G=B, roles by lightness only (status colors
included — documented trade-off: pair with icons/labels). AMOLED: dark-only
true-black variant (pure #000000, OLED pixels off; #121212 baseline of
Monochrome doesn't do that), containers collapse where nothing darker
exists. Both ship maximum-contrast grayscale pairs (12.8:1+ on error
slots).

* chore(theme): drop Neon Noir, finalize docs + picker label

- remove NeonNoirScheme.kt + ThemePreset.NEON_NOIR everywhere (dispatcher,
  picker, strings EN/KO, tests, AGENTS.md, THEMES.md) — 6 presets remain:
  Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Nord
- picker label for the default preset is now plain 'Default'
- THEMES.md: preset conventions section (named swatches, bright/faded
  split, grayscale-status caveat, contrast test gate) + verify step runs
  testDebugUnitTest

* fix(theme): make drawer icon colors visibly follow the active preset

Selected icon now tints with the theme's primary accent (teal for Slate,
orange for Gruvbox, mauve for Catppuccin, frost-blue for Nord) instead of
onPrimaryContainer — the new presets' containers are all dark gray with
near-white text, so the selected row looked identical across themes.
Unselected icon/text colors pinned explicitly to onSurfaceVariant.

* chore(ui): remove lightning emoji from the app

- drawer title + About name: '⚡ Hermes' -> 'Hermes' (EN + KO)
- skill markers (chat self-improvement badge, memory graph): ⚡ -> 🛠
- in-progress indicators (subagent inspection): ⚡ -> 🔄

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-08 15:58:25 +03:00
M57andM57 1cca7764cc perf: optimize data loading across Config, System, Profiles, and Billing tabs (#678)
* perf(model): optimize model tab load speed with two-phase fetch and loadQuick

* perf: optimize data loading across Config, System, Profiles, and Billing tabs

* chore: remove leftover bottom nav references and unused updatePrimaryScreens

* style: fix ktlint violations in Config/Profiles/Billing ViewModels

- property/argument-list wrapping for async(...) calls
- multiline-expression wrapping for async blocks
- remove unused import in ConfigViewModel
- break long error-message string literals to stay under 120 cols

---------

Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
2026-07-22 15:03:30 +03:00
M57 86c780c10b docs: sync AGENTS.md, README.md, and CONTRIBUTING.md with current codebase (#644)
* docs: update AGENTS.md, README.md, and CONTRIBUTING.md

Sync all three docs with current codebase state:
Kotlin 2.4.10, Kotlinx Serialization, 28 screens,
6 theme presets, CI instrumented-tests job, color guard.

* docs: missed control
2026-07-20 18:27:16 +03:00
M57andHy4ri 0a0b612d3a refactor(#619): kill DRAWER_GESTURE_SCREENS — screen-owned drawer gestures via HermesScaffold (#624)
Replaces the fragile DRAWER_GESTURE_SCREENS global set + defensive
LaunchedEffect(snapTo(Closed)) + closeDrawer callback workaround with a
single source of truth: each screen declares its drawer-gesture preference
via HermesScaffold's new drawerGesturesEnabled parameter (default true).

Architecture:
- DrawerGestureController holds the active gesture state. HermesScaffold
  reconciles each screen's preference into it via SideEffect; when a screen
  opts out while the drawer is open, the controller closes the drawer itself.
- LocalDrawerGestureController CompositionLocal flows the controller from
  MainNavigation down to ModalNavigationDrawer, which reads .enabled and
  passes it to Material's gesturesEnabled parameter.
- DisableDrawerGestures() helper for entry screens that don't use
  HermesScaffold (Landing, AuthLogin, PairingCodeEntry).
- All 6 Settings drill-down sub-pages pass drawerGesturesEnabled = false.
- NavigationController.closeDrawer removed; navigateTo no longer needs a
  synchronous drawer-dismiss hook.

Removes: DRAWER_GESTURE_SCREENS set, both LaunchedEffect(gesturesEnabled)
and LaunchedEffect(drawerGesturesEnabled) blocks, drawerGesturesEnabled
local state, closeDrawer callback on NavigationController.

Fixes the recurring drawer-scrim stuck-open bug class (PRs #445, #454, #455).

Verified: ktlintCheck, assembleDebug, testDebugUnitTest all green locally.

Co-authored-by: Hy4ri <hy4ri@users.noreply.github.com>
2026-07-18 14:25:23 +03:00
M57 85a35c58ce docs: update AGENTS.md — generic build section, remove system-specific rules (#499)
- Replace machine-specific build instructions (/opt/android-sdk paths)
  with generic gradle commands any contributor can use
- Remove Ronia-specific agent-author rule that would confuse external
  contributors
- Remove kanban-internal bug annotation format from commit conventions
- Reword stale 'no SDK' caveat in Things to Avoid to match new build section
2026-07-08 13:13:28 +03:00
M57andM57 57a815c67e feat(#446): plugins tab full parity — install from URL, provider selection, remove confirm, badges (#456)
* docs: document HermesScaffold padding foot-gun root cause in AGENTS.md

* feat(#446): plugins tab full parity — install from URL, provider selection, remove confirm, badges

- Add Install from URL section with force reinstall + enable after install toggles
- Add Plugin Provider selection section (memory provider + context engine dropdowns)
- Add rescan button in top bar actions
- Add confirmation dialog before removing plugins
- Show source badges, runtime status, auth badges on plugin cards
- Show dashboard slots from plugin manifest
- Add visibility toggle (show/hide in sidebar) for manifest plugins
- Show orphan dashboard plugins section
- Add PluginManifestData, PluginsHubProviders, ProviderOption models
- Add rescanPlugins, savePluginProviders, setPluginVisibility API endpoints

* fix(#446): fix CI compilation errors — DashboardPluginMeta mapping and @Composable context

* fix(#446): pass missing PluginInfo ctor params for orphan plugin mapping

* fix(#446): remove duplicate refresh button in plugins top bar

Scaffold's onRefresh param added a second refresh icon alongside the
rescan action button. Removed onRefresh/isRefreshing from HermesScaffold
and moved pull-to-refresh into a manual PullToRefreshBox wrapping the
content, keeping the rescan icon as the sole top-bar action.

* fix(#446): remove rescan icon from plugins top bar

Only one action icon left in top bar; pull-to-refresh (loadPlugins)
remains the sole refresh mechanism. rescanPlugins() viewmodel logic
is unchanged but now unreachable from the UI.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-07-03 15:31:43 +03:00
M57 d80e4dc7b1 docs: add #424 lessons — drawer scrim fix, ViewModel state cleanup, WS lifecycle patterns 2026-06-30 17:13:29 +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
M57andM57 efba360d8a fix(ci): add release-compile safeguard to catch debugImplementation regressions (#336)
- Reverts okhttp.logging dependency from debugImplementation back to
  implementation so it's on the release classpath
- Adds compileReleaseKotlin CI job to catch release-variant compilation
  errors on every PR
- Fixes signing config to allow compileReleaseKotlin without keystore env
  vars (graceful fallback instead of hard require())
- Updates AGENTS.md with dependency scoping pitfall and CI docs

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 05:46:11 +03:00
M57andM57 fbc4ee2b75 fix(#295): update test count, correct AGENTS.md DAO return type
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 15:19:47 +03:00
M57andM57 c6d7a45905 fix(#135): guard goBack() against empty back stack (#157)
goBack() now uses ChatScreen as a fallback when the stack has
only one item, preventing the Settings back-button crash.

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

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

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

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

---------

Co-authored-by: ronia <ronia@m57>
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 20:45:37 +03:00
M57andronia a8600668c2 docs: add AGENTS.md with build, style, and workflow guidance for AI coding agents (#84)
Co-authored-by: ronia <ronia@m57>
2026-06-19 20:17:55 +03:00