feat(aiturk): hide what the paired IDE cannot serve, plain error sentences, reconnect on return
Merge Conflict Detector / detect-and-label (push) Successful in 3s
Android CI/CD / Detect Changes (push) Successful in 8s
Android CI/CD / Android Lint — Static Analysis (push) Failing after 2m58s
Android CI/CD / Release Compile Check (push) Failing after 1m32s
CodeQL Analysis / Analyze (java-kotlin) (push) Failing after 3m47s
Android CI/CD / ktlint — Code Style (push) Failing after 3m52s
Android CI/CD / Unit & Integration Tests (push) Failing after 3m56s
Android CI/CD / Build Debug APK (push) Failing after 4m7s
Android CI/CD / Instrumented Tests (push) Failing after 1m18s
Android CI/CD / CI Summary (push) Failing after 1s

When paired with AITURK IDE:
- an "unknown method" (-32601) answer and the proxy's 403/route-404 are shown
  as plain sentences, never as raw JsonRpcError text; a method the IDE does
  not have is not sent again on the same socket
- Account connectors, Vault, Keys and Files are hidden (the IDE has no
  connectors.* / vault.* and its phone proxy refuses /api/env and /api/files)
- an idle-closed chat socket is reopened when the app returns to the
  foreground; idle tunnel connections are dropped on a network change
- an unsaved chat no longer polls /api/sessions/<id>
- the pairing screen shows the app version

A plain Hermes connection is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ppceaR5s6cvMnm7uGRnf
This commit is contained in:
2026-10-05 17:21:32 +03:00
co-authored by Claude Opus 5.5
parent c2d828b90a
commit 827c74b3c3
18 changed files with 709 additions and 22 deletions
@@ -0,0 +1,60 @@
package com.m57.hermescontrol.ui.aiturkpair
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Computer
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.navigation3.runtime.NavKey
import com.m57.hermescontrol.AccountConnectorsScreen
import com.m57.hermescontrol.FilesScreen
import com.m57.hermescontrol.KeysScreen
import com.m57.hermescontrol.R
import com.m57.hermescontrol.SettingsVault
import com.m57.hermescontrol.data.aiturk.AiturkCompat
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.NavIcon
/**
* Screens a paired AITURK IDE cannot serve, hidden from the drawer and the
* settings list while that pairing is the active connection:
*
* - Account connectors, Credential vault: the IDE's agent has no
* `connectors.*` / `vault.*` methods;
* - Keys, Files: the IDE's phone proxy refuses `/api/env` and `/api/files`
* on purpose (secrets and the computer's files stay on the computer).
*
* A plain Hermes dashboard connection shows everything, as upstream does.
*/
object AiturkFeatureGate {
val HIDDEN_WHEN_LINKED: Set<NavKey> = setOf(AccountConnectorsScreen, KeysScreen, FilesScreen, SettingsVault)
fun shows(
key: NavKey,
linked: Boolean = AiturkCompat.isLinked(),
): Boolean = !(linked && key in HIDDEN_WHEN_LINKED)
}
/** Shown instead of a hidden screen that is still reached (restored back stack, shortcut). */
@Composable
fun AiturkUnavailableScreen(
@StringRes titleRes: Int,
onOpenDrawer: (() -> Unit)?,
) {
HermesScaffold(
title = { Text(stringResource(titleRes)) },
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
) { padding ->
EmptyState(
title = stringResource(R.string.aiturk_feature_unavailable),
subtitle = stringResource(R.string.aiturk_feature_unavailable_hint),
icon = Icons.Filled.Computer,
modifier = Modifier.padding(padding).testTag("aiturk_unavailable"),
)
}
}
@@ -43,6 +43,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily
@@ -61,6 +62,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.aiturk.AiturkCompat
import com.m57.hermescontrol.data.aiturk.AiturkPairing
import com.m57.hermescontrol.data.remote.requiresLocalNetworkPermission
@@ -286,6 +288,15 @@ fun AiturkPairScreen(
TextButton(onClick = onBack, enabled = !state.busy) {
Text(stringResource(R.string.aiturk_pair_back))
}
// So the user can tell which build is installed when support asks.
Text(
text = stringResource(R.string.aiturk_app_version, AiturkCompat.versionLabel()),
modifier = Modifier.testTag("aiturk_app_version"),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center,
)
}
}
}