Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7263f87
Add Android mobile support
juliusmarminge Jun 27, 2026
3940c8d
Checkpoint Android Ghostty terminal prototype
juliusmarminge Jun 27, 2026
bc274f5
Fix Android terminal input handling
juliusmarminge Jun 27, 2026
b4ab65e
Use Shiki for markdown code blocks
juliusmarminge Jun 27, 2026
1086910
fix(mobile): align Android terminal for 16KB pages (#3595)
HorusGoul Jun 29, 2026
e4fea10
fix(mobile): stub Android setters for patched RNS glass-chrome props
juliusmarminge Jul 3, 2026
84e3ca4
fix(mobile): use theme-aware surface for markdown tables in dark mode
maria-rcks Jul 4, 2026
ab36472
fix(mobile): render copy button icons on Android
maria-rcks Jul 4, 2026
8169f6d
feat(mobile): add copy button to chat code blocks
maria-rcks Jul 4, 2026
69c50b2
fix(mobile): render project folder icons on Android with open state
maria-rcks Jul 4, 2026
d39ac7d
fix(mobile): render work log icons on Android
maria-rcks Jul 4, 2026
9fe4b2b
fix(mobile): raise Gradle heap to fix D8 OOM during dex merge
shivamhwp Jul 5, 2026
632784d
feat(mobile): polish Android chrome β€” home FAB, themed popup menus, f…
shivamhwp Jul 5, 2026
a872963
fix(mobile): native right-aligned checks, opaque popups, chevron subm…
shivamhwp Jul 5, 2026
f7279df
feat(mobile): make the Android new-task draft read like a thread page
shivamhwp Jul 5, 2026
e48a9c6
fix(mobile): flat Android settings sections and visible row icons
shivamhwp Jul 5, 2026
cf0f396
fix(mobile): keep Appearance page cards and render environment row icons
shivamhwp Jul 5, 2026
dfde2ad
style(mobile): fix formatter and ktlint violations
shivamhwp Jul 5, 2026
b2057e2
style(mobile): suppress detekt complexity findings in native module p…
shivamhwp Jul 5, 2026
2f15df1
fix(mobile): render all Android icons through the AppSymbol wrapper
shivamhwp Jul 5, 2026
76cf489
feat(mobile): swipe-only thread rows and folder-state project headers…
shivamhwp Jul 5, 2026
31c8df6
feat(mobile): push git flows and environments as full pages on Android
shivamhwp Jul 5, 2026
af6100b
fix(mobile): stop dev-client launches from hitting NotFound and Wi-Fi
shivamhwp Jul 5, 2026
74c935b
Improve Android collapsed composer alignment and toolbar
shivamhwp Jul 5, 2026
5fc495e
Use 36px height for collapsed composer on Android
shivamhwp Jul 5, 2026
baff316
fix(mobile): archive page chrome and home header polish on Android
shivamhwp Jul 6, 2026
fe8fcce
Use form sheets for Android git flows and polish mobile UX
shivamhwp Jul 6, 2026
9fe61a6
fix(mobile): drop per-row overflow menu in archived threads
shivamhwp Jul 6, 2026
274b299
Modernize Android alert dialogs and popup menus with app tokens
shivamhwp Jul 6, 2026
f1fe7aa
Compact Android git sheet header and align card radius
shivamhwp Jul 6, 2026
5fdf4d1
Replace stock Android dropdowns with token-styled anchored menu
shivamhwp Jul 6, 2026
47c5849
feat(mobile): blur Android anchored-menu backdrops via app-level blur…
shivamhwp Jul 8, 2026
dbdf432
feat(mobile): enable haptics and long-press thread menus on Android
shivamhwp Jul 8, 2026
13cca5f
fix(mobile): keep keyboard up for composer menus and smooth Android e…
shivamhwp Jul 8, 2026
cf65adc
fix(mobile): keyboard-aware menu placement and keyboard-ridden compos…
shivamhwp Jul 8, 2026
7df8cf3
fix(mobile): push gating and personal-team hardening for Android PR (…
bmdavis419 Jul 9, 2026
01199df
fix(mobile): align Android home branding with desktop (#3774)
PixPMusic Jul 9, 2026
fcb5645
refactor(mobile): make Android stack Uniwind-first
juliusmarminge Jul 9, 2026
0911f5a
fix(mobile): restore native config plugins after rebase
juliusmarminge Jul 9, 2026
8c3e487
fix(mobile): reference generated Android font family
juliusmarminge Jul 9, 2026
7a40525
fix(mobile): preserve blur target root layout
juliusmarminge Jul 9, 2026
43c55b4
refactor(mobile): use AppSymbolName from the AppSymbol wrapper in thr…
shivamhwp Jul 9, 2026
5aae4b3
feat(mobile): Android launcher app shortcuts via expo-quick-actions
shivamhwp Jul 9, 2026
9b924e6
feat(mobile): enable the Android predictive back gesture
shivamhwp Jul 9, 2026
efdc6c6
fix(mobile): let flex-filling composer pills stretch full width again
shivamhwp Jul 9, 2026
3422d9b
fix(mobile): harden launcher shortcut routing and persistence
shivamhwp Jul 10, 2026
57e6060
fix(mobile): step anchored-menu back through submenus
shivamhwp Jul 10, 2026
80cf687
feat(mobile): Android terminal improvements β€” input, selection, sessi…
PixPMusic Jul 12, 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
3 changes: 3 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"expo": {}
}
24 changes: 24 additions & 0 deletions apps/mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ Build and run the local iOS dev client:
vp run ios:dev
```

If your Xcode account only has a Personal Team, use a bundle identifier you control and opt into the
reduced-capability local build. Personal Team builds omit the widget extension, push entitlement, and
native Sign in with Apple entitlement; builds without this opt-in are unchanged.

```bash
T3CODE_IOS_PERSONAL_TEAM=1 \
T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code.dev \
vp run ios:dev
```

Build and install a self-contained Release app that does not need Metro:

```bash
vp run ios:release
```

The Personal Team equivalent also needs a unique bundle identifier:

```bash
T3CODE_IOS_PERSONAL_TEAM=1 \
T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code \
vp run ios:release
```

Build and run the local iOS preview app:

```bash
Expand Down
84 changes: 62 additions & 22 deletions apps/mobile/app.config.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Medium

appleTeamId: "ARK85ZXQ4Z",

When T3CODE_IOS_PERSONAL_TEAM=1, the config still pins ios.appleTeamId to ARK85ZXQ4Z, so prebuild/Xcode signs with the T3 Tools team instead of the developer's personal team. The new personal-team path therefore fails at code signing rather than using the developer's own team. Consider clearing or overriding ios.appleTeamId when isIosPersonalTeamBuild is true.

πŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/app.config.ts around line 139:

When `T3CODE_IOS_PERSONAL_TEAM=1`, the config still pins `ios.appleTeamId` to `ARK85ZXQ4Z`, so prebuild/Xcode signs with the T3 Tools team instead of the developer's personal team. The new personal-team path therefore fails at code signing rather than using the developer's own team. Consider clearing or overriding `ios.appleTeamId` when `isIosPersonalTeamBuild` is true.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ const repoEnv = loadRepoEnv();
Object.assign(process.env, repoEnv);

const APP_VARIANT = resolveAppVariant(repoEnv.APP_VARIANT);
const isIosPersonalTeamBuild = repoEnv.T3CODE_IOS_PERSONAL_TEAM === "1";

const personalTeamBundleIdentifier = repoEnv.T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID?.trim();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High mobile/app.config.ts:13

T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID is validated but never used in the Expo config, so a T3CODE_IOS_PERSONAL_TEAM=1 build still targets the shared com.t3tools.* bundle ID and pinned appleTeamId instead of the caller's personal-team identifier. The build advertises a reduced-capability local build path, but personal-team iOS builds will fail because the config never switches to the caller's bundle ID and team. The validation block guards the value, but nothing reads personalTeamBundleIdentifier when constructing config.ios.bundleIdentifier, the widget identifiers, or ios.appleTeamId. Consider applying personalTeamBundleIdentifier to ios.bundleIdentifier (and the widget bundleIdentifier/groupIdentifier) when isIosPersonalTeamBuild is true, and switching appleTeamId accordingly.

πŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/app.config.ts around line 13:

`T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID` is validated but never used in the Expo config, so a `T3CODE_IOS_PERSONAL_TEAM=1` build still targets the shared `com.t3tools.*` bundle ID and pinned `appleTeamId` instead of the caller's personal-team identifier. The build advertises a reduced-capability local build path, but personal-team iOS builds will fail because the config never switches to the caller's bundle ID and team. The validation block guards the value, but nothing reads `personalTeamBundleIdentifier` when constructing `config.ios.bundleIdentifier`, the widget identifiers, or `ios.appleTeamId`. Consider applying `personalTeamBundleIdentifier` to `ios.bundleIdentifier` (and the widget `bundleIdentifier`/`groupIdentifier`) when `isIosPersonalTeamBuild` is true, and switching `appleTeamId` accordingly.

const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/;

if (
isIosPersonalTeamBuild &&
(!personalTeamBundleIdentifier ||
!IOS_BUNDLE_IDENTIFIER_PATTERN.test(personalTeamBundleIdentifier))
) {
throw new Error(
"T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID must be a reverse-DNS identifier such as com.example.t3code when T3CODE_IOS_PERSONAL_TEAM=1.",
);
}
Comment thread
cursor[bot] marked this conversation as resolved.

const VARIANT_CONFIG: Record<
AppVariant,
Expand Down Expand Up @@ -69,6 +83,26 @@ const dmSansFonts = {
bold: "@expo-google-fonts/dm-sans/700Bold/DMSans_700Bold.ttf",
} as const;

const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [
"expo-widgets",
{
bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
groupIdentifier: `group.${variant.iosBundleIdentifier}`,
enablePushNotifications: true,
// Agent activity can update many times an hour; without the
// frequent-updates entitlement iOS throttles the update budget sooner.
frequentUpdates: true,
widgets: [
{
name: "AgentActivity",
displayName: "Agent Activity",
description: "Shows the current state of active T3 Code agents.",
supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
},
],
},
];

// These aliases match the fonts' PostScript names on iOS. Register the same
// names on Android so React Native and the native composer use one set of
// family names without waiting for runtime font loading.
Expand Down Expand Up @@ -125,12 +159,16 @@ const config: ExpoConfig = {
backgroundImage: "./assets/android-icon-background.png",
monochromeImage: "./assets/android-icon-monochrome.png",
},
predictiveBackGestureEnabled: false,
// Opts into OnBackInvokedCallback-based back dispatch (Android 13+).
// JS back handling survives it via react-native's Android 16 shim plus
// withAndroidPredictiveBackCompat on Android 13-15.
predictiveBackGestureEnabled: true,
},
web: {
favicon: "./assets/favicon.png",
},
plugins: [
"expo-asset",
[
"expo-font",
{
Expand All @@ -157,8 +195,23 @@ const config: ExpoConfig = {
],
"expo-secure-store",
"expo-sqlite",
["@clerk/expo", { theme: "./clerk-theme.json" }],
Comment thread
cursor[bot] marked this conversation as resolved.
// appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before
// plugins earlier in this array, so it cannot strip the entitlement Clerk would add.
["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }],
"expo-web-browser",
[
"expo-quick-actions",
{
// Adaptive launcher-shortcut icon; referenced by resource name from
// the shortcut items set in src/features/shortcuts.
androidIcons: {
shortcut_icon: {
foregroundImage: "./assets/android-icon-foreground.png",
backgroundColor: "#E6F4FE",
},
},
},
],
[
"expo-camera",
{
Expand Down Expand Up @@ -198,31 +251,18 @@ const config: ExpoConfig = {
// expo-widgets' β€” its dangerous mod wipes ios/ExpoWidgetsTarget/ (which
// would delete the asset catalog) and its xcodeproj mod creates the widget
// target (which must exist before the compile phase can be attached).
"./plugins/withWidgetLogoAsset.cjs",
[
"expo-widgets",
{
bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
groupIdentifier: `group.${variant.iosBundleIdentifier}`,
enablePushNotifications: true,
// Agent activity can update many times an hour; without the
// frequent-updates entitlement iOS throttles the update budget sooner.
frequentUpdates: true,
widgets: [
{
name: "AgentActivity",
displayName: "Agent Activity",
description: "Shows the current state of active T3 Code agents.",
supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
},
],
},
],
...(!isIosPersonalTeamBuild ? ["./plugins/withWidgetLogoAsset.cjs", widgetsPlugin] : []),
"./plugins/withIosSceneLifecycle.cjs",
"./plugins/withAndroidCleartextTraffic.cjs",
"./plugins/withAndroidGradleHeap.cjs",
"./plugins/withAndroidModernPopupMenu.cjs",
"./plugins/withAndroidModernAlertDialog.cjs",
"./plugins/withAndroidPredictiveBackCompat.cjs",
...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []),
],
extra: {
appVariant: APP_VARIANT,
iosPersonalTeamBuild: isIosPersonalTeamBuild,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Medium mobile/app.config.ts:253

When T3CODE_IOS_PERSONAL_TEAM=1, this config strips widget, app-group, and push notification entitlements from the binary, but extra.iosPersonalTeamBuild is the only signal exposed for that. The live-activity entrypoints (armAgentAwarenessLiveActivityForLocalWork, registerAgentAwarenessConnection, refreshActiveLiveActivityRemoteRegistration, etc.) still gate only on Platform.OS === "ios", so on a personal-team build they continue calling AgentActivity.getInstances() / AgentActivity.start() against a binary with no widget capabilities and fail at runtime instead of being skipped. Make those entrypoints also check extra.iosPersonalTeamBuild (or Constants.expoConfig?.extra?.iosPersonalTeamBuild) so the live-activity paths are fully opted out on personal-team builds.

Also found in 2 other location(s)

apps/mobile/src/features/agent-awareness/capabilities.ts:4

supportsAgentAwarenessPush() at line 4 now returns true on every non-Personal-Team build, including Android. SettingsRouteScreen uses this flag to enable the Live Activity Updates switch, but the underlying registration code still hard-stops on Platform.OS !== &#34;ios&#34;. On Android, once the user is signed in and preferences load, the switch becomes actionable and setLiveActivityUpdatesEnabled(...) runs even though Live Activities are unsupported there, leading to a misleading enabled flow and cloud preference updates that can never make Android notifications work.

apps/mobile/src/features/agent-awareness/remoteRegistration.ts:253

The new supportsAgentAwarenessPush() guard at nativePushTokenRegistration only disables APNs device-token registration. The rest of the Live Activity flow still gates on canRegisterRemoteLiveActivities() alone (armAgentAwarenessLiveActivityForLocalWork, refreshActiveLiveActivityRemoteRegistration, registerLiveActivityPushToken), so an iosPersonalTeamBuild can still arm local Live Activities and register their activity tokens even though makeRelayDeviceRegistrationRequest now tells the relay liveActivitiesEnabled: false. In those builds the card can appear locally but the relay will never treat the device as eligible for Live Activity updates/end events, leaving stale or orphaned activities on screen.

πŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/app.config.ts around line 253:

When `T3CODE_IOS_PERSONAL_TEAM=1`, this config strips widget, app-group, and push notification entitlements from the binary, but `extra.iosPersonalTeamBuild` is the only signal exposed for that. The live-activity entrypoints (`armAgentAwarenessLiveActivityForLocalWork`, `registerAgentAwarenessConnection`, `refreshActiveLiveActivityRemoteRegistration`, etc.) still gate only on `Platform.OS === "ios"`, so on a personal-team build they continue calling `AgentActivity.getInstances()` / `AgentActivity.start()` against a binary with no widget capabilities and fail at runtime instead of being skipped. Make those entrypoints also check `extra.iosPersonalTeamBuild` (or `Constants.expoConfig?.extra?.iosPersonalTeamBuild`) so the live-activity paths are fully opted out on personal-team builds.

Also found in 2 other location(s):
- apps/mobile/src/features/agent-awareness/capabilities.ts:4 -- `supportsAgentAwarenessPush()` at line 4 now returns `true` on every non-Personal-Team build, including Android. `SettingsRouteScreen` uses this flag to enable the `Live Activity Updates` switch, but the underlying registration code still hard-stops on `Platform.OS !== "ios"`. On Android, once the user is signed in and preferences load, the switch becomes actionable and `setLiveActivityUpdatesEnabled(...)` runs even though Live Activities are unsupported there, leading to a misleading enabled flow and cloud preference updates that can never make Android notifications work.
- apps/mobile/src/features/agent-awareness/remoteRegistration.ts:253 -- The new `supportsAgentAwarenessPush()` guard at `nativePushTokenRegistration` only disables APNs device-token registration. The rest of the Live Activity flow still gates on `canRegisterRemoteLiveActivities()` alone (`armAgentAwarenessLiveActivityForLocalWork`, `refreshActiveLiveActivityRemoteRegistration`, `registerLiveActivityPushToken`), so an `iosPersonalTeamBuild` can still arm local Live Activities and register their activity tokens even though `makeRelayDeviceRegistrationRequest` now tells the relay `liveActivitiesEnabled: false`. In those builds the card can appear locally but the relay will never treat the device as eligible for Live Activity updates/end events, leaving stale or orphaned activities on screen.

relay: {
url: repoEnv.T3CODE_RELAY_URL ?? null,
},
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/* Card / surface */
--color-card: #ffffff;
--color-card-alt: #f5f5f5;
--color-card-translucent: rgba(255, 255, 255, 0.94);
--color-card-translucent: rgba(255, 255, 255, 0.8);

/* Text */
--color-foreground: #262626;
Expand Down Expand Up @@ -106,7 +106,7 @@
/* Card / surface */
--color-card: #171717;
--color-card-alt: #1c1c1c;
--color-card-translucent: rgba(17, 17, 17, 0.94);
--color-card-translucent: rgba(17, 17, 17, 0.8);

/* Text */
--color-foreground: #f5f5f5;
Expand Down
9 changes: 9 additions & 0 deletions apps/mobile/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { withUniwindConfig } = require("uniwind/metro");
/** @type {import("expo/metro-config").MetroConfig} */
const config = getDefaultConfig(__dirname);
const workspaceRoot = path.resolve(__dirname, "../..");
const escapedWorkspaceRoot = workspaceRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const mobileShikiRoot = path.dirname(require.resolve("shiki/package.json", { paths: [__dirname] }));
const resolveShikiDependencyRoot = (packageName) => {
const entryPath = require.resolve(packageName, { paths: [mobileShikiRoot] });
Expand All @@ -25,6 +26,14 @@ const resolveShikiDependencyRoot = (packageName) => {
config.watchFolders = [...new Set([...(config.watchFolders ?? []), workspaceRoot])];
config.resolver = {
...config.resolver,
blockList: [
...(Array.isArray(config.resolver?.blockList)
? config.resolver.blockList
: config.resolver?.blockList
? [config.resolver.blockList]
: []),
new RegExp(`${escapedWorkspaceRoot}[/\\\\]\\.t3[/\\\\].*`),
],
extraNodeModules: {
// oxlint-disable-next-line unicorn/no-useless-fallback-in-spread
...(config.resolver?.extraNodeModules ?? {}),
Expand Down
19 changes: 19 additions & 0 deletions apps/mobile/modules/t3-composer-editor/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'

group = 'com.t3tools.composereditor'
version = '0.0.0'

android {
namespace 'expo.modules.t3composereditor'
compileSdk rootProject.ext.compileSdkVersion

defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
}

dependencies {
implementation project(':expo-modules-core')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package expo.modules.t3composereditor

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class T3ComposerEditorModule : Module() {
override fun definition() = ModuleDefinition {
Name("T3ComposerEditor")

View(T3ComposerEditorView::class) {
Prop("controlledDocumentJson") { view: T3ComposerEditorView, documentJson: String ->
view.setControlledDocumentJson(documentJson)
}
Prop("themeJson") { view: T3ComposerEditorView, themeJson: String ->
view.setThemeJson(themeJson)
}
Prop("placeholder") { view: T3ComposerEditorView, placeholder: String ->
view.setPlaceholder(placeholder)
}
Prop("fontFamily") { view: T3ComposerEditorView, fontFamily: String ->
view.setFontFamily(fontFamily)
}
Prop("fontSize") { view: T3ComposerEditorView, fontSize: Double ->
view.setFontSize(fontSize.toFloat())
}
Prop("lineHeight") { view: T3ComposerEditorView, lineHeight: Double ->
view.setLineHeight(lineHeight.toFloat())
}
Prop("contentInsetVertical") { view: T3ComposerEditorView, contentInsetVertical: Double ->
view.setContentInsetVertical(contentInsetVertical.toInt())
}

Prop("singleLineCentered") { view: T3ComposerEditorView, singleLineCentered: Boolean ->
view.setSingleLineCentered(singleLineCentered)
}
Prop("editable") { view: T3ComposerEditorView, editable: Boolean ->
view.setEditable(editable)
}
Prop("scrollEnabled") { view: T3ComposerEditorView, scrollEnabled: Boolean ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Medium t3composereditor/T3ComposerEditorModule.kt:35

The scrollEnabled prop does not actually disable scrolling. setScrollEnabled only toggles editor.isVerticalScrollBarEnabled, which controls whether the scrollbar is drawn β€” not whether the view scrolls. When scrollEnabled={false} is passed from JS, the editor remains scrollable and only the scrollbar disappears, so the prop silently does not work. Consider disabling touch interception or overriding onTouchEvent to actually prevent scrolling when scrollEnabled is false.

Also found in 1 other location(s)

apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt:199

setScrollEnabled() at line 199 only assigns editor.isVerticalScrollBarEnabled. Android's setVerticalScrollBarEnabled() controls whether the scrollbar is drawn, not whether an EditText can actually scroll. When callers pass scrollEnabled=false, long composer contents can still be vertically scrolled; only the scrollbar disappears, so the exposed prop does not work on Android.

πŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt around line 35:

The `scrollEnabled` prop does not actually disable scrolling. `setScrollEnabled` only toggles `editor.isVerticalScrollBarEnabled`, which controls whether the scrollbar is drawn β€” not whether the view scrolls. When `scrollEnabled={false}` is passed from JS, the editor remains scrollable and only the scrollbar disappears, so the prop silently does not work. Consider disabling touch interception or overriding `onTouchEvent` to actually prevent scrolling when `scrollEnabled` is false.

Also found in 1 other location(s):
- apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt:199 -- `setScrollEnabled()` at line `199` only assigns `editor.isVerticalScrollBarEnabled`. Android's `setVerticalScrollBarEnabled()` controls whether the scrollbar is drawn, not whether an `EditText` can actually scroll. When callers pass `scrollEnabled=false`, long composer contents can still be vertically scrolled; only the scrollbar disappears, so the exposed prop does not work on Android.

view.setScrollEnabled(scrollEnabled)
}
Prop("autoFocus") { view: T3ComposerEditorView, autoFocus: Boolean ->
view.setAutoFocus(autoFocus)
}
Prop("autoCorrect") { view: T3ComposerEditorView, autoCorrect: Boolean ->
view.setAutoCorrect(autoCorrect)
}
Prop("spellCheck") { view: T3ComposerEditorView, spellCheck: Boolean ->
view.setSpellCheck(spellCheck)
}

Events(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Medium t3composereditor/T3ComposerEditorModule.kt:48

The Events(...) declaration omits onComposerSubmit, and T3ComposerEditorView defines no corresponding EventDispatcher or hardware-keyboard handler. As a result, the onSubmit prop exposed by ComposerEditorProps is never invoked on Android β€” hardware-keyboard submit that works on iOS silently does nothing here. Consider adding "onComposerSubmit" to the Events list, wiring up an onComposerSubmit dispatcher in the view, and detecting the submit key combination (e.g., Enter without Shift) to fire it.

πŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt around line 48:

The `Events(...)` declaration omits `onComposerSubmit`, and `T3ComposerEditorView` defines no corresponding `EventDispatcher` or hardware-keyboard handler. As a result, the `onSubmit` prop exposed by `ComposerEditorProps` is never invoked on Android β€” hardware-keyboard submit that works on iOS silently does nothing here. Consider adding `"onComposerSubmit"` to the `Events` list, wiring up an `onComposerSubmit` dispatcher in the view, and detecting the submit key combination (e.g., Enter without Shift) to fire it.

"onComposerChange",
"onComposerSelectionChange",
"onComposerFocus",
"onComposerBlur",
"onComposerPasteImages",
"onComposerContentSizeChange",
)

AsyncFunction("focus") { view: T3ComposerEditorView ->
view.focusEditor()
}
AsyncFunction("blur") { view: T3ComposerEditorView ->
view.blurEditor()
}
AsyncFunction("setSelection") { view: T3ComposerEditorView, start: Int, end: Int ->
view.setSelection(start, end)
}
}
}
}
Loading
Loading