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>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-22 15:51:07 +03:00
1 parent 58420d7b2a
commit 2e97ee9a84
29 files changed
+10 -550

No files matched your search

@@ -8,14 +8,6 @@ fun ServerStoreState.addOrReplaceServer(profile: ConnectionProfile): ServerStore
return copy(connectionProfiles = updated)
}
fun ServerStoreState.removeServer(id: String): ServerStoreState {
val updated = connectionProfiles.filter { it.id != id }
val newSelected = if (selectedProfileId == id) null else selectedProfileId
return copy(connectionProfiles = updated, selectedProfileId = newSelected)
}
fun ServerStoreState.switchToServer(id: String?): ServerStoreState = copy(selectedProfileId = id)
fun ServerStoreState.selfHealed(): ServerStoreState {
val hasActive = connectionProfiles.any { it.id == selectedProfileId }
val newSelected = if (selectedProfileId != null && !hasActive) null else selectedProfileId
@@ -5,7 +5,6 @@ import androidx.datastore.core.Serializer
import com.m57.hermescontrol.data.remote.ServerEndpoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -22,7 +21,6 @@ class ServerStore(
) {
private val _stateFlow: MutableStateFlow<ServerStoreState>
val stateFlow: StateFlow<ServerStoreState>
val state: Flow<ServerStoreState>
init {
val initial =
@@ -35,7 +33,6 @@ class ServerStore(
}
_stateFlow = MutableStateFlow(initial)
stateFlow = _stateFlow.asStateFlow()
state = stateFlow
}
fun getLatestState(): ServerStoreState = _stateFlow.value
@@ -450,18 +450,6 @@ object AuthManager {
fun getPort(): Int = serverStore.getLatestState().resolvedPort
fun setHost(host: String) {
val endpoint = endpoint()
val normalizedHost = host.trim().removePrefix("[").removeSuffix("]")
setBaseUrl(endpoint.baseUrl.newBuilder().host(normalizedHost).build().toString())
}
fun setPort(port: Int) {
require(port in 1..65535) { "Port is out of range" }
val endpoint = endpoint()
setBaseUrl(endpoint.baseUrl.newBuilder().port(port).build().toString())
}
// ── Auto-reconnect ───────────────────────────────────────────────────
fun isAutoReconnect(): Boolean = serverStore.getLatestState().autoReconnect
@@ -125,11 +125,6 @@ data class UsageBar(
// ── subscription.preview ──────────────────────────────────────────────────
@Serializable
data class SubscriptionPreviewRequest(
val subscription_type_id: String,
)
@Serializable
data class SubscriptionPreviewResponse(
val ok: Boolean? = null,
@@ -184,11 +179,6 @@ data class SubscriptionResumeResponse(
// ── subscription.upgrade ──────────────────────────────────────────────────
@Serializable
data class SubscriptionUpgradeRequest(
val subscription_type_id: String,
)
@Serializable
data class SubscriptionUpgradeResponse(
val ok: Boolean? = null,
@@ -1,7 +1,6 @@
package com.m57.hermescontrol.data.remote
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@Serializable
@@ -12,11 +11,6 @@ private data class PasswordLoginPayload(
val next: String,
)
@Serializable
private data class WebSocketTicketPayload(
val ticket: String,
)
/** JSON serialization for authentication requests and responses. */
object AuthPayloads {
fun passwordLogin(
@@ -31,11 +25,4 @@ object AuthPayloads {
next = "",
),
)
fun webSocketTicket(body: String): String? =
runCatching {
OkHttpProvider.json.decodeFromString<WebSocketTicketPayload>(body)
.ticket
.takeIf { it.isNotBlank() }
}.getOrNull()
}
@@ -77,13 +77,6 @@ fun wrapSessionCookie(rawValue: String): Cookie? {
}.getOrNull()
}
/**
* Build a permissive [Cookie] from a Set-Cookie `hermes_session_at=...`
* header captured during login. Used by [PersistentCookieJar] when the server
* does not echo an explicit domain (common for same-host dashboard logins).
*/
fun sessionCookieFromValue(value: String): Cookie? = wrapSessionCookie(value)
const val SESSION_COOKIE_NAME = "hermes_session_at"
/**
@@ -76,7 +76,6 @@ import com.m57.hermescontrol.data.model.SkillContentResponse
import com.m57.hermescontrol.data.model.SkillHubInstallRequest
import com.m57.hermescontrol.data.model.SkillHubSearchResponse
import com.m57.hermescontrol.data.model.SkillHubUninstallRequest
import com.m57.hermescontrol.data.model.SkillScanResponse
import com.m57.hermescontrol.data.model.StatusResponse
import com.m57.hermescontrol.data.model.SystemStatsResponse
import com.m57.hermescontrol.data.model.TelegramOnboardingApplyRequest
@@ -211,14 +210,6 @@ interface HermesApiService {
@Query("identifier") identifier: String,
): Response<SkillContentResponse>
@GET("api/skills/hub/scan")
suspend fun scanHubSkill(
@Query("identifier") identifier: String,
): Response<SkillScanResponse>
@GET("api/skills/hub/sources")
suspend fun getHubSources(): Response<List<String>>
@POST("api/skills/hub/install")
suspend fun installHubSkill(
@Body body: SkillHubInstallRequest,
@@ -21,9 +21,6 @@ class ServerEndpoint private constructor(
val baseUrl: HttpUrl,
val securityWarning: String?,
) {
val isCleartext: Boolean
get() = baseUrl.scheme == "http"
fun resolve(relativePath: String): HttpUrl {
val path = relativePath.trim().trimStart('/')
require(path.isNotEmpty()) { "Endpoint path must not be blank" }
@@ -24,7 +24,6 @@ object WsMethods {
// ── Commands catalog ──────────────────────────────────────────────────
const val COMMANDS_CATALOG = "commands.catalog"
const val COMMAND_DISPATCH = "command.dispatch"
const val COMMAND_RESOLVE = "command.resolve"
const val SLASH_EXEC = "slash.exec"
const val CONFIG_SET = "config.set"
@@ -77,21 +77,12 @@ val StatusGreyLight = Color(0xFFF5F5F5)
// ── Chat-specific colours (always brand-defined for consistency) ───────
val UserBubble = HermesPurple
val UserBubbleDark = HermesPurpleDark
val AssistantBubble = Color(0xFF1C1C26)
val AssistantBubbleLight = Color(0xFFEDEAF4)
val SystemMessageColor = Color(0xFF8B879A)
val ToolChipColor = Color(0xFF262633)
val ToolChipColorLight = Color(0xFFE6E3EE)
// ── Skeleton shimmer colours ───────────────────────────────────────────
val SkeletonBase = Color(0xFF1C1C26)
val SkeletonHighlight = Color(0xFF2F2F3D)
val SkeletonBaseLight = Color(0xFFE6E3EE)
val SkeletonHighlightLight = Color(0xFFEFEDF4)
// ── Syntax highlighting tokens (code blocks) ─────────────────────────────
// VS Code Dark+ palette — good general readability across all presets.
// These are consumed exclusively by CodeBlockCard (MessageCards.kt).
@@ -1,78 +1,13 @@
package com.m57.hermescontrol.theme
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.spring
import androidx.compose.runtime.staticCompositionLocalOf
/**
* Motion tokens — duration, easing, and spring specs.
*
* Design intent (ui-ux-pro-max §7):
* - Micro-interactions: 150–300 ms
* - Complex transitions: ≤ 400 ms
* - Never exceed 500 ms
* - Enter: ease-out (decelerated), Exit: ease-in (accelerated)
* - Exit shorter than enter (~60–70 %)
* - Spring physics for interactive elements
* - All animations must be interruptible
*
* Access via `LocalMotion.current` in composables.
*/
object Motion {
// ── Duration tokens (ms) ─────────────────────────────────────────
/** Instant — state changes with no perceptible delay. */
const val INSTANT = 0
/** Fast — micro-interactions, ripples, opacity toggles. */
const val FAST = 150
/** Normal — standard UI transitions, sheet slides, expansion. */
const val NORMAL = 250
/** Slow — complex multi-element transitions, screen navigation. */
const val SLOW = 400
// ── Easing curves ────────────────────────────────────────────────
/** Emphasized decelerate — for ENTERING elements (ease-out). */
val emphasizedDecelerate: Easing =
CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f)
/** Emphasized accelerate — for EXITING elements (ease-in). */
val emphasizedAccelerate: Easing =
CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
/** Standard — default M3 easing for neutral transitions. */
val standard: Easing = FastOutSlowInEasing
// ── Spring specs ─────────────────────────────────────────────────
/** Bouncy spring for interactive elements (press, release, drag). */
fun <T> bouncySpring(): SpringSpec<T> =
spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium,
)
/** Snappy spring for state toggles (switch, selection). */
fun <T> snappySpring(): SpringSpec<T> =
spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessHigh,
)
/** Gentle spring for layout animations (expand, collapse). */
fun <T> gentleSpring(): SpringSpec<T> =
spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow,
)
}
object Motion
val MotionDefaults = Motion
@@ -86,24 +86,6 @@ class AchievementsViewModel : ViewModel(), ToastHost {
)
}
fun loadScanStatus() {
safeLaunchLoad(
apiCall = { safeApiCall { api.getAchievementScanStatus() } },
onStart = { /* silent background refresh */ },
onSuccess = { status ->
_uiState.update {
it.copy(
scanState = status.state,
scanLastError = status.lastError,
scanLastDurationMs = status.lastDurationMs,
scanRunCount = status.runCount,
)
}
},
onError = { /* silent — scan runs independently */ },
)
}
fun loadRecentUnlocks() {
safeLaunchLoad(
apiCall = { safeApiCall { api.getRecentUnlocks() } },
@@ -260,6 +260,4 @@ class BillingViewModel : ViewModel() {
}
}
}
fun clearActionMessage() = _uiState.update { it.copy(actionMessage = null) }
}
@@ -260,10 +260,6 @@ class ChannelsViewModel :
)
}
fun dismissRestartNeeded() {
_uiState.update { it.copy(restartNeeded = false) }
}
// ── Telegram onboarding ────────────────────────────────────────────────
fun startTelegramOnboarding() {
@@ -502,10 +498,6 @@ class ChannelsViewModel :
}
}
fun dismissOnboardingError() {
_uiState.update { it.copy(onboardingError = null) }
}
override fun clearToast() {
_uiState.update { it.copy(toastMessage = null) }
}
@@ -91,7 +91,6 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.theme.SystemMessageColor
import com.m57.hermescontrol.theme.ToolChipColor
import com.m57.hermescontrol.theme.ToolChipColorLight
import com.m57.hermescontrol.theme.UserBubble
import com.m57.hermescontrol.theme.onColorFor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -1607,10 +1607,6 @@ class ChatViewModel(
// ── UI actions ───────────────────────────────────────────────────────
fun toggleSessionPicker() {
_uiState.update { it.copy(showSessionPicker = !it.showSessionPicker) }
}
/**
* Dismiss the active clarify prompt and reject it (tell the agent no answer
* was given).
@@ -96,20 +96,6 @@ class ConnectViewModel(
_uiState.update { it.copy(baseUrl = trimmed, transportWarning = warning, errorMessage = null) }
}
fun onHostChange(value: String) {
val endpoint = AuthManager.endpoint()
val next = endpoint.baseUrl.newBuilder().host(value.trim()).build().toString()
onBaseUrlChange(next)
}
fun onPortChange(value: String) {
val portText = value.filter { c -> c.isDigit() }
val port = portText.toIntOrNull() ?: return
val endpoint = AuthManager.endpoint()
val next = endpoint.baseUrl.newBuilder().port(port).build().toString()
onBaseUrlChange(next)
}
fun connect() {
val state = _uiState.value
if (state.token.isBlank()) {
@@ -234,10 +234,6 @@ class SessionsViewModel : ViewModel(), ToastHost {
}
}
fun exitSelecting() {
_uiState.update { it.copy(isSelecting = false, selectedIds = emptySet()) }
}
fun toggleSessionSelection(id: String) {
_uiState.update {
val updated = it.selectedIds.toMutableSet()
@@ -258,14 +254,6 @@ class SessionsViewModel : ViewModel(), ToastHost {
// ── Rename ───────────────────────────────────────────────────────────
fun startRenaming(sessionId: String) {
_uiState.update { it.copy(renamingSessionId = sessionId) }
}
fun cancelRenaming() {
_uiState.update { it.copy(renamingSessionId = null) }
}
fun renameSession(
sessionId: String,
newTitle: String,
@@ -308,27 +296,6 @@ class SessionsViewModel : ViewModel(), ToastHost {
}
}
// ── Copy prompt ──────────────────────────────────────────────────────
fun copySessionPrompt(sessionId: String) {
viewModelScope.launch {
val result =
safeApiCall {
ApiClient.hermesApi.getSessionPrompt(sessionId)
}
when (result) {
is NetworkResult.Success -> {
val promptText = result.data.prompt ?: "No prompt available"
_uiState.update { it.copy(toastMessage = promptText) }
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to get prompt: ${result.error.message}") }
}
}
}
}
// ── Delete (single) ──────────────────────────────────────────────────
fun requestDeleteSession(sessionId: String) {
@@ -108,22 +108,6 @@ class SettingsViewModel(
ApiClient.rebuild()
}
fun onRenameProfileNameChange(value: String) {
_uiState.update { it.copy(renameProfileName = value, isSaved = false) }
}
fun renameProfile() {
val currentId = _uiState.value.selectedProfileId ?: return
val newName = _uiState.value.renameProfileName.trim()
if (newName.isBlank()) return
val updatedProfiles =
AuthManager.getConnectionProfiles().map {
if (it.id == currentId) it.copy(name = newName) else it
}
AuthManager.saveConnectionProfiles(updatedProfiles)
viewModelScope.launch(ioDispatcher) { loadSettings() }
}
fun deleteProfile(profileId: String) {
val updatedProfiles = AuthManager.getConnectionProfiles().filter { it.id != profileId }
AuthManager.saveConnectionProfiles(updatedProfiles)
@@ -235,10 +219,6 @@ class SettingsViewModel(
ApiClient.rebuild()
}
fun clearNavigateToLogin() {
_uiState.update { it.copy(navigateToLogin = false) }
}
// ── Delete confirmation ──────────────────────────────────────────────
fun requestDeleteProfile(profileId: String) {
@@ -205,17 +205,6 @@ class SkillsViewModel(
}
}
fun clearHubPreview() {
_uiState.update {
it.copy(
hubPreviewIdentifier = null,
hubPreviewContent = null,
isHubPreviewing = false,
hubPreviewError = null,
)
}
}
// ── Hub install ─────────────────────────────────────────────────────────
fun installSkill(identifier: String) {
@@ -293,43 +282,6 @@ class SkillsViewModel(
}
}
// ── SKILL.md preview ────────────────────────────────────────────────────
fun previewSkill(skillName: String) {
_uiState.update {
it.copy(
previewSkillName = skillName,
previewSkillContent = null,
isLoadingPreview = true,
)
}
viewModelScope.launch {
val result =
withContext(Dispatchers.IO) {
safeApiCall { ApiClient.hermesApi.getSkillContent(skillName) }
}
when (result) {
is NetworkResult.Success -> {
_uiState.update {
it.copy(
isLoadingPreview = false,
previewSkillContent = result.data.content,
)
}
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
isLoadingPreview = false,
toastMessage = "Failed to load preview: ${result.error.message}",
)
}
}
}
}
}
fun clearPreview() {
_uiState.update {
it.copy(
@@ -700,23 +700,4 @@ class SystemViewModel :
override fun clearToast() {
_uiState.update { it.copy(toastMessage = null) }
}
fun clearTransientState() {
actionPollingJob?.cancel()
_uiState.update {
it.copy(
errorMessage = null,
toastMessage = null,
activeAction = null,
actionLog = null,
isLoading = false,
addingCred = false,
creatingHook = false,
sharing = false,
checkingUpdate = false,
updateConfirmOpen = false,
hookModalOpen = false,
)
}
}
}