Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f61f979
fix(web,mobile): drop the baked-in tile from the Antigravity icon (#1…
flamboh Sep 24, 2026
53456bc
fix(marketing): use the official OpenCode and Antigravity logos (#13365)
flamboh Sep 24, 2026
e759847
fix(acp): keep one answer when a running tool reports progress (#13386)
juliusmarminge Sep 24, 2026
66129c6
feat(web): run shell commands from chat in the thread terminal (#13060)
Bil0000 Sep 24, 2026
f3cb2a1
fix(antigravity): keep Windows runtime unpacking under MAX_PATH (#13389)
juliusmarminge Sep 24, 2026
567783e
fix(codex): the protocol generator runs again on Effect rc.115 (#13480)
juliusmarminge Sep 24, 2026
d5d4874
feat(codex): require Codex 0.156 and regenerate its protocol (#13481)
juliusmarminge Sep 24, 2026
0109670
feat(threads): add per-thread auto-settle switch (#11846)
t3dotgg Sep 24, 2026
8251c8d
fix(web): working and monitoring threads fade in the sidebar again (#…
t3dotgg Sep 24, 2026
a36af06
fix(server): streamed section titles wait for the text under them (#1…
t3dotgg Sep 24, 2026
720490a
fix(web): normalize disabled control opacity (#11441)
t3-code[bot] Sep 24, 2026
9a91177
fix(web): sidebar Back always returns to the main app (#13516)
t3dotgg Sep 24, 2026
d4a3345
fix(desktop): desktop updates reconnect in seconds, not minutes (#12006)
t3dotgg Sep 24, 2026
8d7b5e9
fix(connect): remove tunnels after hosts go offline (#9386)
t3dotgg Sep 25, 2026
9c524d5
fix(mobile): capture a lit 6.9-inch lock screen in the agent-activity…
juliusmarminge Sep 25, 2026
72447f2
feat(grok): offer one-click updates through `grok update` (#13523)
juliusmarminge Sep 25, 2026
59abcd6
fix(mobile): make Android subscription usage widgets scrollable (#13474)
tris203 Sep 25, 2026
fd46510
fix(web): keep sidebar terminal pulses in sync (#12962)
t3dotgg Sep 25, 2026
46f3c2c
feat(web): add iPhone Duo 3D controls (#12813)
juliusmarminge Sep 25, 2026
d23eab1
fix(relay): export tunnel cleanup counters to Axiom (#13528)
juliusmarminge Sep 25, 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 .github/workflows/deploy-relay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }}
RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }}
RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }}
RELAY_TUNNEL_CLEANUP_MODE: ${{ vars.RELAY_TUNNEL_CLEANUP_MODE }}
CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }}
CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }}
APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }}
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/electron/ElectronMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
destructive: sourceItem.destructive === true,
disabled: sourceItem.disabled === true,
...(sourceItem.separatorBefore === true ? { separatorBefore: true } : {}),
...(typeof sourceItem.checked === "boolean" ? { checked: sourceItem.checked } : {}),
};

if (sourceItem.children) {
Expand Down Expand Up @@ -168,6 +169,7 @@ export const make = Effect.gen(function* () {
const itemOption: Electron.MenuItemConstructorOptions = {
label: item.label,
enabled: !item.disabled,
...(typeof item.checked === "boolean" ? { type: "checkbox", checked: item.checked } : {}),
};
if (item.children && item.children.length > 0) {
itemOption.submenu = buildTemplate(item.children, complete);
Expand Down
53 changes: 53 additions & 0 deletions apps/desktop/src/updates/DesktopUpdates.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert, describe, it } from "@effect/vitest";
import { DESKTOP_UPDATE_RESTART_MARKER_FILE } from "@t3tools/contracts";
import * as Cause from "effect/Cause";
import * as Deferred from "effect/Deferred";
import * as Duration from "effect/Duration";
Expand All @@ -14,6 +15,7 @@ import * as TestClock from "effect/testing/TestClock";

import * as ElectronUpdater from "../electron/ElectronUpdater.ts";
import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
import * as DesktopState from "../app/DesktopState.ts";
import * as DesktopUpdates from "./DesktopUpdates.ts";
import { flushCallbacks, makeHarness } from "./updatesTestHarness.ts";
Expand Down Expand Up @@ -559,6 +561,55 @@ describe("DesktopUpdates", () => {
).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
});

it.effect("marks the backend stop for an install as an update restart", () => {
let markersAtStop: ReadonlyArray<string> = [];
const harness = makeHarness({
stopBackend: Effect.sync(() => {
markersAtStop = [...harness.updateRestartMarkers];
}),
});

return Effect.scoped(
Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const updates = yield* DesktopUpdates.DesktopUpdates;
yield* updates.configure;
harness.emit("update-downloaded", { version: "1.2.4" });
yield* flushCallbacks;

assert.isTrue((yield* updates.install).accepted);
assert.deepEqual(markersAtStop, [
environment.path.join(environment.baseDir, "runtime", DESKTOP_UPDATE_RESTART_MARKER_FILE),
]);
}),
).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
});

it.effect("drops the update restart marker when an install is interrupted", () =>
Effect.gen(function* () {
const stopping = yield* Deferred.make<void>();
const harness = makeHarness({
stopBackend: Deferred.succeed(stopping, undefined).pipe(Effect.andThen(Effect.never)),
});

yield* Effect.scoped(
Effect.gen(function* () {
const updates = yield* DesktopUpdates.DesktopUpdates;
yield* updates.configure;
harness.emit("update-downloaded", { version: "1.2.4" });
yield* flushCallbacks;

const installFiber = yield* updates.install.pipe(Effect.forkScoped);
yield* Deferred.await(stopping);
assert.equal(harness.updateRestartMarkers.size, 1);

yield* Fiber.interrupt(installFiber);
assert.equal(harness.updateRestartMarkers.size, 0);
}),
).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
}),
);

it.effect("keeps windows and restarts backends when quitAndInstall fails", () => {
const harness = makeHarness({
quitAndInstall: Effect.fail(
Expand All @@ -583,6 +634,8 @@ describe("DesktopUpdates", () => {
assert.isTrue(result.accepted);
assert.isFalse(yield* Ref.get(desktopState.quitting));
assert.deepEqual(harness.installSteps, ["quitAndInstall", "startBackend"]);
// The restarted old backend must release its tunnel on a later quit.
assert.equal(harness.updateRestartMarkers.size, 0);
}),
).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer)));
});
Expand Down
32 changes: 31 additions & 1 deletion apps/desktop/src/updates/DesktopUpdates.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
DESKTOP_UPDATE_RESTART_MARKER_FILE,
DesktopUpdateChannelSchema,
type DesktopRuntimeInfo,
type DesktopUpdateActionResult,
Expand Down Expand Up @@ -501,8 +502,35 @@ export const make = Effect.gen(function* () {
);
}).pipe(Effect.withSpan("desktop.updates.downloadAvailableUpdate"));

// Tells the primary backend that the coming stop is an update restart, so it
// keeps its managed tunnel for the backend the updated app starts. Best
// effort: without the marker the backend only re-provisions its tunnel.
const updateRestartMarkerDir = environment.path.join(environment.baseDir, "runtime");
const updateRestartMarkerPath = environment.path.join(
updateRestartMarkerDir,
DESKTOP_UPDATE_RESTART_MARKER_FILE,
);
const writeUpdateRestartMarker = fileSystem
.makeDirectory(updateRestartMarkerDir, { recursive: true })
.pipe(
Effect.andThen(fileSystem.writeFileString(updateRestartMarkerPath, "")),
Effect.catch((error) =>
logUpdaterWarning("Could not write the update restart marker.", { errorTag: error._tag }),
),
);

// A failed or interrupted install brings no updated backend, so a later
// quit must release the tunnel.
const removeUpdateRestartMarker = fileSystem
.remove(updateRestartMarkerPath, { force: true })
.pipe(Effect.ignore);

const resetInstallAction = Effect.all(
[finishUpdateAction("install"), Ref.set(desktopState.quitting, false)],
[
finishUpdateAction("install"),
Ref.set(desktopState.quitting, false),
removeUpdateRestartMarker,
],
{ discard: true },
);

Expand All @@ -517,6 +545,7 @@ export const make = Effect.gen(function* () {
if (!ownsRecovery) return;

yield* Ref.set(desktopState.quitting, false);
yield* removeUpdateRestartMarker;
yield* Effect.gen(function* () {
const instances = yield* pool.list;
const restartExit = yield* Effect.forEach(instances, (instance) => instance.start, {
Expand Down Expand Up @@ -586,6 +615,7 @@ export const make = Effect.gen(function* () {
yield* Ref.set(desktopState.quitting, true);

return yield* Effect.gen(function* () {
yield* writeUpdateRestartMarker;
// Stop every backend in the pool, not just the primary. With
// parallel WSL + Windows backends, leaving the WSL instance up
// means quitAndInstall's app.quit() exits before the pool's
Expand Down
18 changes: 18 additions & 0 deletions apps/desktop/src/updates/updatesTestHarness.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import type { DesktopUpdateState } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";

Expand Down Expand Up @@ -203,7 +204,23 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
} satisfies DesktopAppSettings.DesktopAppSettings["Service"])
: DesktopAppSettings.layer;

// Tracks the restart markers installs leave, so installs stay free of real
// disk I/O that would outrun the tests' settle loops.
const updateRestartMarkers = new Set<string>();
const fileSystemLayer = FileSystem.layerNoop({
makeDirectory: () => Effect.void,
writeFileString: (path) =>
Effect.sync(() => {
updateRestartMarkers.add(path);
}),
remove: (path) =>
Effect.sync(() => {
updateRestartMarkers.delete(path);
}),
});

const layer = DesktopUpdates.layer.pipe(
Layer.provide(fileSystemLayer),
Layer.provideMerge(updaterLayer),
Layer.provideMerge(windowLayer),
Layer.provideMerge(backendLayer),
Expand All @@ -226,6 +243,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
checkCount: () => checkCount,
quitAndInstalls: () => quitAndInstallCount,
installSteps,
updateRestartMarkers,
downloadCount: () => downloadCount,
feedUrls: () => feedUrls,
fullChangelog: () => fullChangelog,
Expand Down
Binary file removed apps/marketing/public/harnesses/antigravity.png
Binary file not shown.
1 change: 1 addition & 0 deletions apps/marketing/public/harnesses/antigravity.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/marketing/public/harnesses/opencode-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 3 additions & 8 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const mobileEndorsementRows = [
<div class="hero-float" aria-hidden="true">
{[
{ key: "opencode", icon: "/harnesses/opencode-dark.svg", cls: "hf-opencode" },
{ key: "antigravity", icon: "/harnesses/antigravity.png", cls: "hf-antigravity" },
{ key: "antigravity", icon: "/harnesses/antigravity.svg", cls: "hf-antigravity" },
{ key: "claude", icon: "/harnesses/claude-ai-icon.svg", cls: "hf-claude" },
{ key: "codex", icon: "/harnesses/openai_dark.svg", cls: "hf-codex" },
{ key: "grok", icon: "/harnesses/grok-dark.svg", cls: "hf-grok" },
Expand Down Expand Up @@ -247,7 +247,7 @@ const mobileEndorsementRows = [
</div>
</div>
<div class="harness">
<div class="harness-mark"><img src="/harnesses/antigravity.png" alt="" /></div>
<div class="harness-mark"><img src="/harnesses/antigravity.svg" alt="" /></div>
<div class="harness-meta">
<div class="harness-name">Antigravity</div>
<div class="harness-tag">Google sign-in</div>
Expand Down Expand Up @@ -709,11 +709,6 @@ const mobileEndorsementRows = [
object-fit: contain;
}

/* The Antigravity icon ships with its own rounded dark tile, so it fills the
card edge to edge instead of sitting inside it. */
.hf-antigravity .hero-float-card { background: #0d0d10; border-color: rgba(255, 255, 255, 0.1); }
.hf-antigravity .hero-float-card img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }

@keyframes mark-in {
from { opacity: 0; transform: translate(var(--fx), var(--fy)) rotate(calc(var(--rot) + 24deg)) scale(0.6); }
to { opacity: 1; transform: translate(0, 0) rotate(var(--rot)) scale(1); }
Expand Down Expand Up @@ -829,7 +824,7 @@ const mobileEndorsementRows = [
flex-shrink: 0; width: 28px; height: 28px;
display: grid; place-items: center;
}
.harness-mark img { width: 22px; height: 22px; object-fit: contain; border-radius: 5px; }
.harness-mark img { width: 22px; height: 22px; object-fit: contain; }

.harness-meta { flex: 1; min-width: 0; }
.harness-name {
Expand Down
Binary file modified apps/mobile/assets/antigravity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver android:name="expo.modules.t3subscriptionwidget.SubscriptionUsageWidget" android:exported="false" android:label="@string/t3_subscription_widget_name">
<receiver android:name="expo.modules.t3subscriptionwidget.SubscriptionUsageWidget" android:enabled="@bool/t3_subscription_widget_enabled" android:exported="false" android:label="@string/t3_subscription_widget_name">
<intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/t3_subscription_widget_info" />
</receiver>
Expand Down
Loading
Loading