Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const config: ExpoConfig = {
slug: "t3-code",
platforms: ["ios", "android"],
scheme: variant.scheme,
version: "1.0.1",
version: "1.0.2",
// Default: fingerprint policy so OTAs only reach binaries with matching native
// inputs (deps, config plugins, patches). Override with MOBILE_RUNTIME_VERSION_OVERRIDE
// when CI must ship pure-JS fixes to an already-installed binary (e.g. Free-plan
Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,12 @@ public final class T3TerminalView: ExpoView, UITextFieldDelegate {
return
}

if buffer.isEmpty {
feedData(Data("\u{1B}[3J\u{1B}[H\u{1B}[2J".utf8))
lastAppliedBuffer = ""
return
}

if buffer.hasPrefix(lastAppliedBuffer) {
let suffix = String(buffer.dropFirst(lastAppliedBuffer.count))
feedData(Data(suffix.utf8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export function ConnectionsRouteScreen() {
contentInsetAdjustmentBehavior="automatic"
showsVerticalScrollIndicator={false}
className="flex-1"
contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
contentContainerStyle={{
paddingBottom: Math.max(insets.bottom, 18) + 18,
paddingHorizontal: 20,
paddingTop: 16,
}}
Expand Down
10 changes: 6 additions & 4 deletions apps/mobile/src/features/settings/SettingsAuthRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useAuth } from "@clerk/expo";
import { AuthView, UserProfileView } from "@clerk/expo/native";
import { StackActions, useNavigation } from "@react-navigation/native";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { useEffect } from "react";
import { useCallback, useEffect } from "react";
import { View } from "react-native";

import { hasCloudPublicConfig } from "../cloud/publicConfig";
Expand All @@ -21,16 +21,18 @@ export function SettingsAuthRouteScreen() {

function ConfiguredSettingsAuthRouteScreen() {
const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
const navigation = useNavigation();
const handleHostBack = useCallback(() => navigation.goBack(), [navigation]);

return (
<>
<NativeStackScreenOptions options={{ title: isSignedIn ? "Account" : "Sign in" }} />
<NativeStackScreenOptions options={{ headerShown: false }} />
<View collapsable={false} className="flex-1 overflow-hidden bg-sheet">
{isLoaded ? (
isSignedIn ? (
<UserProfileView isDismissible={false} />
<UserProfileView isDismissible={false} onHostBack={handleHostBack} />
) : (
<AuthView isDismissible={false} />
<AuthView isDismissible={false} onHostBack={handleHostBack} />
)
) : null}
</View>
Expand Down
3 changes: 1 addition & 2 deletions apps/mobile/src/features/threads/NewTaskRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,9 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara
contentInsetAdjustmentBehavior="automatic"
showsVerticalScrollIndicator={false}
className="flex-1"
contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
contentContainerStyle={{
gap: 12,
paddingBottom: 8,
paddingBottom: Math.max(insets.bottom, 18) + 18,
paddingHorizontal: 20,
paddingTop: 8,
}}
Expand Down
24 changes: 18 additions & 6 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Image,
Platform,
Pressable,
StyleSheet,
useColorScheme,
View,
type ViewStyle,
Expand Down Expand Up @@ -769,14 +770,25 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
style={{
paddingTop: isExpanded ? 8 : 6,
paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6),
// Keep the top soft for a short blend into the feed, but make the
// lower band nearly opaque so timeline rows never read as sitting
// *inside* the composer chrome.
experimental_backgroundImage: isDarkMode
? "linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.82) 42%, rgba(0,0,0,0.96) 100%)"
: "linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.88) 42%, rgba(255,255,255,0.98) 100%)",
}}
>
{/* The backdrop gradient lives on a plain View: Reanimated's Animated.View
silently drops experimental_backgroundImage on Android, which left this
strip fully transparent and the feed text legible through the composer. */}
<View
pointerEvents="none"
style={[
StyleSheet.absoluteFill,
{
// Fork stops: keep the top soft for a short blend into the feed, but
// make the lower band nearly opaque so timeline rows never read as
// sitting *inside* the composer chrome.
experimental_backgroundImage: isDarkMode
? "linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.82) 42%, rgba(0,0,0,0.96) 100%)"
: "linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.88) 42%, rgba(255,255,255,0.98) 100%)",
},
]}
/>
<Animated.View
className="relative w-full self-center"
layout={COMPOSER_LAYOUT_TRANSITION}
Expand Down
57 changes: 45 additions & 12 deletions apps/web/src/components/SidebarV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,12 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
snoozeSupported: boolean;
// Renders the pin glyph. Pinned cards keep the full settle/snooze quick
// actions: settling clears the pin server-side, and snoozing hides the
// card until wake with the pin intact underneath. Pin/unpin themselves
// live in the context menu only.
// card until wake with the pin intact underneath. The glyph is also the
// in-row pin state cue (the pinned block has no header), so it always
// shows while pinned; it only becomes a clickable unpin quick-action once
// the pinning capability is confirmed, and stays a passive marker while
// the descriptor is not loaded. Pinning itself lives in the context menu.
pinningSupported: boolean;
isPinned: boolean;
// Compact wake countdown ("2h") for rows in the snoozed shelf.
snoozeWakeLabelText: string | null;
Expand Down Expand Up @@ -513,6 +517,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
onUnsettle: (threadRef: ScopedThreadRef) => void;
onSnooze: (threadRef: ScopedThreadRef, preset: SnoozePreset) => void;
onUnsnooze: (threadRef: ScopedThreadRef) => void;
onUnpin: (threadRef: ScopedThreadRef) => void;
onAcknowledgeWoke: (threadRef: ScopedThreadRef, visitedAt: string) => void;
onChangeRequestState: (threadKey: string, state: "open" | "closed" | "merged" | null) => void;
}) {
Expand All @@ -531,6 +536,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
onThreadClick,
onUnsettle,
onUnsnooze,
onUnpin,
renamingTitle,
thread,
variant,
Expand Down Expand Up @@ -798,6 +804,14 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
},
[onUnsnooze, threadRef],
);
const handleUnpinClick = useCallback(
(event: ReactMouseEvent) => {
event.preventDefault();
event.stopPropagation();
onUnpin(threadRef);
},
[onUnpin, threadRef],
);
const handleSnoozePreset = useCallback(
(preset: SnoozePreset) => {
onSnooze(threadRef, preset);
Expand Down Expand Up @@ -1107,11 +1121,23 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
<span className="flex-1" />
)}
{props.isPinned ? (
<PinIcon
aria-label="Pinned"
role="img"
className="size-3 shrink-0 text-muted-foreground/65"
/>
props.pinningSupported ? (
<button
type="button"
aria-label="Unpin thread"
title="Unpin thread"
onClick={handleUnpinClick}
className="inline-flex cursor-pointer items-center rounded-sm text-muted-foreground/65 outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
<PinIcon aria-hidden className="size-3 shrink-0" />
</button>
) : (
<PinIcon
aria-label="Pinned"
role="img"
className="size-3 shrink-0 text-muted-foreground/65"
/>
)
) : null}
{/* The visible state owns this slot's width: status at rest,
actions on hover/keyboard focus or while the popover is open. Keeping
Expand Down Expand Up @@ -3038,12 +3064,14 @@ export default function SidebarV2() {
}, [isMobile, router, setOpenMobile]);

const commandPaletteShortcutLabel = shortcutLabelForCommand(keybindings, "commandPalette.toggle");
// Same resolution as v1: prefer the local-thread binding, fall back to
// chat.new, no platform gating — web users have working shortcuts too.
const newThreadShortcutLabel =
shortcutLabelForCommand(keybindings, "chat.newLocal") ??
shortcutLabelForCommand(keybindings, "chat.new");
const boardShortcutLabel = shortcutLabelForCommand(keybindings, "board.open");
// The button mirrors chat.new: in multi-project setups both route through
// the command palette's "New thread in..." picker, and in single-project
// setups both create immediately. chat.newLocal always creates directly, so
// it is only a correct label when chat.new is unbound.
const newThreadShortcutLabel =
shortcutLabelForCommand(keybindings, "chat.new") ??
shortcutLabelForCommand(keybindings, "chat.newLocal");
return (
<>
<SidebarChromeHeader isElectron={isElectron} />
Expand Down Expand Up @@ -3550,6 +3578,10 @@ export default function SidebarV2() {
serverConfigs.get(thread.environmentId)?.environment.capabilities
.threadSnooze === true
}
pinningSupported={
serverConfigs.get(thread.environmentId)?.environment.capabilities
.threadPinning === true
}
isPinned={section === "pinned"}
snoozeWakeLabelText={
section === "snoozed" && thread.snoozedUntil != null
Expand Down Expand Up @@ -3590,6 +3622,7 @@ export default function SidebarV2() {
onUnsettle={attemptUnsettle}
onSnooze={attemptSnooze}
onUnsnooze={attemptUnsnooze}
onUnpin={attemptUnpin}
onAcknowledgeWoke={acknowledgeWoke}
onChangeRequestState={handleChangeRequestState}
/>
Expand Down
9 changes: 9 additions & 0 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ const isolatedUnitTestFiles = [
"src/components/preview/PreviewView.test.tsx",
"src/components/preview/openPreviewSession.test.ts",
"src/components/preview/openTerminalLinkInPreview.test.ts",
// Tests that mock `react` itself and drive components through
// reactHookHarness need their own module registry: under `isolate: false`
// the component graph may already be bound to the real react/compiler
// runtime by an earlier file in the same worker, and the mock then never
// applies — the compiled component reports a memo-cache hit and skips the
// hooks these tests assert on. Upstream ships these without isolation
// because it does not share registries; the fork does.
"src/components/settings/AddProviderInstanceDialog.environment.test.tsx",
"src/components/settings/ProviderSettingsPanel.environment.test.tsx",
"src/connection/storage.test.ts",
"src/contextMenuFallback.test.ts",
"src/environments/primary/bootstrap.test.ts",
Expand Down
13 changes: 13 additions & 0 deletions patches/@react-native__gradle-plugin@0.85.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b3c46a3bf5eb21c0bb1c9435b5582aecbc57d57a..14f1e9c2063ef10ca3f2ecbb2336b8a4aacfcc9c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -13,7 +13,7 @@ pluginManagement {
}
}

-plugins { id("org.gradle.toolchains.foojay-resolver-convention").version("0.5.0") }
+plugins { id("org.gradle.toolchains.foojay-resolver-convention").version("1.0.0") }

include(
":react-native-gradle-plugin",
Loading
Loading