Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b5b29e7
fix(server): stream tight list items one at a time in paragraph mode …
juliusmarminge Sep 15, 2026
7cafe52
fix(server): keep thread titles tied to user intent (#10720)
t3dotgg Sep 15, 2026
e9b0555
Remove labels from effect service conventions
juliusmarminge Sep 15, 2026
537dc0f
Remove labels from ui-consistency.md
juliusmarminge Sep 15, 2026
970a873
Change conclusion status from failure to neutral
juliusmarminge Sep 15, 2026
8b9f6d3
Change conclusion from 'failure' to 'neutral'
juliusmarminge Sep 15, 2026
08abda9
refactor(server): resolve title links through source control provider…
juliusmarminge Sep 15, 2026
a62e7d6
refactor(server): align title generation with Effect conventions (#11…
juliusmarminge Sep 15, 2026
5623089
fix(server): disable color probes in worktree setup (#11843)
juliusmarminge Sep 15, 2026
0310cbf
fix: keep worktree setup visible after leaving and reopening the thre…
t3dotgg Sep 15, 2026
b20d29d
fix(desktop): prevent startup from running twice (#11857)
juliusmarminge Sep 15, 2026
26b8f98
feat(mobile): add iPad keyboard shortcuts and command palette (#11679)
bmdavis419 Sep 15, 2026
2c19283
feat(server): persist the worktree setup send and progress on the thr…
juliusmarminge Sep 15, 2026
cc839c4
feat(web): queue messages sent client-side while the agent is working…
t3dotgg Sep 15, 2026
5b377e2
fix(server): bound Git process bursts to keep connections responsive …
Bil0000 Sep 15, 2026
a37b852
perf(server): speed up worktree fetch and checkout (#11633)
Bil0000 Sep 15, 2026
9ea892e
fix(client): show thread state changes before remote replies (#11408)
Bil0000 Sep 15, 2026
6ecc15f
fix(mobile): restrict row highlighting to pointer input (#11863)
juliusmarminge Sep 15, 2026
50ff4c3
fix(mobile): ensure a compatible native client before verification (#…
juliusmarminge Sep 15, 2026
3c4c9a1
fix(web): restore composer focus after closing option menus (#11884)
Bil0000 Sep 15, 2026
bf3be75
fix(web): center refresh devices in the empty state (#11808)
shivamhwp Sep 15, 2026
e33b710
fix(mobile): match command palette colors to sheets (#11861)
juliusmarminge Sep 15, 2026
ae53072
fix(web): keep the composer ready during background worktree setup (#…
Bil0000 Sep 15, 2026
c1b2210
fix(desktop): keep the sidebar brand and window buttons aligned (#11906)
shivamhwp Sep 15, 2026
9a6b57b
fix(web): drop the filled well behind the sidebar header buttons (#11…
flamboh Sep 15, 2026
2a264ad
fix(server): explain how to configure a missing Codex executable (#11…
shivamhwp Sep 15, 2026
d1790aa
fix(mobile): add missing thread rename action (#11503)
Michel-Liao Sep 15, 2026
caf8b5d
fix(mobile): wait for thread deep link hydration (#11502)
Michel-Liao Sep 15, 2026
d07ffbe
fix(mobile): keep iOS chat rows aligned after measurement (#11813)
dominic-r Sep 15, 2026
438465d
feat: add customizable soft-tint project monograms (#11845)
eimexdev Sep 15, 2026
24b711b
fix: multiple UI and server bug fixes (#11593)
kridaydave Sep 15, 2026
7235701
fix(server): release preview hosts after unanswered requests (#11381)
yashranaway Sep 15, 2026
3efdcc5
Preserve diff tree order and collapsed folders (#11931)
juliusmarminge Sep 15, 2026
a5da327
fix(client-runtime): preserve cached turns and older-page loading (#8…
lnieuwenhuis Sep 15, 2026
844203d
chore(deps): bump Clerk stack to latest stable versions (#11956)
juliusmarminge Sep 15, 2026
b18a560
fix(mobile): update Reanimated and Worklets (#11957)
juliusmarminge Sep 15, 2026
96bddf8
fix(desktop): paste as text no longer doubles the pasted text (#11958)
TonybynMp4 Sep 15, 2026
719a76c
feat(web): choose queue or steer for follow-up messages (#11964)
Bil0000 Sep 15, 2026
e6ae764
feat(mobile): add v2 preview store builds (#11966)
juliusmarminge Sep 15, 2026
2c16c1d
fix(mobile): block incompatible server connections (#11974)
juliusmarminge Sep 15, 2026
f0a0ead
fix(web): keep PR controls readable in narrow panels (#11962)
Bil0000 Sep 15, 2026
b84f63b
fix(server): block updates under legacy service launchers (#11940)
Gigioxx Sep 15, 2026
f4600d7
fix: reduce GitHub quota use with sharing enabled (#11888)
Bil0000 Sep 15, 2026
87a12b5
fix(usage): refresh limits when the tab opens (#11928)
Bil0000 Sep 15, 2026
6f7aaff
fix(contracts): avoid Intl.Segmenter in monogram validation (Hermes c…
bompus Sep 15, 2026
37a8ab2
feat(lint): extend Hermes API bans with a configurable API list (#11982)
juliusmarminge Sep 15, 2026
b12c92f
fix(server): reuse Git index metadata during checkpoint capture (#10792)
im-kvijay Sep 15, 2026
7a368fe
refactor: give project monograms their own icon variant (#11993)
juliusmarminge Sep 16, 2026
935c55b
fix(clients): disable incompatible environments during discovery (#11…
juliusmarminge Sep 16, 2026
8c18b5b
fix(antigravity): stop health checks from filling the disk with _MEI …
t3dotgg Sep 16, 2026
7db37d3
chore: merge upstream/main while preserving fork features
Fryuni Sep 16, 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
2 changes: 2 additions & 0 deletions .agents/skills/ios-debugger-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Avoid generic Mac window automation for switching among Simulator windows. Expli

## Choose build or launch

For T3 Code Mobile, run `node scripts/mobile-native-client.ts ensure ios <simulator-udid>` from the checkout on the simulator host first. It checks the local Expo native fingerprint against the installed client and builds/installs when stale, missing, or unknown. Then launch with the intended Metro bundle. Authorized verification includes native builds and installs; do not stop because the existing client is old. Use `check` instead of `ensure` only when the user explicitly prohibits rebuilding or requests a read-only check.

- Use `build_run_sim` when native source, native dependencies, entitlements, or project configuration changed.
- Use `test_sim` for the smallest relevant native test target or test cases; do not run an entire workspace test matrix routinely.
- Use `launch_app_sim` when a compatible app is already installed and no native rebuild is needed.
Expand Down
32 changes: 17 additions & 15 deletions .agents/skills/test-t3-mobile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@ Inspect the host and the affected code before launching processes:

- On macOS with Xcode, prefer one representative iOS Simulator when the change is cross-platform so the user can watch through serve-sim. Load and follow [`ios-debugger-agent`](../ios-debugger-agent/SKILL.md), and load [`ios-simulator-browser`](../ios-simulator-browser/SKILL.md) when live streaming is available.
- On macOS, Linux, or Windows with the Android SDK, use one Android Emulator when Android is the affected surface or iOS tooling is unavailable.
- When the change is platform-specific, test that platform. When neither platform is viable, report the missing SDK, emulator, or dev-client prerequisite rather than claiming verification.
- When the change is platform-specific, test that platform. When neither platform is viable, report the missing SDK or emulator prerequisite rather than claiming verification. A missing development client is a build step, not a blocker.

Do not treat unavailable iOS tooling as a blocker when Android is a valid representative target.

## Choose the lightest valid launch path
## Ensure a compatible native client

- For JavaScript, TypeScript, or asset-only changes, reuse a compatible installed development client and start Metro. Do not rebuild native code merely to load a new bundle.
- For native source, native dependencies, entitlements, config plugins, or generated project changes, rebuild the affected platform.
- Use `vp run ios:dev` or `vp run android:dev` only when an Expo clean prebuild is actually required; both commands regenerate the native project.
- If the user requested no native rebuild and no compatible app is installed, reuse an existing compatible `.app` or `.apk` artifact when available. Otherwise report the missing dev client instead of silently rebuilding.
Authorized mobile verification includes building and installing a development client. A missing, stale, or unknown native client is not a reason to skip verification or leave a PR in draft. Build and install it, then continue. Respect an explicit user instruction not to rebuild; otherwise do not ask for separate permission.

The development identity on both platforms is:
Run this from the checkout being tested, on the machine that hosts the selected simulator or emulator. Select and boot one explicit iOS UDID or Android emulator serial first:

- App: `T3 Code Dev`
- Bundle/package identifier: `com.t3tools.t3code.dev`
- URL scheme: `t3code-dev`
```bash
node scripts/mobile-native-client.ts ensure ios <simulator-udid>
node scripts/mobile-native-client.ts ensure android <emulator-serial>
```

`ensure` compares the checkout's local Expo development fingerprint and the installed app's binary contents against the last successful build record. It reuses a matching client; otherwise it runs a clean prebuild, builds and installs the development app, and records the successful result. It does not start Metro. Start Metro below after it succeeds. On hosts with an `agent-job` requirement, run the entire `ensure` command through that queue.

For a read-only decision, use `check` in place of `ensure`. Exit 0 means compatible, 2 means build required, and 1 means an operational error. An app installed outside this helper is initially unknown and gets rebuilt once. Records are local to the simulator host under `~/.cache/t3code/native-clients` and work across checkouts. Do not copy records between machines or write them manually.

A JavaScript-only diff, bundle identifier, app version, or recent install date does not prove native compatibility. Always check the whole checkout. Expo fingerprints are computed locally with `APP_VARIANT=development`; no EAS credentials or cloud build are required. Generated `ios/` and `android/` directories are excluded by `.fingerprintignore`, so edit native source modules or config plugins rather than generated output.

Bundle or package presence proves the correct variant, not native compatibility. Reuse it only when the current changes did not alter its Expo SDK, native dependencies, config plugins, entitlements, generated project, or native source.
The development identity is `T3 Code Dev`, bundle/package `com.t3tools.t3code.dev`, scheme `t3code-dev`. If a build fails, investigate the build error and fix the local prerequisites. Report the concrete failure if it cannot be resolved, not “no compatible client.”

## Start one disposable T3 environment

Expand Down Expand Up @@ -98,21 +102,19 @@ Use `ios-debugger-agent` to select one UDID and set these XcodeBuildMCP session
- Simulator ID: the selected UDID
- Bundle ID: `com.t3tools.t3code.dev`

Check the installed client with:
After `ensure` succeeds, open the Metro URL:

```bash
xcrun simctl get_app_container <simulator-udid> com.t3tools.t3code.dev app
xcrun simctl openurl <simulator-udid> <printed-dev-client-url>
```

Accept the iOS confirmation prompt and dismiss the developer menu when it obscures the app.

### Android launch

Select one running emulator serial from `adb devices` and check the installed client:
Use the emulator serial already checked by `ensure`:

```bash
adb -s <emulator-serial> shell pm path com.t3tools.t3code.dev
adb -s <emulator-serial> reverse tcp:<metro-port> tcp:<metro-port>
adb -s <emulator-serial> shell am start -W \
-a android.intent.action.VIEW \
Expand Down
5 changes: 1 addition & 4 deletions .macroscope/check-run-agents/effect-service-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ include:
- "infra/**/*.ts"
exclude:
- "**/*.test.ts"
labels:
- vouch:trusted
- macroscope-review
requires:
- Check
maxBudgetPerRun: 5
maxBudgetPerPR: 25
conclusion: failure
conclusion: neutral
showToolCalls: true
---

Expand Down
5 changes: 1 addition & 4 deletions .macroscope/check-run-agents/ui-consistency.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ include:
- "apps/web/src/**/*.css"
exclude:
- "apps/web/src/**/*.test.tsx"
labels:
- vouch:trusted
- macroscope-review
requires:
- Check
maxBudgetPerRun: 2
maxBudgetPerPR: 10
conclusion: failure
conclusion: neutral
---

# UI consistency review
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real
- The server is event-sourced and its async flows emit typed receipts. Wait on receipts and worker drains, never on sleeps or polling. A test that needs a timeout to pass is wrong.
- Upon request, user-visible frontend changes should get one integrated pass in a real client: `test-t3-app` for web, `test-t3-mobile` for mobile. The primary agent does this once after integrating. Subagents do not launch their own dev servers. Ask permission before doing computer use or spinning up browsers.

For authorized mobile verification, a missing or outdated native client is a build step, not a blocker. Run `node scripts/mobile-native-client.ts ensure <ios|android> <device-id>` on the simulator host before starting Metro. It checks the local Expo fingerprint and builds/installs when needed. See `test-t3-mobile` for the full workflow.

## Pull requests

- Never make a PR unless the developer explicitly asks you to do so.
Expand Down
110 changes: 110 additions & 0 deletions apps/desktop/scripts/main-process-bundle.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as NodeFSP from "node:fs/promises";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeVM from "node:vm";
import { build } from "vite-plus/pack";
import { assert, it } from "vite-plus/test";

import desktopConfig from "../vite.config.ts";

it("keeps lazy Linux imports and worker bundles from executing desktop startup twice", async () => {
const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-desktop-bundle-"));
try {
const workerEntries = [
"src/electron/WindowsForegroundFocusWorker.ts",
"src/snapShot/GlobalShiftShortcutWorker.ts",
"src/snapShot/RegionSnapShotWorker.ts",
"src/snapShot/SnapShotAccessibilityWorker.ts",
];
await Promise.all([
NodeFSP.mkdir(NodePath.join(directory, "src/electron"), { recursive: true }),
NodeFSP.mkdir(NodePath.join(directory, "src/snapShot"), { recursive: true }),
]);
await Promise.all([
NodeFSP.writeFile(
NodePath.join(directory, "src/main.ts"),
`import { shared } from "./shared.ts";
process.emit("startup", shared.value);
void import("./linux.ts").then(({ result }) => process.emit("ready", result));`,
),
NodeFSP.writeFile(
NodePath.join(directory, "src/shared.ts"),
"export const shared = { value: 42 };",
),
NodeFSP.writeFile(
NodePath.join(directory, "src/linux.ts"),
'import { shared } from "./shared.ts"; export const result = shared.value + 1;',
),
...workerEntries.map((entry) =>
NodeFSP.writeFile(
NodePath.join(directory, entry),
'import { shared } from "../shared.ts"; process.emit("worker", shared.value);',
),
),
]);
assert.ok(Array.isArray(desktopConfig.pack));
const fixtureEntries = new Set(["src/main.ts", ...workerEntries]);
for (const packConfig of desktopConfig.pack) {
if (!Array.isArray(packConfig.entry)) continue;
if (!packConfig.entry.some((entry) => fixtureEntries.has(entry))) continue;
await build({
...packConfig,
config: false,
cwd: directory,
tsconfig: false,
sourcemap: false,
onSuccess: undefined,
logLevel: "silent",
});
}

const outputDirectory = NodePath.join(directory, "dist-electron");
const filenames = (await NodeFSP.readdir(outputDirectory, { recursive: true })).filter(
(filename) => filename.endsWith(".cjs"),
);
const sources = new Map(
await Promise.all(
filenames.map(async (filename) => {
const path = NodePath.join(outputDirectory, filename);
return [path, await NodeFSP.readFile(path, "utf8")];
}),
),
);
const modules = new Map();
const startups = [];
const workers = [];
const ready = Promise.withResolvers();
const load = (filename, cacheModule = true) => {
const cached = modules.get(filename);
if (cached) return cached.exports;
const module = { exports: {} };
if (cacheModule) modules.set(filename, module);
const source = sources.get(filename);
assert.ok(source, `Missing bundle: ${filename}`);
NodeVM.runInNewContext(source, {
exports: module.exports,
module,
require: (specifier) => load(NodePath.resolve(NodePath.dirname(filename), specifier)),
process: {
emit: (event, value) => {
if (event === "startup") startups.push(value);
if (event === "worker") workers.push(value);
if (event === "ready") ready.resolve(value);
},
},
});
return module.exports;
};

load(NodePath.join(outputDirectory, "main.cjs"), false);
assert.equal(await ready.promise, 43);
assert.deepEqual(startups, [42]);
for (const entry of workerEntries) {
load(NodePath.join(outputDirectory, entry.replace(/^src\//, "").replace(/\.ts$/, ".cjs")));
}
assert.deepEqual(workers, [42, 42, 42, 42]);
assert.deepEqual(startups, [42]);
} finally {
await NodeFSP.rm(directory, { recursive: true, force: true });
}
});
15 changes: 14 additions & 1 deletion apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
DesktopSnapShotEvent,
} from "@t3tools/contracts";
import { exposeClerkBridge } from "@clerk/electron/preload";
import { contextBridge, ipcRenderer } from "electron";
import { contextBridge, ipcRenderer, webFrame } from "electron";

import * as IpcChannels from "./ipc/channels.ts";

Expand All @@ -32,6 +32,19 @@ exposeClerkBridge({ passkeys: true });
// oxlint-disable-next-line t3code/no-global-process-runtime -- Electron exposes the client platform in its sandboxed preload process.
const clientPlatform = process.platform;

if (clientPlatform === "darwin") {
// Native window buttons do not scale with Chromium zoom. Keep their reserved
// space in native points, including when a zoomed page is reloaded.
const syncWindowControlInset = () => {
document.documentElement.style.setProperty(
"--desktop-window-controls-inset",
`${90 / webFrame.getZoomFactor()}px`,
);
};
window.addEventListener("DOMContentLoaded", syncWindowControlInset, { once: true });
window.addEventListener("resize", syncWindowControlInset);
}

function unwrapEnsureSshEnvironmentResult(result: unknown) {
if (
typeof result === "object" &&
Expand Down
31 changes: 31 additions & 0 deletions apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,37 @@ describe("DesktopApplicationMenu", () => {
}),
);

// Chromium pastes as plain text for the accelerator on its own. Dispatching
// the action as well injects a second paste, which doubles the pasted text.
it.effect("leaves the accelerator to Chromium instead of injecting a paste", () =>
Effect.gen(function* () {
const selectedAction = yield* Deferred.make<string>();
const applicationMenuTemplate =
yield* Deferred.make<readonly Electron.MenuItemConstructorOptions[]>();

yield* configureMenu(selectedAction, applicationMenuTemplate);

const template = yield* Deferred.await(applicationMenuTemplate);
const editMenu = template.find((item) => item.label === "Edit");
if (!Array.isArray(editMenu?.submenu)) {
throw new Error("Expected Edit menu submenu to be an array.");
}
const pasteAsTextItem = editMenu.submenu.find((item) => item.label === "Paste as Text");
if (typeof pasteAsTextItem?.click !== "function") {
throw new Error("Expected Paste as Text menu item to have a click handler.");
}

pasteAsTextItem.click(
{} as Electron.MenuItem,
{} as Electron.BrowserWindow,
{
triggeredByAccelerator: true,
} as unknown as KeyboardEvent,
);
assert.isFalse(yield* Deferred.isDone(selectedAction));
}),
);

// Zoom must route through DesktopWindow.zoomMain instead of the Electron
// zoom roles: the roles zoom whichever webContents has focus, which breaks
// app zoom while an embedded preview WebContentsView holds focus.
Expand Down
12 changes: 11 additions & 1 deletion apps/desktop/src/window/DesktopApplicationMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,17 @@ export const make = Effect.gen(function* () {
const settingsClick = () => {
runMenuEffect("open-settings", dispatchMenuAction("open-settings"));
};
const pasteAsTextClick = () => {
// Chromium already pastes as plain text for this chord, so the accelerator
// needs nothing from the menu: the composer and the terminal each arm
// themselves from the same keydown. Routing it through the renderer anyway
// lands a second, injected paste and doubles the text. Only a menu click,
// which produces no keystroke for them to see, needs that round trip.
const pasteAsTextClick = (
_item: Electron.MenuItem,
_window: Electron.BaseWindow | undefined,
event: Electron.KeyboardEvent,
) => {
if (event.triggeredByAccelerator === true) return;
runMenuEffect("paste-as-text", dispatchMenuAction("paste-as-text"));
};
const zoomClick = (direction: DesktopWindow.MainWindowZoomDirection) => () => {
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/window/DesktopWindow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function makeFakeBrowserWindow() {
isDestroyed: vi.fn(() => false),
getURL: vi.fn(() => "t3code-dev://app/"),
getZoomLevel: vi.fn(() => zoomLevel),
getZoomFactor: vi.fn(() => 1.2 ** zoomLevel),
setZoomLevel: vi.fn((level: number) => {
zoomLevel = level;
}),
Expand Down Expand Up @@ -118,6 +119,7 @@ function makeFakeBrowserWindow() {
setOpacity: vi.fn(),
setTitle: vi.fn(),
setTitleBarOverlay: vi.fn(),
setWindowButtonPosition: vi.fn(),
show: vi.fn(),
webContents,
};
Expand All @@ -136,6 +138,7 @@ function makeFakeBrowserWindow() {
reload: webContents.reload,
send: webContents.send,
setZoomLevel: webContents.setZoomLevel,
setWindowButtonPosition: window.setWindowButtonPosition,
setBackgroundThrottling: webContents.setBackgroundThrottling,
setAutoHideCursor: window.setAutoHideCursor,
setFullScreen: window.setFullScreen,
Expand Down Expand Up @@ -742,6 +745,39 @@ describe("DesktopWindow", () => {
}),
);

it.effect("keeps macOS window buttons centered when zooming and leaving fullscreen", () =>
Effect.gen(function* () {
const fakeWindow = makeFakeBrowserWindow();
const createCount = yield* Ref.make(0);
const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow });

yield* Effect.gen(function* () {
const desktopWindow = yield* DesktopWindow.DesktopWindow;
yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773"));

for (const direction of ["in", "in", "out", "reset", "out"] as const) {
yield* desktopWindow.zoomMain(direction);
const position = fakeWindow.setWindowButtonPosition.mock.lastCall?.[0];
assert.isDefined(position);
// The 14-point native buttons should share the zoomed 52px header's center.
const headerCenter = 26 * fakeWindow.window.webContents.getZoomFactor();
assert.isAtMost(Math.abs(position.y + 7 - headerCenter), 0.5);
assert.equal(position.x, 16);
}

fakeWindow.isFullScreen.mockReturnValue(true);
fakeWindow.setWindowButtonPosition.mockClear();
yield* desktopWindow.zoomMain("reset");
assert.equal(fakeWindow.setWindowButtonPosition.mock.calls.length, 0);

fakeWindow.isFullScreen.mockReturnValue(false);
fakeWindow.windowListeners.get("leave-full-screen")?.();
assert.deepEqual(fakeWindow.setWindowButtonPosition.mock.lastCall, [{ x: 16, y: 19 }]);
}).pipe(Effect.provide(layer));
}),
);

it.effect("uses the persisted main window bounds when opening the window", () =>
Effect.gen(function* () {
const fakeWindow = makeFakeBrowserWindow();
Expand Down
Loading
Loading