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
f391794
fix(shared): classify workspace previews by literal filenames (#10311)
yashranaway Oct 3, 2026
1945ce8
fix(mobile): iOS threads no longer jump to the top (#14808)
znoraka Oct 3, 2026
44b4965
fix(web): reduce the gap above the draft composer (#15196)
Bil0000 Oct 3, 2026
a0bf1ee
fix(mobile): a dev server left running no longer shows the waiting bo…
jakeleventhal Oct 3, 2026
ce90eec
fix(server): a Claude command you stop shows as interrupted (#14896)
SunkenInTime Oct 3, 2026
0080e80
fix(server): editors appear once a slow discovery scan finishes (#13917)
bfowler Oct 3, 2026
aad7329
fix(server): Claude threads no longer stay stuck in plan mode Claude …
juliusmarminge Oct 3, 2026
65731f9
fix(mobile): show complete subagent details (#15189)
jakeleventhal Oct 3, 2026
77823bd
fix(mobile): an expired Live Activity no longer leaves a second card …
juliusmarminge Oct 3, 2026
cfdff56
test(web): remove redundant thread sort fallback tests (#15095)
t3-code[bot] Oct 3, 2026
70e5a40
fix(web): thinking row after a failed tool expands the run's tool cal…
maria-rcks Oct 3, 2026
39efcd8
perf(web): DOM changes no longer restyle the whole page (#15265)
flamboh Oct 3, 2026
71dbaf1
perf(usage): cut warm usage scans from seconds to milliseconds on lar…
t3dotgg Oct 3, 2026
fffe6e6
perf(web): virtualize command palette results (#15266)
flamboh Oct 3, 2026
0cfa113
chore(lint): flag :has() variants that restyle the whole page (#15274)
flamboh Oct 3, 2026
429c625
fix(web): workspace card docks beside chat when the window is narrow …
t3dotgg Oct 3, 2026
5e35272
feat(web): render mermaid code blocks as diagrams (#15067)
maria-rcks Oct 3, 2026
f1dcd93
feat(web): Nightly tells you to get the beta mobile app (#15070)
t3dotgg Oct 3, 2026
a21702d
test(server): ACP adapter tests no longer race the prompt settle (#15…
t3dotgg Oct 3, 2026
b05adb7
feat(usage): fold preview model IDs into the model they belong to (#1…
t3dotgg Oct 3, 2026
a78760e
refactor(server): check RPC scopes in group middleware (#15324)
juliusmarminge Oct 3, 2026
8d84666
feat(mobile): beta Working section hides busy threads until they need…
t3dotgg Oct 3, 2026
00eb8f6
fix(settings): symlinked settings files stay linked when saved (#15009)
yordis Oct 3, 2026
d65cd33
fix(server): Stop ends a dev server left running before a provider sw…
t3dotgg Oct 3, 2026
243d1e7
fix(server): merged threads settle even after the agent wakes on its …
t3dotgg Oct 3, 2026
c47f426
fix(web): no-project drafts can switch machines (#15356)
maria-rcks Oct 3, 2026
f786ff3
fix(web): highlight tool inputs and remove nested work log indentatio…
Yash-Singh1 Oct 3, 2026
5108c97
fix(server): restarts keep delegated tasks, queued threads, and stops…
maria-rcks Oct 3, 2026
a976f8c
fix(web): sending past the resume banner compacts first (#15290)
maria-rcks Oct 3, 2026
44bd4c9
fix(codex): resume archived native sessions (#15389)
maria-rcks Oct 3, 2026
f2cc80a
feat(web): morph composer and panel action icons (#14924)
jakeleventhal Oct 4, 2026
d307127
fix(web): subagents sent a follow-up show as running in Lineage (#15334)
scratchyone Oct 4, 2026
2d81e4c
fix(web): clear stale chat action shortcuts (#15394)
maria-rcks Oct 4, 2026
88744f3
fix(orchestration-v2): restore earlier app agent transcript pages (#1…
Bil0000 Oct 4, 2026
1b1e5b9
fix(web): remove the square thread info panel shadow (#15069)
PixPMusic Oct 4, 2026
8e39994
fix(mobile): Android usage widget no longer sticks on "Loading widget…
tris203 Oct 4, 2026
a6aebbe
fix(web): size the model picker to its content (#15152)
saphid Oct 4, 2026
2a45557
test(server): replay checks a Claude subagent's thread takes its repo…
juliusmarminge Oct 4, 2026
dab26f5
fix(web): subagent finish notifications look like subagent cards (#15…
flamboh Oct 4, 2026
4543c73
fix(web): thread status dot has an accessible name (#14587)
ryanilano Oct 4, 2026
c5bc98d
fix(web): legacy sidebar options button has a label (#14602)
ryanilano Oct 4, 2026
2b8bb0c
fix(web): imported themes keep switches and focus rings visible (#14498)
flamboh Oct 4, 2026
7bc161f
fix(web): links to issues no longer strand the pull request viewer (#…
flamboh Oct 4, 2026
237d51e
fix(web): repo/task breadcrumb no longer bounces when the sidebar col…
flamboh Oct 4, 2026
f90b77d
fix(web): Pull request panel entry works for linked PRs (#15061)
flamboh Oct 4, 2026
95edeb7
fix(web): add context menu to draft threads in the sidebar (#10637)
flamboh Oct 4, 2026
9efb016
fix(web): keep sidebar branding and build pills from clipping at vary…
flamboh Oct 4, 2026
0c81120
fix(usage): model shares and order follow the selected metric (#11391)
flamboh Oct 4, 2026
1826fb5
feat(web): sweep sidebar buttons to settle, un-settle, and wake threa…
argofowl Oct 4, 2026
7379933
feat: retry a failed workspace preparation (#15326)
juliusmarminge Oct 4, 2026
8fb068c
fix(server): registry test stubs no longer outlive the test run (#15457)
yordis Oct 4, 2026
60a56f1
test(server): the registry's fake Claude CLI is a fixture file, not a…
yordis Oct 4, 2026
845ddd9
refactor(clients): share opening a machine's No project folder (#14759)
bmdavis419 Oct 4, 2026
070127c
test(server): the git-ssh wrapper's fake SSH script is a fixture file…
yordis Oct 4, 2026
1508552
test(server): the ACP registry's fake npm is a fixture file, not a ge…
yordis Oct 4, 2026
f870c41
test(server): the ACP registry's fake uv is a fixture file, not a gen…
yordis Oct 4, 2026
c5a0c78
feat(clients): step a new thread to the next machine from the keyboar…
juliusmarminge Oct 4, 2026
d073c26
fix(web): promoting a draft thread no longer logs a React key warning…
yordis Oct 4, 2026
ee7b49d
test(server): the text generation's fake Claude CLI is a fixture file…
yordis Oct 4, 2026
bf5c146
fix(mobile): keep dictation running across navigation behind an edge …
juliusmarminge Oct 4, 2026
a92c43b
fix(client-runtime): relay disconnects no longer show as thread error…
juliusmarminge Oct 4, 2026
daa1d0e
fix(web): subagent cards name the provider account (#15493)
SunkenInTime Oct 4, 2026
f4f3abf
fix(server): read paginated review replies when watching PRs (#15427)
eimexdev Oct 4, 2026
fe93a5d
fix(server): offer one-click provider updates for every install (#15416)
maria-rcks Oct 4, 2026
930aec1
fix(mobile): keep the dictation timer from shifting width (#15504)
juliusmarminge Oct 4, 2026
4059607
fix(relay): T3 Connect links no longer fail on colliding prepared sta…
juliusmarminge Oct 4, 2026
0fe4fa4
fix(server): sqlite transactions wait for the write lock instead of f…
juliusmarminge Oct 4, 2026
6e0abd5
fix(web): unpin button shows the pin-off icon on hover (#15425)
flamboh Oct 4, 2026
ae010fe
fix(mobile): make queued message removal tappable (#15417)
PixPMusic Oct 4, 2026
e19a48d
fix(web): keep workspace panels below dialogs (#15454)
maria-rcks Oct 4, 2026
1302cca
fix(clients): Working section keeps its order while agents finish and…
t3dotgg Oct 4, 2026
d60a71e
feat(mobile): full-screen simulator viewer with on-demand controls (#…
juliusmarminge Oct 4, 2026
445af7e
Merge upstream main through d60a71ef6e
github-actions[bot] Oct 4, 2026
eac52f0
fix(client-runtime): closing a busy stream no longer drops the connec…
t3dotgg Oct 4, 2026
c8889b8
fix(server): set failureKind on launch retry git error
github-actions[bot] Oct 4, 2026
9462f9a
Merge upstream main through eac52f0087
github-actions[bot] Oct 4, 2026
0ead876
fix(web): stub router DOM globals in node unit tests
github-actions[bot] Oct 4, 2026
af302f7
fix(web): isolate the thread search atom mock
github-actions[bot] Oct 4, 2026
33b7d7b
feat(web): add shift-held pull request quick actions (#15549)
maria-rcks Oct 4, 2026
5a96895
fix: expanded tool calls show their output, empty ones don't expand (…
maria-rcks Oct 4, 2026
5a24b74
Merge upstream main through 5a96895a85
github-actions[bot] Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 28 additions & 0 deletions apps/desktop/src/settings/DesktopAppSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,34 @@ describe("DesktopSettings", () => {
),
);

it.effect("saves through a symlinked settings file without replacing the link", () =>
withSettings(
Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const settings = yield* DesktopAppSettings.DesktopAppSettings;
const dotfiles = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-settings-dotfiles-",
});
const linkedSettingsPath = `${dotfiles}/desktop-settings.json`;
yield* fileSystem.writeFileString(linkedSettingsPath, "{}\n");
yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
yield* fileSystem.symlink(linkedSettingsPath, environment.desktopSettingsPath);

yield* settings.setServerExposureMode("network-accessible");

assert.equal(
yield* fileSystem.readLink(environment.desktopSettingsPath),
linkedSettingsPath,
);
const persisted = yield* decodeDesktopSettingsPatch(
yield* fileSystem.readFileString(linkedSettingsPath),
);
assert.equal(persisted.serverExposureMode, "network-accessible");
}),
),
);

it.effect("migrates legacy implicit update channels to the runtime default", () =>
withSettings(
Effect.gen(function* () {
Expand Down
20 changes: 17 additions & 3 deletions apps/desktop/src/settings/DesktopAppSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type DesktopUpdateChannel,
} from "@t3tools/contracts";
import { fromLenientJson } from "@t3tools/shared/schemaJson";
import { resolveSymlinkTarget } from "@t3tools/shared/symlink";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -130,6 +131,7 @@ const settingsChange = (settings: DesktopSettings, changed: boolean): DesktopSet

const DesktopSettingsWriteOperation = Schema.Literals([
"create-temporary-file-name",
"resolve-symlink",
"encode-document",
"create-directory",
"write-temporary-file",
Expand Down Expand Up @@ -421,8 +423,20 @@ const writeSettings = Effect.fn("desktop.settings.writeSettings")(function* (inp
readonly defaultSettings: DesktopSettings;
readonly suffix: string;
}): Effect.fn.Return<void, DesktopSettingsWriteError> {
const directory = input.path.dirname(input.settingsPath);
const tempPath = `${input.settingsPath}.${process.pid}.${input.suffix}.tmp`;
const targetPath = yield* resolveSymlinkTarget(input.settingsPath).pipe(
Effect.provideService(FileSystem.FileSystem, input.fileSystem),
Effect.provideService(Path.Path, input.path),
Effect.mapError(
(cause) =>
new DesktopSettingsWriteError({
operation: "resolve-symlink",
path: input.settingsPath,
cause,
}),
),
);
const directory = input.path.dirname(targetPath);
const tempPath = `${targetPath}.${process.pid}.${input.suffix}.tmp`;
const encoded = yield* encodeDesktopSettingsJson(
toDesktopSettingsDocument(input.settings, input.defaultSettings),
).pipe(
Expand Down Expand Up @@ -455,7 +469,7 @@ const writeSettings = Effect.fn("desktop.settings.writeSettings")(function* (inp
}),
),
);
yield* input.fileSystem.rename(tempPath, input.settingsPath).pipe(
yield* input.fileSystem.rename(tempPath, targetPath).pipe(
Effect.mapError(
(cause) =>
new DesktopSettingsWriteError({
Expand Down
28 changes: 28 additions & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,34 @@ describe("DesktopClientSettings", () => {
),
);

it.effect("saves through a symlinked client settings file without replacing the link", () =>
withClientSettings(
Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const settings = yield* DesktopClientSettings.DesktopClientSettings;
const dotfiles = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-client-settings-dotfiles-",
});
const linkedSettingsPath = `${dotfiles}/client-settings.json`;
yield* fileSystem.writeFileString(linkedSettingsPath, "{}\n");
yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
yield* fileSystem.symlink(linkedSettingsPath, environment.clientSettingsPath);

yield* settings.set(clientSettings);

assert.equal(
yield* fileSystem.readLink(environment.clientSettingsPath),
linkedSettingsPath,
);
assert.deepEqual(
yield* decodeClientSettingsJson(yield* fileSystem.readFileString(linkedSettingsPath)),
clientSettings,
);
}),
),
);

it.effect.each([
{ label: "permission", reason: "PermissionDenied" },
{ label: "I/O", reason: "Unknown" },
Expand Down
20 changes: 17 additions & 3 deletions apps/desktop/src/settings/DesktopClientSettings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ClientSettingsSchema, type ClientSettings } from "@t3tools/contracts";
import { fromLenientJson } from "@t3tools/shared/schemaJson";
import { resolveSymlinkTarget } from "@t3tools/shared/symlink";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -41,6 +42,7 @@ export class DesktopClientSettingsReadError extends Schema.TaggedError<DesktopCl

const DesktopClientSettingsWriteOperation = Schema.Literals([
"create-temporary-file-name",
"resolve-symlink",
"encode-document",
"create-directory",
"write-temporary-file",
Expand Down Expand Up @@ -126,8 +128,20 @@ const writeClientSettings = Effect.fnUntraced(function* (input: {
readonly settings: ClientSettings;
readonly suffix: string;
}): Effect.fn.Return<void, DesktopClientSettingsWriteError> {
const directory = input.path.dirname(input.settingsPath);
const tempPath = `${input.settingsPath}.${process.pid}.${input.suffix}.tmp`;
const targetPath = yield* resolveSymlinkTarget(input.settingsPath).pipe(
Effect.provideService(FileSystem.FileSystem, input.fileSystem),
Effect.provideService(Path.Path, input.path),
Effect.mapError(
(cause) =>
new DesktopClientSettingsWriteError({
operation: "resolve-symlink",
path: input.settingsPath,
cause,
}),
),
);
const directory = input.path.dirname(targetPath);
const tempPath = `${targetPath}.${process.pid}.${input.suffix}.tmp`;
const encoded = yield* encodeClientSettingsJson(input.settings).pipe(
Effect.mapError(
(cause) =>
Expand Down Expand Up @@ -158,7 +172,7 @@ const writeClientSettings = Effect.fnUntraced(function* (input: {
}),
),
);
yield* input.fileSystem.rename(tempPath, input.settingsPath).pipe(
yield* input.fileSystem.rename(tempPath, targetPath).pipe(
Effect.mapError(
(cause) =>
new DesktopClientSettingsWriteError({
Expand Down
16 changes: 15 additions & 1 deletion apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ const config: ExpoConfig = {
// JS back handling survives it via react-native's Android 16 shim plus
// withAndroidPredictiveBackCompat on Android 13-15.
predictiveBackGestureEnabled: true,
// expo-sensors declares this for its pedometer, which the app does not use.
blockedPermissions: ["android.permission.ACTIVITY_RECOGNITION"],
},
web: {
favicon: variant.assets.appIcon,
Expand Down Expand Up @@ -443,7 +445,16 @@ const config: ExpoConfig = {
minSdkVersion: 24,
// kotlinx-io uses Kotlin 2.3's return-value checker annotation, while
// SDK 58 builds with Kotlin 2.2. It has no runtime behavior.
extraProguardRules: "-dontwarn kotlin.MustUseReturnValues",
//
// WorkManager 2.9 keeps InputMerger classes but not their constructors,
// and R8 full mode no longer keeps a default constructor implicitly.
// Without it no work request can start, so the Glance session behind
// the widget never renders and it stays on "Loading widget". WorkManager
// 2.10 ships this rule itself; drop it once the resolved version gets there.
extraProguardRules: [
"-dontwarn kotlin.MustUseReturnValues",
"-keep class * extends androidx.work.InputMerger { <init>(); }",
].join("\n"),
},
ios: {
deploymentTarget: "18.0",
Expand All @@ -456,6 +467,9 @@ const config: ExpoConfig = {
},
],
"./plugins/withIosCocoaPodsUuidCache.cjs",
// Only the accelerometer is used (device viewer shake). Compile out the
// pedometer so iOS needs no motion purpose string.
["expo-sensors", { motionPermission: false }],
// Must be listed BEFORE expo-widgets: same-type mods run last-registered-
// first, so registering earlier makes this plugin's mods run AFTER
// expo-widgets' — its dangerous mod wipes ios/ExpoWidgetsTarget/ (which
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ class T3KeyboardCommandsView(
var enabledCommands = emptySet<String>()

override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val copiesThreadReference =
event.action == KeyEvent.ACTION_DOWN &&
event.repeatCount == 0 &&
event.keyCode == KeyEvent.KEYCODE_C &&
event.isCtrlPressed &&
event.isShiftPressed &&
!event.isAltPressed &&
enabledCommands.contains("copyThreadReference")
if (copiesThreadReference) {
onCommand(mapOf("command" to "copyThreadReference"))
return true
val command = commandFor(event)?.takeIf { enabledCommands.contains(it) }
if (command != null) onCommand(mapOf("command" to command))
return command != null || super.dispatchKeyEvent(event)
}

private fun commandFor(event: KeyEvent): String? {
if (event.action != KeyEvent.ACTION_DOWN || event.repeatCount != 0 || !event.isCtrlPressed) {
return null
}
return when {
event.keyCode == KeyEvent.KEYCODE_C && event.isShiftPressed && !event.isAltPressed ->
"copyThreadReference"
event.keyCode == KeyEvent.KEYCODE_H && event.isShiftPressed && !event.isAltPressed ->
"cycleHost"
else -> null
}
return super.dispatchKeyEvent(event)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public final class T3KeyboardCommandsView: ExpoView {
title: "Copy PR Link or Thread ID"
),
enabledCommand("toggleSidebar", input: "\\", modifiers: .command, action: #selector(handleToggleSidebar), title: "Toggle Sidebar"),
enabledCommand("cycleHost", input: "h", modifiers: [.command, .shift], action: #selector(cycleHost), title: "Next Machine"),
].compactMap { $0 }
if isPad {
commands += (1...9).compactMap { index in
Expand Down Expand Up @@ -150,6 +151,7 @@ public final class T3KeyboardCommandsView: ExpoView {
@objc private func openReview() { emit("review") }
@objc private func copyThreadReference() { emit("copyThreadReference") }
@objc private func handleToggleSidebar() { emit("toggleSidebar") }
@objc private func cycleHost() { emit("cycleHost") }

private func emit(_ command: String) {
onCommand(["command": command])
Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"expo-paste-input": "^0.1.15",
"expo-quick-actions": "^6.0.2",
"expo-secure-store": "~58.0.2",
"expo-sensors": "~58.0.1",
"expo-sharing": "58.0.13",
"expo-splash-screen": "~58.0.4",
"expo-sqlite": "~58.0.8",
Expand All @@ -117,7 +118,7 @@
"react-native": "0.88.0-rc.3",
"react-native-gesture-handler": "~3.2.1",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-controller": "1.22.4",
"react-native-keyboard-controller": "1.22.6",
"react-native-nitro-markdown": "^0.5.0",
"react-native-nitro-modules": "0.35.9",
"react-native-reanimated": "4.7.0",
Expand Down
30 changes: 18 additions & 12 deletions apps/mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { OverlayPortalHost } from "./components/OverlayPortal";
import { shouldHandleAppLink } from "./lib/appLinking";
import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme";
import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator";
import { VoiceInputProvider } from "./features/voice-input/VoiceInputProvider";
import { GlobalVoiceInputControl } from "./features/voice-input/GlobalVoiceInputControl";

import "../global.css";

Expand Down Expand Up @@ -77,23 +79,27 @@ function AppContent() {
<GestureHandlerRootView className="flex-1">
<KeyboardProvider statusBarTranslucent>
<SafeAreaProvider>
<StatusBar barStyle={themeAppearance === "dark" ? "light-content" : "dark-content"} />
{/* The navigation theme drives the NATIVE header appearance: native-stack
<VoiceInputProvider>
<StatusBar barStyle={themeAppearance === "dark" ? "light-content" : "dark-content"} />
{/* The navigation theme drives the NATIVE header appearance: native-stack
forwards `dark` as the nav bar's overrideUserInterfaceStyle. Without
this, React Navigation defaults to its light theme and every native
header (glass buttons, title, materials) is forced light even when
the system is in dark mode. */}
<View style={{ flex: 1 }}>
<IncomingShareProvider>
<Navigation linking={appLinking} theme={navigationTheme} />
</IncomingShareProvider>
<ConfirmDialogHost />
<IdentityClaimGate />
<ThreadArrangementHost />
</View>
{/* Anchored-menu overlays render here — in-window, so the
<GlobalVoiceInputControl>
<View style={{ flex: 1 }}>
<IncomingShareProvider>
<Navigation linking={appLinking} theme={navigationTheme} />
</IncomingShareProvider>
<ConfirmDialogHost />
<IdentityClaimGate />
<ThreadArrangementHost />
</View>
</GlobalVoiceInputControl>
{/* Anchored-menu overlays render here — in-window, so the
keyboard stays up while a dropdown is open. */}
<OverlayPortalHost />
<OverlayPortalHost />
</VoiceInputProvider>
</SafeAreaProvider>
</KeyboardProvider>
</GestureHandlerRootView>
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,8 @@ const RootStackConfig = createNativeStackNavigator({
presentation: "fullScreenModal",
headerShown: false,
gestureEnabled: false,
autoHideHomeIndicator: true,
navigationBarHidden: true,
},
}),
ThreadReview: createNativeStackScreen({
Expand Down
Loading
Loading