Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
803ed02
[codex] Expand real-route app store screenshot harness (#4014)
juliusmarminge Jul 17, 2026
ba68a36
fix(server): use CLAUDE_CONFIG_DIR instead of HOME for Claude instanc…
dmstoykov Jul 17, 2026
ed54175
feat: show nightly update changelog tooltip (#3832)
HugoVizcainoSantana Jul 17, 2026
b16d8dc
fix(git): treat selected commit paths literally (#3998)
EricTsai83 Jul 17, 2026
f89e6cf
fix(server): stabilize non-repository Git diagnostics (#4077)
EricTsai83 Jul 17, 2026
1737d92
Refresh app icons across release variants (#4080)
juliusmarminge Jul 17, 2026
8ec8ecc
Update marketing GitHub star count (#4088)
AmoonPod Jul 17, 2026
be9d8b9
fix(marketing): correct Cursor icon color (#4090)
AmoonPod Jul 17, 2026
4d96667
Normalize protocol-relative remote host input as https (#3971)
kridaydave Jul 17, 2026
762fc59
fix(cursor): default binary path to cursor-agent (avoid path conflict…
BunnyGamezsc Jul 17, 2026
9b3aa4f
Fix documented task-runner commands (bun run -> vp) (#3965)
kridaydave Jul 17, 2026
c6e3ba1
Allow preview panel to grow on wide displays (#4044)
olivoil Jul 17, 2026
a1ff27f
fix: prevent initial right-click from selecting a context menu item (…
Fazalkadivar21 Jul 17, 2026
3589f96
Fix duplicate keybinding rule when replacing with an existing rule (#…
kridaydave Jul 17, 2026
5b13d67
fix(server): image upload crashed dispatchCommand with a stack overfl…
t3dotgg Jul 17, 2026
4556068
Remove unused code parameter from describePreviewError (#3970)
kridaydave Jul 17, 2026
c6366df
[codex] prevent ACP assistant ID collisions after restarts (#3932)
maxwellyoung Jul 17, 2026
33b0316
fix(web): inset Windows desktop scrollbars from resize edge (#4097)
nateEc Jul 17, 2026
ac9f8e7
[codex] fix mobile composer Enter behavior (#3930)
maxwellyoung Jul 17, 2026
0c17b3d
feat(server): include runtime model and effort in Codex developer ins…
t3dotgg Jul 17, 2026
9b622c3
fix(ux): spamming cmd + , no longer stack opening settings (#2757)
jamesx0416 Jul 17, 2026
92a1db0
fix(terminal): strip AppImage runtime env from spawned terminals (#3108)
leorivastech Jul 17, 2026
76921fe
fix(server): thread cwd through Claude capability probe (#2048) (#2124)
mvanhorn Jul 17, 2026
308d74f
[codex] fix: guard invalid web timestamps (#3515)
StiensWout Jul 17, 2026
ba7dee3
[codex] fix: tolerate invalid latest user message timestamps (#3521)
StiensWout Jul 17, 2026
b540af8
[codex] Fix provider update checks restore defaults (#3531)
StiensWout Jul 17, 2026
814e026
fix(server): skip undecodable provider runtime rows when listing sess…
t3dotgg Jul 17, 2026
2853fd1
Share MCP OAuth locks across Codex shadow homes (#4104)
juliusmarminge Jul 17, 2026
538498d
Preserve T3 Code identity in macOS development launcher (#4102)
juliusmarminge Jul 17, 2026
c1f5687
fix(web): increase contrast of question option descriptions (#3867)
xxashxx-svg Jul 17, 2026
d4fd500
fix(sync): reconcile fork divergences after upstream cherry-picks
wizzoapp[bot] Jul 18, 2026
5312c4b
style: format GitVcsDriverCore.test.ts (vp check --fix)
Jul 18, 2026
3bb814b
fix(sync): coerce optional itemId to string in CursorAdapter.test ass…
Jul 18, 2026
bd159fa
style: format CursorAdapter.test.ts
Jul 18, 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
134 changes: 134 additions & 0 deletions .github/workflows/mobile-showcase-screenshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Mobile Showcase Screenshots

on:
workflow_dispatch:
inputs:
platform:
description: Device platforms to capture
required: true
default: all
type: choice
options:
- all
- ios
- android
appearance:
description: System appearances to capture
required: true
default: both
type: choice
options:
- both
- dark
- light

permissions:
contents: read

env:
NODE_OPTIONS: --max-old-space-size=8192

jobs:
ios:
name: iPhone 6.9, iPhone 6.5, and iPad 13
if: inputs.platform == 'all' || inputs.platform == 'ios'
runs-on: blacksmith-12vcpu-macos-26
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

- name: Expose pnpm
run: |
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version

- name: Capture iOS showcase
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}"

- name: Validate App Store Connect assets
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --validate-only

- name: Upload iOS screenshots
if: always()
uses: actions/upload-artifact@v7
with:
name: app-store-connect-screenshots
path: artifacts/app-store/screenshots/apple/
if-no-files-found: warn
retention-days: 14

android:
name: Android phone, 7-inch tablet, and 10-inch tablet
if: inputs.platform == 'all' || inputs.platform == 'android'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 60
env:
T3_SHOWCASE_ANDROID_ABI: x86_64
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

- name: Expose pnpm
run: |
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version

- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Setup Gradle cache
uses: gradle/actions/setup-gradle@v5

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Capture Android showcase
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 36
target: google_apis
arch: x86_64
profile: pixel_7_pro
avd-name: Pixel_10_Pro
cores: 8
ram-size: 4096M
disable-animations: false
script: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}"

- name: Validate Google Play assets
run: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}" --validate-only

- name: Upload Android screenshots
if: always()
uses: actions/upload-artifact@v7
with:
name: google-play-screenshots
path: artifacts/app-store/screenshots/google-play/
if-no-files-found: warn
retention-days: 14
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ squashfs-root/
.gstack/
dist-electron/
.electron-runtime/
.showcase/
apps/mobile/.showcase/
artifacts/app-store/screenshots/
node_modules/
.alchemy/
*.log
Expand Down
43 changes: 34 additions & 9 deletions apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const APP_BUNDLE_ID = isDevelopment
? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
: "com.t3tools.t3code";
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
const LAUNCHER_VERSION = 12;
const LAUNCHER_VERSION = 14;
const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns");
const developmentMacIconPngPath = NodePath.join(
repoRoot,
Expand Down Expand Up @@ -220,11 +220,12 @@ function ensureDevelopmentIconIcns(runtimeDir) {
}
}

function patchMainBundleInfoPlist(appBundlePath, iconPath) {
function patchMainBundleInfoPlist(appBundlePath, iconPath, executableName) {
const infoPlistPath = NodePath.join(appBundlePath, "Contents", "Info.plist");
setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME);
setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME);
setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID);
setPlistString(infoPlistPath, "CFBundleExecutable", executableName);
setPlistString(infoPlistPath, "CFBundleIconFile", "icon.icns");
setPlistJson(infoPlistPath, "CFBundleURLTypes", [
{
Expand Down Expand Up @@ -277,11 +278,25 @@ function readJson(path) {
}
}

export function resolveMacLauncherPaths(appBundlePath, displayName = APP_DISPLAY_NAME) {
const executableDir = NodePath.join(appBundlePath, "Contents", "MacOS");
const launcherExecutableName = `${displayName} Launcher`;
return {
launcherExecutableName,
launcherBinaryPath: NodePath.join(executableDir, launcherExecutableName),
runtimeElectronBinaryPath: NodePath.join(executableDir, "Electron"),
};
}

function buildMacLauncher(electronBinaryPath) {
const sourceAppBundlePath = NodePath.resolve(NodePath.dirname(electronBinaryPath), "../..");
const runtimeDir = NodePath.join(desktopDir, ".electron-runtime");
const targetAppBundlePath = NodePath.join(runtimeDir, `${APP_DISPLAY_NAME}.app`);
const targetBinaryPath = NodePath.join(targetAppBundlePath, "Contents", "MacOS", "Electron");
const developmentPaths = resolveMacLauncherPaths(targetAppBundlePath);
const runtimeElectronBinaryPath = developmentPaths.runtimeElectronBinaryPath;
const launcherBinaryPath = isDevelopment
? developmentPaths.launcherBinaryPath
: runtimeElectronBinaryPath;
const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath;
const metadataPath = NodePath.join(runtimeDir, "metadata.json");

Expand All @@ -298,18 +313,19 @@ function buildMacLauncher(electronBinaryPath) {

const currentMetadata = readJson(metadataPath);
if (
NodeFS.existsSync(targetBinaryPath) &&
NodeFS.existsSync(launcherBinaryPath) &&
(!isDevelopment || NodeFS.existsSync(runtimeElectronBinaryPath)) &&
currentMetadata &&
JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata)
) {
if (isDevelopment) {
// The launcher also handles protocol activations outside the dev runner,
// so refresh its fallback environment on every launch. Never let a value
// captured by an older parent app override the live dev-runner environment.
writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);
writeDevelopmentLauncherScript(launcherBinaryPath, runtimeElectronBinaryPath);
}
registerMacLauncherBundle(targetAppBundlePath);
return targetBinaryPath;
return launcherBinaryPath;
}

NodeFS.rmSync(targetAppBundlePath, { recursive: true, force: true });
Expand All @@ -321,15 +337,24 @@ function buildMacLauncher(electronBinaryPath) {
recursive: true,
verbatimSymlinks: true,
});
patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
patchMainBundleInfoPlist(
targetAppBundlePath,
iconPath,
isDevelopment ? developmentPaths.launcherExecutableName : "Electron",
);
patchHelperBundleInfoPlists(targetAppBundlePath);
if (isDevelopment) {
writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);
// Keep Electron's native executable inside the branded bundle. Launching the
// node_modules copy makes macOS associate the process (and Dock label) with
// Electron.app even though this bundle's Info.plist has the T3 Code name.
// Its conventional executable name also keeps Electron's default-app runtime
// in development mode instead of making app.isPackaged report true.
writeDevelopmentLauncherScript(launcherBinaryPath, runtimeElectronBinaryPath);
}
NodeFS.writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);
registerMacLauncherBundle(targetAppBundlePath);

return targetBinaryPath;
return launcherBinaryPath;
}

function isLinuxSetuidSandboxConfigured(electronBinaryPath) {
Expand Down
35 changes: 34 additions & 1 deletion apps/desktop/scripts/electron-launcher.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { assert, describe, it } from "vite-plus/test";

import { makeDevelopmentLauncherScript, resolveElectronBinaryPath } from "./electron-launcher.mjs";
import {
makeDevelopmentLauncherScript,
resolveElectronBinaryPath,
resolveMacLauncherPaths,
} from "./electron-launcher.mjs";

describe("electron development launcher", () => {
it("uses captured values only as fallbacks for a live runner environment", () => {
Expand Down Expand Up @@ -45,4 +49,33 @@ describe("electron development launcher", () => {
);
assert.deepEqual(calls, ["ensure", "require:electron"]);
});

it("keeps the native Electron executable name inside the branded macOS bundle", () => {
const paths = resolveMacLauncherPaths(
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app",
"T3 Code (Dev)",
);

assert.equal(paths.launcherExecutableName, "T3 Code (Dev) Launcher");
assert.equal(
paths.launcherBinaryPath,
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/T3 Code (Dev) Launcher",
);
assert.equal(
paths.runtimeElectronBinaryPath,
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron",
);

const script = makeDevelopmentLauncherScript({
electronBinaryPath: paths.runtimeElectronBinaryPath,
mainEntryPath: "/repo/apps/desktop/dist-electron/main.cjs",
desktopRoot: "/repo/apps/desktop",
environment: {},
});
assert.include(
script,
"exec '/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron'",
);
assert.notInclude(script, "node_modules/electron");
});
});
14 changes: 14 additions & 0 deletions apps/desktop/src/electron/ElectronUpdater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { autoUpdaterMock } = vi.hoisted(() => ({
autoInstallOnAppQuit: true,
channel: "latest",
disableDifferentialDownload: false,
fullChangelog: false,
checkForUpdates: vi.fn(() => Promise.resolve(null)),
downloadUpdate: vi.fn(() => Promise.resolve([])),
on: vi.fn(),
Expand All @@ -33,6 +34,7 @@ describe("ElectronUpdater", () => {
autoUpdaterMock.autoInstallOnAppQuit = true;
autoUpdaterMock.channel = "latest";
autoUpdaterMock.disableDifferentialDownload = false;
autoUpdaterMock.fullChangelog = false;
autoUpdaterMock.checkForUpdates.mockClear();
autoUpdaterMock.checkForUpdates.mockImplementation(() => Promise.resolve(null));
autoUpdaterMock.downloadUpdate.mockClear();
Expand Down Expand Up @@ -108,6 +110,18 @@ describe("ElectronUpdater", () => {
}).pipe(Effect.provide(ElectronUpdater.layer)),
);

it.effect("sets full changelog mode", () =>
Effect.gen(function* () {
const updater = yield* ElectronUpdater.ElectronUpdater;

yield* updater.setFullChangelog(true);
assert.equal(autoUpdaterMock.fullChangelog, true);

yield* updater.setFullChangelog(false);
assert.equal(autoUpdaterMock.fullChangelog, false);
}).pipe(Effect.provide(ElectronUpdater.layer)),
);

it.effect("preserves quit-and-install flags and the execution-time channel", () =>
Effect.gen(function* () {
const cause = new Error("quit and install failed");
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/src/electron/ElectronUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class ElectronUpdater extends Context.Service<
readonly setAllowPrerelease: (value: boolean) => Effect.Effect<void>;
readonly allowDowngrade: Effect.Effect<boolean>;
readonly setAllowDowngrade: (value: boolean) => Effect.Effect<void>;
readonly setFullChangelog: (value: boolean) => Effect.Effect<void>;
readonly setDisableDifferentialDownload: (value: boolean) => Effect.Effect<void>;
readonly verifyAvailable: Effect.Effect<void>;
readonly checkForUpdates: Effect.Effect<void, ElectronUpdaterCheckForUpdatesError>;
Expand Down Expand Up @@ -163,6 +164,14 @@ export const make = ElectronUpdater.of({
}),
),
),
setFullChangelog: (value) =>
loadAutoUpdater.pipe(
Effect.flatMap((autoUpdater) =>
Effect.sync(() => {
autoUpdater.fullChangelog = value;
}),
),
),
setDisableDifferentialDownload: (value) =>
loadAutoUpdater.pipe(
Effect.flatMap((autoUpdater) =>
Expand Down
Loading
Loading