Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 12f85ac

Browse files
authored
feat(mobile): default messaging mode to steer (port #3983)
Ports desktop PR #3983 to the React Native app. Steer mode is stable in cloud, and mobile only runs cloud sessions, so new sessions now default to steer instead of queue. Since `defaultMode` is persisted in AsyncStorage, existing installs would otherwise keep queue forever. Bumps the `messaging-mode-storage` persist version to 1 with a migrate that moves untouched installs to steer. Per-task overrides are preserved and still win over the default. An explicit "queue" default is indistinguishable from the old default in the single-key store, so it is reset to steer too (one-time, disclosed tradeoff). Generated-By: PostHog Code Task-Id: 33d606bb-a1b4-48d4-9fb9-e5d26fa4a9f0
1 parent f4fec98 commit 12f85ac

3 files changed

Lines changed: 58 additions & 14 deletions

File tree

apps/mobile/src/app/settings/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ export default function SettingsScreen() {
418418
/>
419419
<SettingsRow
420420
label="Messaging mode"
421-
description="What happens when you send while a turn is running"
421+
description="What new cloud sessions do when you send while a turn is running"
422422
onPress={() => setMessagingModeSheetOpen(true)}
423423
rightSlot={
424424
<>
Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
import { beforeEach, describe, expect, it } from "vitest";
2-
import { useMessagingModeStore } from "./messagingModeStore";
2+
import {
3+
migrateMessagingModeState,
4+
useMessagingModeStore,
5+
} from "./messagingModeStore";
36

47
const INITIAL_STATE = useMessagingModeStore.getState();
58

69
describe("messagingModeStore", () => {
710
beforeEach(() => {
811
useMessagingModeStore.setState(
9-
{ ...INITIAL_STATE, modesByTaskId: {}, defaultMode: "queue" },
12+
{ ...INITIAL_STATE, modesByTaskId: {}, defaultMode: "steer" },
1013
true,
1114
);
1215
});
1316

14-
it("defaults to Queue", () => {
15-
expect(useMessagingModeStore.getState().getEffectiveMode("t1")).toBe(
16-
"queue",
17-
);
18-
});
19-
20-
it("falls back to the global default when a task has no override", () => {
21-
useMessagingModeStore.getState().setDefaultMode("steer");
17+
it("defaults to Steer", () => {
2218
expect(useMessagingModeStore.getState().getEffectiveMode("t1")).toBe(
2319
"steer",
2420
);
2521
});
2622

23+
it.each(["queue", "steer"] as const)(
24+
"falls back to the global default (%s) when a task has no override",
25+
(mode) => {
26+
useMessagingModeStore.getState().setDefaultMode(mode);
27+
expect(useMessagingModeStore.getState().getEffectiveMode("t1")).toBe(
28+
mode,
29+
);
30+
},
31+
);
32+
2733
it("prefers a per-task override over the global default", () => {
28-
useMessagingModeStore.getState().setDefaultMode("steer");
2934
useMessagingModeStore.getState().setMode("t1", "queue");
3035
expect(useMessagingModeStore.getState().getEffectiveMode("t1")).toBe(
3136
"queue",
@@ -37,9 +42,32 @@ describe("messagingModeStore", () => {
3742
});
3843

3944
it("treats an undefined taskId as the global default", () => {
40-
useMessagingModeStore.getState().setDefaultMode("steer");
4145
expect(useMessagingModeStore.getState().getEffectiveMode(undefined)).toBe(
4246
"steer",
4347
);
4448
});
4549
});
50+
51+
describe("migrateMessagingModeState", () => {
52+
it("moves v0 installs to steer while keeping per-task overrides", () => {
53+
const migrated = migrateMessagingModeState(
54+
{ modesByTaskId: { t1: "queue" }, defaultMode: "queue" },
55+
0,
56+
);
57+
expect(migrated).toEqual({
58+
modesByTaskId: { t1: "queue" },
59+
defaultMode: "steer",
60+
});
61+
});
62+
63+
it("leaves already-migrated state untouched", () => {
64+
const state = { modesByTaskId: {}, defaultMode: "queue" as const };
65+
expect(migrateMessagingModeState(state, 1)).toEqual(state);
66+
});
67+
68+
it("tolerates missing persisted state", () => {
69+
expect(migrateMessagingModeState(undefined, 0)).toEqual({
70+
defaultMode: "steer",
71+
});
72+
});
73+
});

apps/mobile/src/features/tasks/stores/messagingModeStore.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,25 @@ interface MessagingModeState {
1313
getEffectiveMode: (taskId: string | undefined) => MessagingMode;
1414
}
1515

16+
/**
17+
* Mobile sessions are all cloud, where steer is stable, so the default moved to
18+
* steer. Existing installs persisted "queue" under v0 and would keep it, so v1
19+
* rehydrates them to steer. Per-task overrides are untouched; an explicit
20+
* "queue" default is indistinguishable from the old default, so it is reset too.
21+
*/
22+
export function migrateMessagingModeState(
23+
persisted: unknown,
24+
version: number,
25+
): Partial<MessagingModeState> {
26+
const state = (persisted ?? {}) as Partial<MessagingModeState>;
27+
return version < 1 ? { ...state, defaultMode: "steer" } : state;
28+
}
29+
1630
export const useMessagingModeStore = create<MessagingModeState>()(
1731
persist(
1832
(set, get) => ({
1933
modesByTaskId: {},
20-
defaultMode: "queue",
34+
defaultMode: "steer",
2135
setMode: (taskId, mode) =>
2236
set((state) => ({
2337
modesByTaskId: { ...state.modesByTaskId, [taskId]: mode },
@@ -34,6 +48,8 @@ export const useMessagingModeStore = create<MessagingModeState>()(
3448
{
3549
name: "messaging-mode-storage",
3650
storage: createJSONStorage(() => AsyncStorage),
51+
version: 1,
52+
migrate: migrateMessagingModeState,
3753
partialize: (state) => ({
3854
modesByTaskId: state.modesByTaskId,
3955
defaultMode: state.defaultMode,

0 commit comments

Comments
 (0)