Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a678018
fix(ios): show and steer messages sent mid-turn
aivsomkar Sep 3, 2026
5221d31
fix(ios): do not fake a cancel a computer cannot perform
aivsomkar Sep 3, 2026
4d7086b
feat: say Steer in words, above the chat bar
aivsomkar Sep 3, 2026
b701a65
fix: say Steering while the interrupt is still landing
aivsomkar Sep 3, 2026
9dac7cc
fix(ios): retire the queued row, and stop streaming a dead turn
aivsomkar Sep 3, 2026
d6fe281
perf(ios): keep the engine-capability read off hydrate's critical path
aivsomkar Sep 3, 2026
177ad92
feat(activity): a per-bot activity log, read from the two logs that a…
aivsomkar Sep 7, 2026
a46304a
feat(outbound): sending on your behalf always asks, or spends a daily…
aivsomkar Sep 7, 2026
ba46dbe
feat(scopes): per-bot connection scopes, enforced at the connector relay
aivsomkar Sep 7, 2026
9596230
feat(fallback): second accounts on an engine, and a per-bot fallback …
aivsomkar Sep 7, 2026
54591c7
feat(team-memory): people, places, decisions and terms every bot in a…
aivsomkar Sep 7, 2026
10c2838
feat(ios): Activity and Team memory screens, with the companion route…
aivsomkar Sep 8, 2026
391893d
docs(mobile): design for a shared touch gesture layer on remote surfaces
aivsomkar Sep 18, 2026
31b9576
docs(mobile): implementation plan for the shared gesture core
aivsomkar Sep 18, 2026
3982881
feat(ios): gesture value types and viewport mapping
aivsomkar Sep 18, 2026
88c8e9d
feat(ios): click sequencing for direct-mode taps
aivsomkar Sep 18, 2026
28bc410
feat(ios): long press, drag threshold and held-button drags
aivsomkar Sep 18, 2026
b588b91
feat(ios): trackpad mode with a virtual cursor and acceleration
aivsomkar Sep 18, 2026
42e31a2
feat(ios): scroll, momentum and local zoom state
aivsomkar Sep 18, 2026
d372542
feat(ios): driving gate and held-input flush
aivsomkar Sep 18, 2026
27b3070
test(ios): shared gesture parity fixture and runner
aivsomkar Sep 18, 2026
11cdb72
feat(android): gesture value types and viewport mapping
aivsomkar Sep 18, 2026
4da3a3d
feat(android): gesture core mirroring CompanionCore
aivsomkar Sep 18, 2026
aa23588
test(android): run the shared gesture parity fixture
aivsomkar Sep 18, 2026
f08241a
feat(companion): per-device browser control capability
aivsomkar Sep 21, 2026
95bd97d
feat(ios): browser-live sink and input queue
aivsomkar Sep 21, 2026
61b0bcd
feat(android): browser-live sink and input queue
aivsomkar Sep 21, 2026
84da8b6
feat(mobile): browser-live transport and message decoding
aivsomkar Sep 21, 2026
ee90c4b
feat(ios): browser control screen and touch adapter
aivsomkar Sep 21, 2026
9a60b99
feat(android): browser control screen and touch adapter
aivsomkar Sep 21, 2026
10499f0
fix(mobile): typing on Android, cursor placement and pan
aivsomkar Sep 21, 2026
5f41d71
fix(mobile): make the browser stream actually decode, and 8 more
aivsomkar Sep 21, 2026
e7fc57c
fix(mobile): revocation, stale clicks, held buttons and a pump race
aivsomkar Sep 21, 2026
8c8136f
test(companion): give proxy fixtures the browser capability field
aivsomkar Sep 22, 2026
fededb2
fix(mobile): show the server's refusal and let a dead stream retry
aivsomkar Sep 22, 2026
0b32590
Merge origin/main into feat/mobile-touch-control
aivsomkar Sep 22, 2026
234fd50
feat(i18n): add complete Chinese localization across desktop and mobile
dreamfarer-space Sep 26, 2026
0d05428
Merge upstream main into the Chinese localization branch
dreamfarer-space Sep 28, 2026
273b5d3
fix(android): align the notification footer and localize the thread s…
dreamfarer-space Sep 28, 2026
71bf173
fix(i18n): correct Chinese mistranslations found in cross-PR review
dreamfarer-space Sep 28, 2026
9d45404
fix(i18n): unify Chinese terminology across desktop, Android, and iOS
dreamfarer-space Sep 28, 2026
dde7cbf
feat(ios): Activity and Team memory screens, with the companion route…
aivsomkar Oct 3, 2026
7223f24
Merge pinned main into mobile browser control
milind-soni Oct 3, 2026
ec3d3ce
Merge main into mid-turn steering and preserve native queue controls
milind-soni Oct 3, 2026
9a91444
fix(companion): expose separate browser access grant in desktop settings
milind-soni Oct 3, 2026
b38915f
Merge reviewed mid-turn steering with current mobile state
milind-soni Oct 3, 2026
b1a0aa6
Merge current main into Chinese localization and refresh source copy
milind-soni Oct 3, 2026
b5f18bb
Merge reviewed Chinese localization with current mobile interfaces
milind-soni Oct 3, 2026
49767c2
Preserve current main expired-card and restore fixes in mobile integr…
milind-soni Oct 3, 2026
c871161
fix: harden mobile browser authorization and lifecycle
milind-soni Oct 3, 2026
8545ad3
Merge reviewed native browser control and grant lifecycle fixes
milind-soni Oct 3, 2026
718c25d
Merge main into trust features with scoped relay and reviewed memory
milind-soni Oct 3, 2026
d6c1041
Preserve original PR 903 merge ancestry
milind-soni Oct 3, 2026
0f99881
Merge commit 'd6c1041e30e22a7c7c86429e3a3c25b4639f70d9' into codex/oc…
milind-soni Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.openmausbot.companion.ui

import androidx.compose.ui.res.stringResource

import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -221,10 +223,10 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
onClick = onDismiss,
modifier = Modifier.align(Alignment.CenterStart),
) {
Text("Done")
Text(stringResource(R.string.mobile_done_e9b450d1))
}
Text(
text = "Bot settings",
text = stringResource(R.string.mobile_bot_settings_7092a294),
fontSize = 17.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.align(Alignment.Center),
Expand Down Expand Up @@ -280,7 +282,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
modifier = Modifier.fillMaxWidth().heightIn(min = MIN_TOUCH_TARGET),
verticalAlignment = Alignment.CenterVertically,
) {
Text(ModelRules.LOADING, fontSize = 15.sp, modifier = Modifier.weight(1f))
Text(localizedMobileCopy(ModelRules.LOADING), fontSize = 15.sp, modifier = Modifier.weight(1f))
CircularProgressIndicator(modifier = Modifier.size(20.dp))
}
} else if (instanceChoices.isEmpty()) {
Expand Down Expand Up @@ -309,7 +311,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
}
}
ChoicePicker(
label = "Provider",
label = stringResource(R.string.mobile_provider_7ceee3f3),
choices = providerRows,
selected = selectedInstanceId,
onSelect = { id ->
Expand All @@ -318,7 +320,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
},
)
ChoicePicker(
label = "Model",
label = stringResource(R.string.mobile_model_68c2cc7f),
choices = ModelRules.modelChoices(selectedInstance, selectedModelId).map {
VoiceChoice(id = it.id, label = it.label, detail = null, enabled = true)
},
Expand All @@ -329,7 +331,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
val effortLevels = ModelRules.effortLevels(selectedInstance)
if (effortLevels.isNotEmpty()) {
ChoicePicker(
label = "Reasoning effort",
label = stringResource(R.string.mobile_reasoning_effort_cd32c0f5),
choices = buildList {
add(VoiceChoice(id = "", label = ModelRules.DEFAULT_EFFORT_LABEL, detail = null, enabled = true))
effortLevels.forEach {
Expand Down Expand Up @@ -381,7 +383,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
bot = current,
size = 112.dp,
state = MausState.HAPPY,
contentDescription = "${current.name} avatar",
contentDescription = stringResource(R.string.mobile_current_name_avatar_de638954, current.name),
)
}

Expand All @@ -397,7 +399,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
count = AvatarCrop.entries.size,
),
) {
Text(ProfileRules.cropLabel(option))
Text(localizedMobileCopy(ProfileRules.cropLabel(option)))
}
}
}
Expand Down Expand Up @@ -447,7 +449,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
OutlinedTextField(
value = prompt,
onValueChange = { prompt = it },
label = { Text("Art direction") },
label = { Text(stringResource(R.string.mobile_art_direction_52d878a2)) },
minLines = 2,
maxLines = 5,
modifier = Modifier.fillMaxWidth(),
Expand Down Expand Up @@ -496,7 +498,7 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
OutlinedTextField(
value = form.name,
onValueChange = { form = form.copy(name = it) },
label = { Text("Name") },
label = { Text(stringResource(R.string.mobile_name_709a2322)) },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
Expand All @@ -506,20 +508,20 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
OutlinedTextField(
value = form.title,
onValueChange = { form = form.copy(title = it) },
label = { Text("Title") },
label = { Text(stringResource(R.string.mobile_title_768e0c1c)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = form.description,
onValueChange = { form = form.copy(description = it) },
label = { Text("What this agent does") },
label = { Text(stringResource(R.string.mobile_what_this_agent_does_82aa1b56)) },
minLines = 3,
maxLines = 8,
modifier = Modifier.fillMaxWidth(),
)
SwitchRow(
label = "Agent notifications",
label = stringResource(R.string.mobile_agent_notifications_a3bd8bd0),
checked = form.notifications,
onCheckedChange = { form = form.copy(notifications = it) },
)
Expand Down Expand Up @@ -558,13 +560,13 @@ internal fun AgentProfileSheet(bot: Bot, onDismiss: () -> Unit, onOpenOverview:
},
) {
ChoicePicker(
label = "Voice",
label = stringResource(R.string.mobile_voice_3091c844),
choices = ProfileRules.voiceChoices(config, voices, form.voice),
selected = form.voice,
onSelect = { form = form.copy(voice = it) },
)
SwitchRow(
label = "Speak replies",
label = stringResource(R.string.mobile_speak_replies_90b05ae0),
checked = form.speakReplies,
enabled = ProfileRules.selectedVoiceCanSpeak(config, form.voice),
onCheckedChange = { form = form.copy(speakReplies = it) },
Expand Down Expand Up @@ -658,7 +660,7 @@ internal fun ChoicePicker(
enabled: Boolean = true,
) {
var expanded by remember { mutableStateOf(false) }
val selectedLabel = choices.firstOrNull { it.id == selected }?.label.orEmpty()
val selectedLabel = localizedMobileCopy(choices.firstOrNull { it.id == selected }?.label.orEmpty())
ExposedDropdownMenuBox(
expanded = expanded && enabled,
onExpandedChange = { if (enabled) expanded = it },
Expand All @@ -669,7 +671,7 @@ internal fun ChoicePicker(
onValueChange = {},
readOnly = true,
enabled = enabled,
label = { Text(label) },
label = { Text(localizedMobileCopy(label)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
Expand All @@ -680,9 +682,9 @@ internal fun ChoicePicker(
DropdownMenuItem(
text = {
Column {
Text(choice.label)
Text(localizedMobileCopy(choice.label))
choice.detail?.let {
Text(it, fontSize = 13.sp, color = secondaryTint)
Text(localizedMobileCopy(it), fontSize = 13.sp, color = secondaryTint)
}
}
},
Expand Down Expand Up @@ -721,7 +723,7 @@ internal fun VoiceSection(
val copy = ProfileRules.voiceCopy(config)
FormSection(header = "Voice", footer = copy.footer) {
ChoicePicker(
label = "Voice engine",
label = stringResource(R.string.mobile_voice_engine_3b4d8dea),
choices = ProfileRules.providerChoices(),
selected = (config?.voiceProvider ?: VoiceProvider.ELEVENLABS).wire,
onSelect = { next -> onSwitchEngine(VoiceProvider.fromWire(next)) },
Expand Down Expand Up @@ -751,15 +753,15 @@ internal fun FormSection(
) {
header?.let {
Text(
text = it.uppercase(),
text = localizedMobileCopy(it).uppercase(),
fontSize = 12.sp,
fontWeight = FontWeight.SemiBold,
color = secondaryTint,
)
}
HorizontalDivider()
content()
footer?.let { Text(text = it, fontSize = 13.sp, color = secondaryTint) }
footer?.let { Text(text = localizedMobileCopy(it), fontSize = 13.sp, color = secondaryTint) }
}
}

Expand Down Expand Up @@ -800,7 +802,7 @@ internal fun ActionRow(
modifier = Modifier.size(20.dp),
)
}
Text(text = text, fontSize = 15.sp, color = tint)
Text(text = localizedMobileCopy(text), fontSize = 15.sp, color = tint)
}
}

Expand Down Expand Up @@ -862,7 +864,7 @@ internal fun IconNote(
modifier = Modifier.size(18.dp),
)
}
Text(text = text, fontSize = 13.sp, color = resolved)
Text(text = localizedMobileCopy(text), fontSize = 13.sp, color = resolved)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.res.stringResource
import com.openmausbot.companion.R
import com.openmausbot.companion.core.Bot
import com.openmausbot.companion.core.BotTask
import com.openmausbot.companion.core.CompanionState
Expand Down Expand Up @@ -94,6 +96,12 @@ private fun attentionRank(entry: AttentionEntry): Int = when {
/** Selection only: the row jumps; menus and management stay in the sheet. */
@Composable
internal fun AttentionRow(entry: AttentionEntry, onOpen: () -> Unit) {
val status = when {
entry.task.activity == "waiting-on-you" -> stringResource(R.string.mobile_waiting_on_you_edab5b72)
entry.task.busy == true || entry.task.activity == "working" -> stringResource(R.string.mobile_thread_working)
entry.queued -> stringResource(R.string.mobile_queued_6a599877)
else -> stringResource(R.string.mobile_unread_07b032b5)
}
Row(
modifier = Modifier
.fillMaxWidth()
Expand All @@ -115,7 +123,7 @@ internal fun AttentionRow(entry: AttentionEntry, onOpen: () -> Unit) {
overflow = TextOverflow.Ellipsis,
)
Text(
text = "${entry.botName} · ${entry.statusLine()}",
text = "${entry.botName} · $status",
fontSize = 12.sp,
color = secondaryTint,
maxLines = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
Expand Down Expand Up @@ -77,7 +78,7 @@ fun BotOverviewScreen(botId: String, onBack: () -> Unit) {
}

val name = overview?.who?.name ?: state.bot(botId)?.name.orEmpty()
val title = OverviewRules.title(name)
val title = stringResource(R.string.mobile_overview_title, name)

Column(modifier = Modifier.fillMaxSize()) {
Row(
Expand Down Expand Up @@ -114,7 +115,7 @@ fun BotOverviewScreen(botId: String, onBack: () -> Unit) {
}
current != null -> OverviewBody(current)
failed -> FormSection(header = null) {
Text(OverviewRules.FAILED, color = secondaryTint)
Text(localizedMobileCopy(OverviewRules.FAILED), color = secondaryTint)
}
}
}
Expand All @@ -139,7 +140,7 @@ private fun OverviewBody(overview: BotOverview) {

FormSection(header = OverviewRules.DOES) {
if (overview.does.isEmpty()) {
Text(OverviewRules.EMPTY_DOES, color = secondaryTint)
Text(localizedMobileCopy(OverviewRules.EMPTY_DOES), color = secondaryTint)
} else {
overview.does.forEach { line ->
IconNote(text = line, painter = R.drawable.ic_schedule)
Expand All @@ -164,7 +165,7 @@ private fun OverviewBody(overview: BotOverview) {

FormSection(header = OverviewRules.RECENT) {
if (overview.recent.isEmpty()) {
Text(OverviewRules.EMPTY_RECENT, color = secondaryTint)
Text(localizedMobileCopy(OverviewRules.EMPTY_RECENT), color = secondaryTint)
} else {
overview.recent.forEach { entry ->
Column {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.openmausbot.companion.ui

import androidx.compose.ui.res.stringResource

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -21,6 +23,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.openmausbot.companion.R
import com.openmausbot.companion.core.BotTask
import com.openmausbot.companion.core.bylineLabel
import com.openmausbot.companion.core.displayTitle
Expand Down Expand Up @@ -53,13 +56,20 @@ internal fun BotThreadRow(
* it, so the row derives it here rather than parsing activity. */
queued: Boolean = false,
) {
val runtime = task.runtimeLabel(queued)
val runtimeLabel = task.runtimeLabel(queued)
val runtime = when (runtimeLabel) {
"Waiting on you" -> stringResource(R.string.mobile_waiting_on_you_edab5b72)
"Waiting on teammate" -> stringResource(R.string.mobile_waiting_on_teammate_158d309b)
"Working" -> stringResource(R.string.mobile_thread_working)
"Queued" -> stringResource(R.string.mobile_queued_6a599877)
else -> null
}
val snoozed = task.isSnoozed(now)
val dimmed = (task.isClosed || task.isArchived || snoozed) && runtime == null && task.unread != true
val foldedState = when {
task.isClosed -> "Closed"
task.isArchived -> "Archived"
snoozed -> "Snoozed"
task.isClosed -> stringResource(R.string.mobile_thread_closed)
task.isArchived -> stringResource(R.string.mobile_archived_eddc813f)
snoozed -> stringResource(R.string.mobile_thread_snoozed)
else -> null
}
Row(
Expand Down Expand Up @@ -89,17 +99,16 @@ internal fun BotThreadRow(
text = runtime,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = when (runtime) {
color = when (runtimeLabel) {
"Waiting on you" -> MaterialTheme.colorScheme.error
"Waiting on teammate" -> secondaryTint
"Queued" -> secondaryTint
"Waiting on teammate", "Queued" -> secondaryTint
else -> MaterialTheme.colorScheme.primary
},
)
}
if (task.unread == true) {
Text(
"Unread",
stringResource(R.string.mobile_unread_07b032b5),
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary,
Expand All @@ -109,7 +118,7 @@ internal fun BotThreadRow(
}
val byline = listOfNotNull(
RelativeStamp.updated(task.listStamp).takeIf { it.isNotEmpty() },
"Pinned".takeIf { task.pinned == true },
stringResource(R.string.mobile_roster_pinned).takeIf { task.pinned == true },
task.bylineLabel(now),
).joinToString(" · ")
if (byline.isNotEmpty()) {
Expand Down
Loading
Loading