Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
ab4c454
Add split-view mobile workspace layout
juliusmarminge Jun 23, 2026
e309c09
Add split-view mobile navigation and native controls
juliusmarminge Jun 24, 2026
4a2f33f
Improve mobile thread navigation and diff selection
juliusmarminge Jun 24, 2026
a2c6b39
Memoize thread navigation callbacks
juliusmarminge Jun 24, 2026
bee588a
Refine mobile layout for iPad and native glass headers
juliusmarminge Jun 26, 2026
50f3501
Update Expo iOS tooling and native glass header
Jun 27, 2026
6a75b97
Bump Clerk and Expo dependencies
juliusmarminge Jun 27, 2026
816bc4e
Refactor session handling and stream event processing
Jun 27, 2026
58eb4b1
Fix EAS iOS CocoaPods UUID cache
Jun 27, 2026
43e8ee8
Polish iPad mobile layout and native headers
Jun 27, 2026
f10eca4
Address iPad layout review feedback
Jun 27, 2026
c5b567f
Fix iPad header and input review issues
Jun 27, 2026
e218c04
Resolve remaining iPad review issues
Jun 27, 2026
103410b
Address mobile review lifecycle issues
Jun 27, 2026
ae2e81f
Fix split inspector review followups
Jun 27, 2026
7494eba
Fix file inspector route regressions
Jun 27, 2026
91722d4
Harden split inspector edge cases
Jun 27, 2026
be87e6c
Fix sidebar selection and file return
Jun 27, 2026
66b3816
Polish inspector routing behavior
Jun 27, 2026
3b22639
Stabilize inspector pane state
Jun 27, 2026
3369bb9
Reveal files inspector once
Jun 27, 2026
67f0494
Adopt native iOS glass navigation chrome
Jun 29, 2026
175523c
Group native iOS header controls
Jun 29, 2026
1d7cf9e
Polish native phone chrome
Jun 29, 2026
02aa919
Restore native iPad thread search
Jun 29, 2026
e9fa6e4
Wire native iPad thread toolbar
Jun 29, 2026
a54fb49
Recover stale mobile thread routes
Jun 30, 2026
16b9c78
Use native split thread header title
Jun 30, 2026
6c4b5d6
Align iPad detail header with Mail
Jun 30, 2026
8218569
Stabilize iPad thread header controls
Jun 30, 2026
734d879
Align iPad thread controls with Mail
Jun 30, 2026
71b5d48
Nudge iPad detail header controls inward
Jun 30, 2026
9741b6d
Hide split detail native title
Jun 30, 2026
f01988e
Fix native iOS thread header integration
Jun 30, 2026
dcaea07
Use Expo Router native home toolbar
Jun 30, 2026
433155f
Simplify iPhone thread native header
Jun 30, 2026
1de372b
Use native glass header for terminal route
Jun 30, 2026
3daa71d
Use shared native scroll edge effect in thread surfaces
Jun 30, 2026
5f29190
Use native edge chrome on file and review routes
Jun 30, 2026
7ff1fd0
Add native sidebar status subtitle
Jun 30, 2026
f849eae
Add split home compose toolbar button
Jun 30, 2026
e4faac5
Stop forcing native glass debug route
Jun 30, 2026
1327fd4
Match Mail sidebar selection on iPad
Jun 30, 2026
f6745a4
Hide thread search while inspector owns search
Jun 30, 2026
cb90c54
Add split sidebar control to terminal header
Jun 30, 2026
54eec5c
Use Mail-style overflow controls on mobile
Jun 30, 2026
96b4f8b
Use native iPad archive header search
Jun 30, 2026
0aae452
Share archive row swipe behavior
Jun 30, 2026
90babc9
Use native header search for file inspector
Jun 30, 2026
f0b2fc8
Use native header for git inspector
Jun 30, 2026
7e4dab7
Show native search in iPad thread header
Jun 30, 2026
fe1341a
Centralize native Mail search toolbar config
Jun 30, 2026
cbc606f
Tune selected sidebar status pill contrast
Jun 30, 2026
1885c11
Use native Mail toolbar for file search
Jun 30, 2026
ccaa0fd
Align inspector toolbar placement on iPad
Jun 30, 2026
c8f6d4b
Use native glass header for file previews
Jun 30, 2026
35c1127
Centralize native header scroll edge effects
Jun 30, 2026
af7583c
Use native scroll edge effects on list headers
Jun 30, 2026
662dd40
Use native new task sheet controls
Jun 30, 2026
e29c04b
Use compact native search in iPad thread header
Jun 30, 2026
b6ac481
Flatten mobile not found link style
Jun 30, 2026
e095a58
Remove stale thread toolbar search slot
Jun 30, 2026
73f5038
Polish native mobile Mail-style chrome
Jun 30, 2026
434b909
Group iPad detail header controls
Jun 30, 2026
22827d6
Tune native mobile glass toolbar grouping
Jun 30, 2026
b1247e1
Wire iPad header search to thread sidebar
Jun 30, 2026
45970f2
Move thread detail chrome to direct RNS
Jun 30, 2026
16fd7da
Replace Expo Router with native stack navigation
Jun 30, 2026
52b59fc
Structure mobile navigation around native stack
Jun 30, 2026
5df0b2c
Tighten mobile navigation targets
Jul 1, 2026
d43ed9f
Migrate mobile navigation to adaptive native stacks
juliusmarminge Jul 2, 2026
7417005
Make mobile layout and typography responsive
juliusmarminge Jul 2, 2026
83d6d78
Add native iPad sidebar chrome and richer thread list presentation
juliusmarminge Jul 2, 2026
7e23abe
Share thread list items between compact Home and iPad sidebar
juliusmarminge Jul 2, 2026
480b906
Fix row taps with true context menus and gate swipes while scrolling
juliusmarminge Jul 2, 2026
c66b952
Enable list recycling with recycling-safe thread rows
juliusmarminge Jul 3, 2026
ec9318b
Deliver the swipe scroll gate via context instead of extraData
juliusmarminge Jul 3, 2026
573c97c
Improve iPad workspace panes and terminal key handling
juliusmarminge Jul 3, 2026
9f54b06
chore: sync lockfile libc metadata after rebase onto main
juliusmarminge Jul 3, 2026
8a0c8f8
Improve mobile header layout on iPad
juliusmarminge Jul 3, 2026
d4c2133
Refine mobile thread layout for iPad responsiveness
juliusmarminge Jul 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 apps/mobile/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
included:
- ios/T3Code
- modules/t3-composer-editor/ios
- modules/t3-native-controls/ios
- modules/t3-terminal/ios
- modules/t3-review-diff/ios

Expand Down
15 changes: 14 additions & 1 deletion apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const VARIANT_CONFIG: Record<
readonly iosIcon: string;
readonly iosBundleIdentifier: string;
readonly androidPackage: string;
readonly relyingParty?: string;
}
> = {
development: {
Expand All @@ -25,20 +26,23 @@ const VARIANT_CONFIG: Record<
iosIcon: "./assets/icon-composer-dev.icon",
iosBundleIdentifier: "com.t3tools.t3code.dev",
androidPackage: "com.t3tools.t3code.dev",
relyingParty: "clerk.t3.codes",
},
preview: {
appName: "T3 Code Preview",
scheme: "t3code-preview",
iosIcon: "./assets/icon-composer-prod.icon",
iosBundleIdentifier: "com.t3tools.t3code.preview",
androidPackage: "com.t3tools.t3code.preview",
relyingParty: "clerk.t3.codes",
},
production: {
appName: "T3 Code",
scheme: "t3code",
iosIcon: "./assets/icon-composer-prod.icon",
iosBundleIdentifier: "com.t3tools.t3code",
androidPackage: "com.t3tools.t3code",
relyingParty: "clerk.t3.codes",
},
};

Expand Down Expand Up @@ -77,6 +81,14 @@ const config: ExpoConfig = {
icon: variant.iosIcon,
supportsTablet: true,
bundleIdentifier: variant.iosBundleIdentifier,
// Pin code signing to the T3 Tools team so non-interactive `expo run:ios`
// does not fall back to a personal team (which cannot sign app groups,
// Sign in with Apple, or push notification entitlements).
appleTeamId: "ARK85ZXQ4Z",
associatedDomains: [
`applinks:${variant.relyingParty}`,
`webcredentials:${variant.relyingParty}`,
],
infoPlist: {
NSAppTransportSecurity: {
NSAllowsArbitraryLoads: true,
Expand All @@ -101,7 +113,6 @@ const config: ExpoConfig = {
favicon: "./assets/favicon.png",
},
plugins: [
"expo-router",
"expo-font",
"expo-secure-store",
["@clerk/expo", { theme: "./clerk-theme.json" }],
Expand Down Expand Up @@ -139,6 +150,7 @@ const config: ExpoConfig = {
},
},
],
"./plugins/withIosCocoaPodsUuidCache.cjs",
[
"expo-widgets",
{
Expand All @@ -155,6 +167,7 @@ const config: ExpoConfig = {
],
},
],
"./plugins/withIosSceneLifecycle.cjs",
"./plugins/withAndroidCleartextTraffic.cjs",
],
extra: {
Expand Down
36 changes: 18 additions & 18 deletions apps/mobile/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -199,24 +199,24 @@
--font-bold: "DMSans_700Bold";

/* Keep this scale aligned with src/lib/typography.ts for native style props. */
--text-3xs: 10px;
--text-3xs--line-height: 13px;
--text-2xs: 11px;
--text-2xs--line-height: 15px;
--text-xs: 12px;
--text-xs--line-height: 16px;
--text-sm: 13px;
--text-sm--line-height: 18px;
--text-base: 15px;
--text-base--line-height: 22px;
--text-lg: 17px;
--text-lg--line-height: 22px;
--text-xl: 20px;
--text-xl--line-height: 26px;
--text-2xl: 24px;
--text-2xl--line-height: 30px;
--text-3xl: 28px;
--text-3xl--line-height: 34px;
--text-3xs: 11px;
--text-3xs--line-height: 14px;
--text-2xs: 12px;
--text-2xs--line-height: 16px;
--text-xs: 13px;
--text-xs--line-height: 17px;
--text-sm: 14px;
--text-sm--line-height: 19px;
--text-base: 16px;
--text-base--line-height: 23px;
--text-lg: 18px;
--text-lg--line-height: 23px;
--text-xl: 21px;
--text-xl--line-height: 28px;
--text-2xl: 26px;
--text-2xl--line-height: 32px;
--text-3xl: 30px;
--text-3xl--line-height: 36px;
}

/* ─── Custom utilities ──────────────────────────────────────────────── */
Expand Down
11 changes: 10 additions & 1 deletion apps/mobile/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
import { registerRootComponent } from "expo";
import "react-native-gesture-handler";
import "expo-router/entry";
import { featureFlags } from "react-native-screens";

import App from "./src/App";

// Required for react-native-screens' iOS FormSheet sizing fix when a nested
// native stack is rendered inside a non-fitToContents formSheet.
featureFlags.experiment.synchronousScreenUpdatesEnabled = true;

registerRootComponent(App);
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class T3ComposerEditorModule: Module {
"onComposerSelectionChange",
"onComposerFocus",
"onComposerBlur",
"onComposerSubmit",
"onComposerPasteImages",
"onComposerContentSizeChange"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ private final class ComposerTextView: UITextView {

var onPasteImages: (([String]) -> Void)?
var onAttributedMutation: (() -> Void)?
var onSubmit: (() -> Void)?

override var keyCommands: [UIKeyCommand]? {
var commands = super.keyCommands ?? []
let submit = UIKeyCommand(
input: "\r",
modifierFlags: .command,
action: #selector(submitMessage(_:))
)
submit.discoverabilityTitle = "Send Message"
submit.wantsPriorityOverSystemBehavior = true
commands.append(submit)
return commands
}

@objc private func submitMessage(_ sender: UIKeyCommand) {
onSubmit?()
}

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(paste(_:)) {
Expand Down Expand Up @@ -299,6 +317,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
let onComposerSelectionChange = EventDispatcher()
let onComposerFocus = EventDispatcher()
let onComposerBlur = EventDispatcher()
let onComposerSubmit = EventDispatcher()
let onComposerPasteImages = EventDispatcher()
let onComposerContentSizeChange = EventDispatcher()

Expand All @@ -320,6 +339,9 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
textView.onAttributedMutation = { [weak self] in
self?.emitTextChange()
}
textView.onSubmit = { [weak self] in
self?.onComposerSubmit([:])
}
addSubview(textView)

placeholderLabel.numberOfLines = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ function nodeKey(node: MarkdownNode, index: number): string {
return `${node.type}:${node.beg ?? index}:${node.end ?? index}`;
}

/** Code inside markdown scales with the base text size (12pt at the default 15pt body). */
function codeBlockFontSize(textStyle: NativeMarkdownTextStyle): number {
return Math.max(10, Math.round(textStyle.fontSize * 0.8));
}

function codeBlockLineHeight(textStyle: NativeMarkdownTextStyle): number {
return codeBlockFontSize(textStyle) + 6;
}

function nodeText(node: MarkdownNode): string {
if (node.content !== undefined) {
return node.content;
Expand Down Expand Up @@ -161,8 +170,8 @@ function HighlightedCodeText(props: {
style={{
color: props.textStyle.codeColor,
fontFamily: "ui-monospace",
fontSize: 12,
lineHeight: 18,
fontSize: codeBlockFontSize(props.textStyle),
lineHeight: codeBlockLineHeight(props.textStyle),
}}
>
{props.content}
Expand Down Expand Up @@ -196,8 +205,8 @@ function HighlightedCodeText(props: {
style={{
color: props.textStyle.codeColor,
fontFamily: "ui-monospace",
fontSize: 12,
lineHeight: 18,
fontSize: codeBlockFontSize(props.textStyle),
lineHeight: codeBlockLineHeight(props.textStyle),
}}
>
{keyedLines.map((line, lineIndex) => (
Expand Down Expand Up @@ -264,7 +273,7 @@ function NativeCodeBlock(props: {
flex: 1,
color: props.textStyle.mutedColor,
fontFamily: "ui-monospace",
fontSize: 12,
fontSize: codeBlockFontSize(props.textStyle),
}}
>
{languageLabel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,25 @@ function runKeySignature(run: NativeMarkdownTextRun): string {
].join(":");
}

const DEFAULT_BODY_FONT_SIZE = 15;
const DEFAULT_HEADING_FONT_SIZES = [22, 19, 17, 16, 15, 15] as const;

function resolveHeadingFontSize(textStyle: NativeMarkdownTextStyle, headingLevel: number): number {
const index = Math.max(0, Math.min(5, headingLevel - 1));
const configured = textStyle.headingFontSizes?.[index];
if (typeof configured === "number" && Number.isFinite(configured)) {
return configured;
}

const scale = textStyle.fontSize / DEFAULT_BODY_FONT_SIZE;
return Math.max(12, Math.round(DEFAULT_HEADING_FONT_SIZES[index] * scale));
}

function runStyle(run: NativeMarkdownTextRun, textStyle: NativeMarkdownTextStyle): TextStyle {
const isFile = run.fileIcon != null;
const isSkill = run.skillName != null;
const headingLevel = Math.max(1, Math.min(6, run.headingLevel ?? 1));
const headingFontSize = [22, 19, 17, 16, 15, 15][headingLevel - 1] ?? 15;
const headingFontSize = resolveHeadingFontSize(textStyle, headingLevel);
const isHeading = run.role === "heading";
const isCodeBlock = run.role === "code-block" || run.role === "code-language";
const hasParagraphStyle = run.headIndent !== undefined;
Expand Down Expand Up @@ -88,7 +102,7 @@ function runStyle(run: NativeMarkdownTextRun, textStyle: NativeMarkdownTextStyle
: isHeading
? headingFontSize
: run.role === "code-language"
? 11
? Math.max(10, Math.round(textStyle.fontSize * 0.73))
: run.code || isCodeBlock
? Math.max(12, textStyle.fontSize - 2)
: textStyle.fontSize,
Expand All @@ -98,9 +112,9 @@ function runStyle(run: NativeMarkdownTextRun, textStyle: NativeMarkdownTextStyle
: run.role === "list-break"
? textStyle.lineHeight + (run.spacing ?? 0)
: isHeading
? Math.max(headingFontSize + 6, 20)
? Math.max(headingFontSize + 6, textStyle.lineHeight + 2)
: isCodeBlock
? 18
? Math.max(16, textStyle.lineHeight - 2)
: textStyle.lineHeight,
fontStyle: run.italic ? "italic" : "normal",
fontWeight: isHeading || run.bold || isFile || isSkill ? "700" : "400",
Expand Down Expand Up @@ -148,6 +162,9 @@ export function NativeMarkdownSelectableText(props: {
// color-only child update can otherwise leave the previous appearance cached.
const appearanceKey = [
colorScheme ?? "unspecified",
props.textStyle.fontSize,
props.textStyle.lineHeight,
props.textStyle.headingFontSizes?.join(","),
props.textStyle.color,
props.textStyle.strongColor,
props.textStyle.mutedColor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface NativeMarkdownTextStyle {
readonly fontFamily: string;
readonly headingFontFamily: string;
readonly boldFontFamily: string;
readonly headingFontSizes?: ReadonlyArray<number>;
}

export interface MarkdownHighlightedToken {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"platforms": ["apple"],
"apple": {
"modules": ["T3NativeControlsModule", "T3KeyboardCommandsModule"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ExpoModulesCore
import UIKit

public final class T3HeaderButtonView: ExpoView {
private static let size: CGFloat = 44
private static let symbolSize: CGFloat = 18

private let button = UIButton(type: .system)
private var systemImage = "circle"

let onTriggered = EventDispatcher()

public required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)

isAccessibilityElement = false
button.frame = bounds
button.autoresizingMask = [.flexibleWidth, .flexibleHeight]
button.addTarget(self, action: #selector(handlePress), for: .primaryActionTriggered)
addSubview(button)
applyConfiguration()
}

public override var intrinsicContentSize: CGSize {
CGSize(width: Self.size, height: Self.size)
}

public func setLabel(_ label: String) {
button.accessibilityLabel = label
}

public func setSystemImage(_ systemImage: String) {
guard self.systemImage != systemImage else {
return
}
self.systemImage = systemImage
applyConfiguration()
}

private func applyConfiguration() {
var configuration: UIButton.Configuration
if #available(iOS 26.0, *) {
configuration = .glass()
configuration.cornerStyle = .capsule
} else {
configuration = .plain()
}

configuration.baseForegroundColor = .label
configuration.contentInsets = .zero
configuration.image = UIImage(systemName: systemImage)
configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(
pointSize: Self.symbolSize,
weight: .regular
)
button.configuration = configuration
}

@objc private func handlePress() {
onTriggered()
}
}
Loading
Loading