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:
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user