Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f193a68
fix(server): bypass owned caches on explicit provider refresh (#13109)
juliusmarminge Sep 22, 2026
d7819c1
chore(devices): bump agent-device to 0.21.12 (#13124)
juliusmarminge Sep 22, 2026
bed0b4c
chore(mobile): drop dead nitro-markdown tgz override and @expo/metro-…
juliusmarminge Sep 22, 2026
242816a
feat(web): show settings scope as a sentence at the top of the page (…
juliusmarminge Sep 22, 2026
db9a067
refactor(web): move settings scope pickers into breadcrumbs (#13165)
Yash-Singh1 Sep 23, 2026
7e65b22
feat(auth): share provider sign-in flows and credential bindings (#12…
juliusmarminge Sep 23, 2026
ec28eef
refactor(mobile): git sheets use uniwind platform variants instead of…
juliusmarminge Sep 23, 2026
3409651
chore(mobile): name the two project favicon caches by their job (#13160)
juliusmarminge Sep 23, 2026
6dcde21
revert(mobile): git sheets back to Platform.OS ternaries (un-guarded …
juliusmarminge Sep 23, 2026
5d493d6
docs(mobile): document the two mobile routes that intentionally skip …
juliusmarminge Sep 23, 2026
5821b77
refactor(mobile): break module cycles with focused extractions (#13151)
juliusmarminge Sep 23, 2026
f223312
fix(server): generate PR diffs from branch changes (#13170)
Yash-Singh1 Sep 23, 2026
68607c5
fix(web): preserve nested scroll behavior in chat timeline (#13167)
Yash-Singh1 Sep 23, 2026
b954af6
test(web): cover usage model ordering without static markup (#13104)
flamboh Sep 23, 2026
e4422ee
fix(desktop): find linuxbrew node for the WSL backend (#7827)
CodyRay Sep 23, 2026
ca864a2
chore(models): use GPT-6 Luna for text generation (#13115)
extoci Sep 23, 2026
a493946
fix(mobile): keep ordinary offline outbox failures out of console.war…
juliusmarminge Sep 23, 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
Binary file not shown.
4 changes: 0 additions & 4 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"@clerk/expo": "catalog:",
"@effect/atom-react": "catalog:",
"@expo-google-fonts/dm-sans": "^0.4.2",
"@expo/metro-runtime": "~57.0.14",
"@expo/ui": "~57.0.14",
"@legendapp/list": "catalog:",
"@material/material-color-utilities": "0.3.0",
Expand Down Expand Up @@ -137,9 +136,6 @@
"tailwindcss": "^4.0.0",
"typescript": "catalog:"
},
"overrides": {
"react-native-nitro-markdown": "file:deps/react-native-nitro-markdown-0.5.0.tgz"
},
"expo": {
"install": {
"exclude": [
Expand Down
17 changes: 17 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,15 @@ const SettingsContentStack = createNativeStackNavigator({
title: "Diagnostics",
},
}),
// Deliberately the one settings screen with no `linking:` path. Its params
// are a tap-time snapshot, not a stable resource address: `now` is the
// wall-clock of the tap, `environmentIds` is the usage screen's local
// filter selection, and the window id/kind identify freshly aggregated
// pools. React Navigation round-trips non-path params through the URL as
// query strings, so a path here would bake in a permanently stale
// timestamp and filter. The deep linkable surface is the list at
// `settings/usage`, which rebuilds this state and pushes the detail from a
// tapped account segment.
SettingsUsageAccount: createNativeStackScreen({
screen: UsageLimitAccountScreen,
options: { title: "Account" },
Expand Down Expand Up @@ -649,6 +658,14 @@ const RootStackConfig = createNativeStackNavigator({
linking: `${THREAD_LINKING_PREFIX}/attachments/:attachmentId`,
options: SOLID_HEADER_OPTIONS,
}),
// Deliberately the one root route with no `linking:` path. The route
// carries zero params: its content is a session object (staged model,
// provider groups, and live update callbacks) that the active
// ThreadComposer presents into ExistingThreadSettingsRouteProvider before
// pushing this screen — state no URL can reconstruct. Reached without a
// presented session the screen navigates straight back, so a path would
// only produce a flash-and-dismiss link. Deep links to a thread land on
// `threads/:environmentId/:threadId`, where this sheet is one tap away.
ThreadSettingsSheet: createNativeStackScreen({
screen: ExistingThreadSettingsRouteScreen,
options: {
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/src/components/ConfirmDialog.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type ConfirmDialogRequest = {
readonly title: string;
readonly message?: string;
readonly cancelText?: string;
readonly confirmText: string;
readonly destructive?: boolean;
readonly onConfirm: () => void;
readonly onCancel?: () => void;
};

export type TextInputDialogRequest = {
readonly title: string;
readonly initialValue: string;
readonly cancelText?: string;
readonly confirmText: string;
readonly onConfirm: (value: string) => void;
readonly onCancel?: () => void;
};
20 changes: 2 additions & 18 deletions apps/mobile/src/components/ConfirmDialogHost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,9 @@ import { Platform, Modal, Pressable, TextInput, View } from "react-native";
import { cn } from "../lib/cn";
import { AppText } from "./AppText";
import { MaterialConfirmDialog } from "./MaterialConfirmDialog";
import type { ConfirmDialogRequest, TextInputDialogRequest } from "./ConfirmDialog.types";

export type ConfirmDialogRequest = {
readonly title: string;
readonly message?: string;
readonly cancelText?: string;
readonly confirmText: string;
readonly destructive?: boolean;
readonly onConfirm: () => void;
readonly onCancel?: () => void;
};

export type TextInputDialogRequest = {
readonly title: string;
readonly initialValue: string;
readonly cancelText?: string;
readonly confirmText: string;
readonly onConfirm: (value: string) => void;
readonly onCancel?: () => void;
};
export type { ConfirmDialogRequest, TextInputDialogRequest } from "./ConfirmDialog.types";

type DialogRequest =
| { readonly kind: "confirm"; readonly request: ConfirmDialogRequest }
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/FilePreview.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { requireNativeModule } from "expo";
import { useEffect, useEffectEvent, useId } from "react";
import { Alert } from "react-native";

import type { ResolvedFilePreviewSource } from "./FilePreviewModal";
import type { ResolvedFilePreviewSource } from "./FilePreviewModal.types";

const NativeControls = requireNativeModule<{
presentFile(
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/FilePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Alert, Modal, Pressable, View } from "react-native";
import ImageViewing from "react-native-image-viewing";

import { openAttachmentInViewer } from "../lib/attachmentDownload";
import type { ResolvedFilePreviewSource } from "./FilePreviewModal";
import type { ResolvedFilePreviewSource } from "./FilePreviewModal.types";
import { MediaImagePreview } from "./MediaImagePreview";
import { AppText as Text } from "./AppText";

Expand Down
21 changes: 2 additions & 19 deletions apps/mobile/src/components/FilePreviewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
import { useIsFocused } from "@react-navigation/native";
import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
import { useEffect, useEffectEvent, useState } from "react";
import { Alert, Keyboard } from "react-native";

import type { FileBackedComposerAttachment } from "../lib/composerImages";
import { loadLocalAttachmentPreview } from "../lib/localAttachmentPreview";
import type { MediaActionsSource } from "../lib/mediaActions";
import { useRefreshAssetUrl } from "../state/assets";
import { FilePreview } from "./FilePreview";
import type { FilePreviewSource } from "./FilePreviewModal.types";

export interface ResolvedFilePreviewSource {
readonly kind: "image" | "pdf" | "document";
readonly mimeType?: string;
readonly uri: string;
readonly name?: string;
readonly sourceIdentifier?: string;
readonly srcFragment?: string;
readonly actionsSource?: MediaActionsSource;
}

export type FilePreviewSource = Omit<ResolvedFilePreviewSource, "uri"> &
(
| { readonly uri: string }
| { readonly attachment: FileBackedComposerAttachment }
| { readonly environmentId: EnvironmentId; readonly resource: AssetResource }
);
export type { FilePreviewSource, ResolvedFilePreviewSource } from "./FilePreviewModal.types";

function ResolvedFilePreview(props: {
readonly source: FilePreviewSource;
Expand Down
21 changes: 21 additions & 0 deletions apps/mobile/src/components/FilePreviewModal.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AssetResource, EnvironmentId } from "@t3tools/contracts";

import type { FileBackedComposerAttachment } from "../lib/composerImages";
import type { MediaActionsSource } from "../lib/mediaActions";

export interface ResolvedFilePreviewSource {
readonly kind: "image" | "pdf" | "document";
readonly mimeType?: string;
readonly uri: string;
readonly name?: string;
readonly sourceIdentifier?: string;
readonly srcFragment?: string;
readonly actionsSource?: MediaActionsSource;
}

export type FilePreviewSource = Omit<ResolvedFilePreviewSource, "uri"> &
(
| { readonly uri: string }
| { readonly attachment: FileBackedComposerAttachment }
| { readonly environmentId: EnvironmentId; readonly resource: AssetResource }
);
2 changes: 1 addition & 1 deletion apps/mobile/src/components/MaterialConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ConfirmDialogRequest } from "./ConfirmDialogHost";
import type { ConfirmDialogRequest } from "./ConfirmDialog.types";

export interface MaterialConfirmDialogProps {
readonly request: Pick<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SegmentedButton, SingleChoiceSegmentedButtonRow, Text } from "@expo/ui/
import { defaultMinSize, fillMaxWidth } from "@expo/ui/jetpack-compose/modifiers";
import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
import type { SegmentedControlProps } from "./SegmentedControl";
import type { SegmentedControlProps } from "./SegmentedControl.types";

/** Compose content shared by screen controls and native dialogs, inside their existing Host. */
export function MaterialSegmentedButtons<Value extends number | string>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MaterialSegmentedButtons } from "./MaterialSegmentedButtons.android";
import { View } from "react-native";

import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import type { SegmentedControlProps } from "./SegmentedControl";
import type { SegmentedControlProps } from "./SegmentedControl.types";

export function MaterialSegmentedControl<Value extends number | string>(
props: SegmentedControlProps<Value>,
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/MaterialSegmentedControl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SegmentedControlProps } from "./SegmentedControl";
import type { SegmentedControlProps } from "./SegmentedControl.types";

export function MaterialSegmentedControl<Value extends number | string>(
_props: SegmentedControlProps<Value>,
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/MaterialSwitch.android.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Host, Switch as ComposeSwitch } from "@expo/ui/jetpack-compose";
import { View } from "react-native";
import type { ThemedSwitchProps } from "./ThemedSwitch";
import type { ThemedSwitchProps } from "./MaterialSwitch.types";

import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";

Expand Down
12 changes: 12 additions & 0 deletions apps/mobile/src/components/MaterialSwitch.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { SwitchProps } from "react-native";

export type ThemedSwitchProps = Pick<
SwitchProps,
| "accessibilityHint"
| "accessibilityLabel"
| "disabled"
| "onValueChange"
| "style"
| "testID"
| "value"
>;
2 changes: 1 addition & 1 deletion apps/mobile/src/components/MediaImagePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useMediaActions } from "../lib/mediaActions";
import { AppText } from "./AppText";
import { SymbolView } from "./AppSymbol";
import type { ResolvedFilePreviewSource } from "./FilePreviewModal";
import type { ResolvedFilePreviewSource } from "./FilePreviewModal.types";
import { MediaActionsMenu } from "./MediaActionsMenu";
import { MediaSourceCaption } from "./MediaSourceCaption";

Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/ProjectFavicon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
hasLoadedProjectFavicon,
markProjectFaviconFailed,
markProjectFaviconLoaded,
} from "./projectFaviconCache";
} from "../lib/projectFaviconRequests";

const EMPTY_FAVICON_URL = Atom.make<string | null>(null);

Expand Down
16 changes: 2 additions & 14 deletions apps/mobile/src/components/SegmentedControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ import Animated, { Easing, LinearTransition, ReduceMotion } from "react-native-r
import { AppText as Text } from "./AppText";
import { cn } from "../lib/cn";
import { MaterialSegmentedControl } from "./MaterialSegmentedControl";
import type { SegmentedControlProps } from "./SegmentedControl.types";

export interface SegmentedControlProps<Value extends number | string> {
readonly options: readonly {
readonly value: Value;
readonly label: string;
readonly accessibilityLabel?: string;
}[];
readonly selected: Value;
readonly onSelect: (value: Value) => void;
/** Compact sizing applies to the non-Material control. */
readonly size?: "default" | "compact";
/** "tab" for the view switcher; filters stay plain buttons. */
readonly role?: "tab" | "button";
readonly className?: string;
}
export type { SegmentedControlProps } from "./SegmentedControl.types";

export function SegmentedControl<Value extends number | string>(
props: SegmentedControlProps<Value>,
Expand Down
14 changes: 14 additions & 0 deletions apps/mobile/src/components/SegmentedControl.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface SegmentedControlProps<Value extends number | string> {
readonly options: readonly {
readonly value: Value;
readonly label: string;
readonly accessibilityLabel?: string;
}[];
readonly selected: Value;
readonly onSelect: (value: Value) => void;
/** Compact sizing applies to the non-Material control. */
readonly size?: "default" | "compact";
/** "tab" for the view switcher; filters stay plain buttons. */
readonly role?: "tab" | "button";
readonly className?: string;
}
14 changes: 3 additions & 11 deletions apps/mobile/src/components/ThemedSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { Platform, Switch, type SwitchProps } from "react-native";
import { Platform, Switch } from "react-native";

import { MaterialSwitch } from "./MaterialSwitch";
import type { ThemedSwitchProps } from "./MaterialSwitch.types";

export type ThemedSwitchProps = Pick<
SwitchProps,
| "accessibilityHint"
| "accessibilityLabel"
| "disabled"
| "onValueChange"
| "style"
| "testID"
| "value"
>;
export type { ThemedSwitchProps } from "./MaterialSwitch.types";

export function ThemedSwitch(props: ThemedSwitchProps) {
if (Platform.OS === "android") {
Expand Down
9 changes: 6 additions & 3 deletions apps/mobile/src/connection/environment-cache-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import * as Option from "effect/Option";
import * as Schema from "effect/Schema";

import * as MobileDatabase from "../persistence/mobile-database";
import { attachProjectFaviconDatabase, projectFaviconCache } from "../lib/projectFaviconCache";
import {
attachProjectFaviconDatabase,
projectFaviconDatabaseCache,
} from "../lib/projectFaviconDatabaseCache";

const SHELL_SNAPSHOT_CACHE_SCHEMA_VERSION = 1;
// v3 adds windowed (paginated) snapshots carrying `page` metadata; the bump
Expand Down Expand Up @@ -130,7 +133,7 @@ export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () {
decode: decodeStoredShellSnapshot,
select: (stored) =>
stored.environmentId === environmentId ? Option.some(stored.snapshot) : Option.none(),
}).pipe(Effect.tap(() => Effect.promise(() => projectFaviconCache.hydrate()))),
}).pipe(Effect.tap(() => Effect.promise(() => projectFaviconDatabaseCache.hydrate()))),
),
saveShell: Effect.fn("MobileEnvironmentCache.saveShell")(function* (environmentId, snapshot) {
const payload = yield* encodeStoredShellSnapshot({
Expand Down Expand Up @@ -241,7 +244,7 @@ export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () {
.pipe(Effect.mapError(mapDatabaseError("clear-vcs-refs"))),
),
clear: Effect.fn("MobileEnvironmentCache.clear")((environmentId) =>
Effect.promise(() => projectFaviconCache.clearEnvironment(environmentId)).pipe(
Effect.promise(() => projectFaviconDatabaseCache.clearEnvironment(environmentId)).pipe(
Effect.andThen(database.clearEnvironmentCache(environmentId)),
Effect.mapError(mapDatabaseError("clear-environment")),
),
Expand Down
Loading
Loading