Skip to content

Commit 8c13449

Browse files
wizzoapp[bot]D3OXYmaxwellyoungjuliusmarmingecodex
authored
chore: sync upstream pingdotgg/t3code (20260719) (#209)
* Fix dropped events during initial thread snapshot (pingdotgg#4079) * [codex] fix mobile composer Enter behavior (pingdotgg#3930) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * feat: draft hero landing on the index route (pingdotgg#4055) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> * feat: file explorer mention actions and zoom-aware context menus (pingdotgg#4054) Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(web): avoid duplicate mention text on paste Co-authored-by: codex <codex@users.noreply.github.com> * fix(mobile): restore iOS home screen branding (pingdotgg#4025) Co-authored-by: Julius Marminge <julius0216@outlook.com> * perf(client): defer active thread cache writes (pingdotgg#4006) * Default diffs to working changes (pingdotgg#3974) Co-authored-by: Julius Marminge <julius0216@outlook.com> * Add Grok to marketing site provider list (pingdotgg#3484) * Fix reopening existing Diff tab (pingdotgg#3973) Co-authored-by: Julius Marminge <julius0216@outlook.com> * Fix sending messages during active turns (pingdotgg#3919) Co-authored-by: Julius Marminge <julius0216@outlook.com> * [codex] Route OpenCode missing-session errors through Effect (pingdotgg#3608) Co-authored-by: Codex <codex@openai.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * [fix/feat:ui] Show default option badge (pingdotgg#3232) Co-authored-by: Julius Marminge <julius0216@outlook.com> * [fix/feat:ui] Preserve open-in editor brand colors (pingdotgg#3225) Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(web): handle macOS Home and End in composer (pingdotgg#2508) Co-authored-by: Julius Marminge <julius0216@outlook.com> * Allow failed remote environments to be removed (pingdotgg#4084) Co-authored-by: Julius Marminge <julius0216@outlook.com> * [codex] canonicalize client timestamps (pingdotgg#4112) * [fix/feat:ui] Make selected menu checks blue (pingdotgg#3234) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(desktop): Validate WSL node version against engine range after probe success (pingdotgg#3621) Co-authored-by: Julius Marminge <julius0216@outlook.com> * Refresh splash screen and favicon branding (pingdotgg#4120) * Add terminal selection copy action (pingdotgg#2904) * fix(sync): reconcile upstream changes with fork-local behaviour Two merge-introduced incompatibilities from the 20260719 upstream sync: - Normalizer: upstream's client-timestamp canonicalization (da3c203) rewrote bootstrap.createThread.createdAt, which BootstrapTurnStartDispatcher uses to fingerprint a replayed bootstrap turn against the thread it already created. Every retry would miss that check and create a duplicate thread. Keep the client's bootstrap timestamp as the stable identity; the turn-level createdAt is still canonicalized. - ThreadTerminalDrawer: upstream's terminal selection copy action (1735e27) passed a Terminal to writeSystemMessage, which this fork narrowed to accept a TerminalWriteQueue. Use terminalWriterRef, matching the other call sites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: Ishan <ishansachu1@gmail.com> Co-authored-by: Maxwell Young <maxtheyoung@gmail.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: Alex <me@pixp.cc> Co-authored-by: Chris Michael Guzman <67719167+Chrrxs@users.noreply.github.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Aditya Mer <101453576+Aditya190803@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: ss <69873514+sandersonstabo@users.noreply.github.com> Co-authored-by: Guilherme Vieira <46866023+GuilhermeVieiraDev@users.noreply.github.com> Co-authored-by: Noah Zepner <noah@zepner.dev> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Taras <Taras.Fomin@gmail.com> Co-authored-by: wizzoapp[bot] <254688279+wizzoapp[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1726590 commit 8c13449

59 files changed

Lines changed: 2461 additions & 410 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/src/electron/ElectronMenu.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ const TestLayer = ElectronMenu.layer.pipe(
3131
Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
3232
);
3333

34+
const makeWindow = (zoomFactor = 1): Electron.BrowserWindow =>
35+
({
36+
id: 7,
37+
webContents: { getZoomFactor: () => zoomFactor },
38+
}) as unknown as Electron.BrowserWindow;
39+
3440
describe("ElectronMenu", () => {
3541
beforeEach(() => {
3642
buildFromTemplateMock.mockReset();
@@ -70,7 +76,7 @@ describe("ElectronMenu", () => {
7076

7177
const electronMenu = yield* ElectronMenu.ElectronMenu;
7278
const selectedItemId = yield* electronMenu.showContextMenu({
73-
window: {} as Electron.BrowserWindow,
79+
window: makeWindow(),
7480
items: [{ id: "copy", label: "Copy" }],
7581
position: Option.none(),
7682
});
@@ -81,20 +87,24 @@ describe("ElectronMenu", () => {
8187

8288
it.effect("resolves with none when the menu closes without a click", () =>
8389
Effect.gen(function* () {
90+
let popupOptions: Electron.PopupOptions | undefined;
8491
buildFromTemplateMock.mockImplementation(() => ({
8592
popup: (options: Electron.PopupOptions) => {
93+
popupOptions = options;
8694
options.callback?.();
8795
},
8896
}));
8997

9098
const electronMenu = yield* ElectronMenu.ElectronMenu;
9199
const selectedItemId = yield* electronMenu.showContextMenu({
92-
window: {} as Electron.BrowserWindow,
100+
window: makeWindow(2),
93101
items: [{ id: "copy", label: "Copy" }],
94102
position: Option.some({ x: 10.8, y: 20.2 }),
95103
});
96104

97105
assert.isTrue(Option.isNone(selectedItemId));
106+
assert.equal(popupOptions?.x, 21);
107+
assert.equal(popupOptions?.y, 40);
98108
assert.deepEqual(buildFromTemplateMock.mock.calls[0]?.[0][0], {
99109
label: "Copy",
100110
enabled: true,

apps/desktop/src/electron/ElectronMenu.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,20 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
9494
return normalizedItems;
9595
}
9696

97+
// Renderer positions arrive in CSS pixels; popup() expects window points, so
98+
// page zoom must be factored in or menus drift proportionally to their
99+
// distance from the window origin.
97100
const normalizePosition = (
98101
position: Option.Option<ElectronMenuPosition>,
102+
zoomFactor: number,
99103
): Option.Option<ElectronMenuPosition> =>
100104
Option.filter(
101105
position,
102-
({ x, y }) => Number.isFinite(x) && Number.isFinite(y) && x >= 0 && y >= 0,
103-
).pipe(Option.map(({ x, y }) => ({ x: Math.floor(x), y: Math.floor(y) })));
106+
({ x, y }) =>
107+
Number.isFinite(x) && Number.isFinite(y) && x >= 0 && y >= 0 && Number.isFinite(zoomFactor),
108+
).pipe(
109+
Option.map(({ x, y }) => ({ x: Math.floor(x * zoomFactor), y: Math.floor(y * zoomFactor) })),
110+
);
104111

105112
export const make = Effect.gen(function* () {
106113
const platform = yield* HostProcessPlatform;
@@ -214,7 +221,10 @@ export const make = Effect.gen(function* () {
214221

215222
try {
216223
const menu = Electron.Menu.buildFromTemplate(buildTemplate(normalizedItems, complete));
217-
const popupPosition = normalizePosition(input.position);
224+
const popupPosition = normalizePosition(
225+
input.position,
226+
input.window.webContents.getZoomFactor(),
227+
);
218228
const popupOptions = Option.match(popupPosition, {
219229
onNone: (): Electron.PopupOptions => ({
220230
window: input.window,

apps/desktop/src/wsl/DesktopWslEnvironment.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
formatNodePtyProbeFailureReason,
1717
formatWslShellTransportFailureReason,
1818
parseNodePath,
19+
parseNodeVersion,
1920
parseResolvedPath,
2021
parseToolchainReport,
2122
probeWslDistros,
@@ -173,6 +174,29 @@ describe("parseNodePath", () => {
173174
});
174175
});
175176

177+
describe("parseNodeVersion", () => {
178+
it("extracts the node version from a nodeVersion: line", () => {
179+
expect(parseNodeVersion("nodeVersion:24.10.0")).toBe("24.10.0");
180+
});
181+
182+
it("returns null when the version value is empty", () => {
183+
expect(parseNodeVersion("nodeVersion:")).toBeNull();
184+
});
185+
186+
it("returns null when there is no nodeVersion line at all", () => {
187+
expect(parseNodeVersion("nodePath:/usr/bin/node\nresolvedPath:/usr/bin")).toBeNull();
188+
});
189+
190+
it("ignores surrounding noise and trims whitespace", () => {
191+
const stdout = [
192+
"some preamble noise",
193+
" nodeVersion:22.16.0 ",
194+
"nodePath:/usr/bin/node",
195+
].join("\n");
196+
expect(parseNodeVersion(stdout)).toBe("22.16.0");
197+
});
198+
});
199+
176200
describe("parseResolvedPath", () => {
177201
it("preserves spaces and apostrophes in the resolved login-shell PATH", () => {
178202
const resolvedPath = "/home/test user/bin:/opt/test's tools/bin:/usr/bin:/bin";

apps/desktop/src/wsl/DesktopWslEnvironment.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ export interface EnsureWslNodePtyOptions {
3232
}
3333

3434
export type EnsureWslNodePtyResult =
35-
| { readonly ok: true; readonly nodePath: string; readonly resolvedPath: string }
35+
| {
36+
readonly ok: true;
37+
readonly nodePath: string;
38+
readonly resolvedPath: string;
39+
}
3640
| {
3741
readonly ok: false;
3842
readonly reason: string;
@@ -222,6 +226,7 @@ export const formatNodePtyProbeFailureReason = (exitCode: number): string | null
222226
const NODE_PTY_PROBE_SCRIPT = (
223227
linuxServerDir: string,
224228
) => `printf 'nodePath:%s\\n' "$(command -v node 2>/dev/null)"
229+
printf 'nodeVersion:%s\\n' "$(node -p 'process.versions.node' 2>/dev/null)"
225230
printf 'resolvedPath:%s\\n' "$PATH"
226231
cd ${shellQuote(linuxServerDir)} && node <<'NODE' >/dev/null 2>&1
227232
const fs = require("node:fs");
@@ -309,6 +314,16 @@ export const parseNodePath = (stdout: string): string | null => {
309314
return path ?? null;
310315
};
311316

317+
export const parseNodeVersion = (stdout: string): string | null => {
318+
const version = stdout
319+
.split("\n")
320+
.map((line) => line.trim())
321+
.filter((line) => line.startsWith("nodeVersion:"))
322+
.map((line) => line.slice("nodeVersion:".length).trim())
323+
.find((value) => value.length > 0);
324+
return version ?? null;
325+
};
326+
312327
// Captures the login-shell PATH after the shared resolver has loaded version
313328
// managers. Preserve the value byte-for-byte apart from a Windows-style CR so
314329
// paths containing spaces or apostrophes can be forwarded as one env argv.
@@ -395,7 +410,11 @@ const ensureNodePtyImpl = (
395410

396411
const transportFailureReason = formatWslShellTransportFailureReason(probe.transportFailure);
397412
if (transportFailureReason !== null) {
398-
return { ok: false, reason: transportFailureReason, fatal: false } as const;
413+
return {
414+
ok: false,
415+
reason: transportFailureReason,
416+
fatal: false,
417+
} as const;
399418
}
400419

401420
// No node at all, even after the shared resolver repaired PATH. Surface
@@ -434,12 +453,45 @@ const ensureNodePtyImpl = (
434453
} as const;
435454
}
436455

437-
if (probe.exitCode === 0) return { ok: true, nodePath, resolvedPath } as const;
456+
// Server dependencies (e.g. "effect") couldn't be resolved on the WSL
457+
// filesystem — a packaging regression, since the server bundle needs its
458+
// node_modules unpacked from the asar. Fatal so wsl-only mode falls back to
459+
// Windows and dual mode surfaces the reason inline, instead of the server
460+
// crash-looping on ERR_MODULE_NOT_FOUND once it actually launches.
461+
if (probe.exitCode === 3) {
462+
return {
463+
ok: false,
464+
reason:
465+
"WSL server dependencies could not be loaded (for example \"effect\"). The server's bundled node_modules is not readable by the WSL distro's Node — this is a packaging problem with this build. Please report it.",
466+
fatal: true,
467+
} as const;
468+
}
469+
470+
if (probe.exitCode === 0) {
471+
const rawVersion = parseNodeVersion(probe.stdout);
472+
if (
473+
rawVersion !== null &&
474+
options.nodeEngineRange &&
475+
!satisfiesSemverRange(rawVersion, options.nodeEngineRange.trim())
476+
) {
477+
const range = options.nodeEngineRange.trim();
478+
return {
479+
ok: false,
480+
reason: `WSL Node.js ${rawVersion} does not satisfy the server's required engine range (${range}). Install a compatible version, and restart the desktop app.`,
481+
fatal: true,
482+
} as const;
483+
}
484+
return { ok: true, nodePath, resolvedPath } as const;
485+
}
438486

439487
if (options.allowBuild !== true) {
440488
const packagedProbeFailure = formatNodePtyProbeFailureReason(probe.exitCode);
441489
if (packagedProbeFailure !== null) {
442-
return { ok: false, reason: packagedProbeFailure, fatal: true } as const;
490+
return {
491+
ok: false,
492+
reason: packagedProbeFailure,
493+
fatal: true,
494+
} as const;
443495
}
444496
}
445497

Lines changed: 4 additions & 0 deletions
Loading

apps/marketing/src/pages/index.astro

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const mobileEndorsementRows = [
3333
<span class="hero-float-mark hf-cursor" title="Cursor">
3434
<img src="/harnesses/cursor_light.svg" alt="" />
3535
</span>
36+
<span class="hero-float-mark hf-grok" title="Grok">
37+
<img src="/harnesses/grok-dark.svg" alt="" />
38+
</span>
3639
</div>
3740

3841
<div class="container hero-inner">
@@ -41,7 +44,7 @@ const mobileEndorsementRows = [
4144
</h1>
4245

4346
<p class="hero-sub">
44-
Orchestrate Claude Code, Codex, OpenCode and Cursor from one surface.
47+
Orchestrate Claude Code, Codex, OpenCode, Cursor, and Grok from one surface.
4548
Bring your own subscription. Fork the whole thing.
4649
</p>
4750

@@ -173,7 +176,7 @@ const mobileEndorsementRows = [
173176
<h2>Bring your own sub</h2>
174177
<p>
175178
T3 Code doesn't resell tokens. Plug in Claude Code, Codex, OpenCode,
176-
or Cursor with the credentials you already have — we orchestrate
179+
Cursor, or Grok with the credentials you already have — we orchestrate
177180
them, you keep your plan.
178181
</p>
179182
</div>
@@ -207,6 +210,13 @@ const mobileEndorsementRows = [
207210
<div class="harness-tag">cursor-agent</div>
208211
</div>
209212
</div>
213+
<div class="harness">
214+
<div class="harness-mark"><img src="/harnesses/grok-dark.svg" alt="" /></div>
215+
<div class="harness-meta">
216+
<div class="harness-name">Grok CLI</div>
217+
<div class="harness-tag">grok login</div>
218+
</div>
219+
</div>
210220
</div>
211221

212222
<div class="harness-footer">
@@ -615,8 +625,9 @@ const mobileEndorsementRows = [
615625

616626
.hero-float-mark.hf-claude { background: radial-gradient(circle at 30% 25%, rgba(217, 119, 87, 0.22), rgba(20, 20, 24, 0.92) 65%); top: 8%; left: 7%; animation-delay: 0s; transform: rotate(-8deg); }
617627
.hero-float-mark.hf-codex { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 6%; right: 7%; animation-delay: -2.5s; transform: rotate(6deg); }
618-
.hero-float-mark.hf-opencode { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.06), rgba(20, 20, 24, 0.92) 65%); top: 32%; left: 5%; animation-delay: -5s; transform: rotate(4deg); }
628+
.hero-float-mark.hf-opencode { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.06), rgba(20, 20, 24, 0.92) 65%); top: 30%; left: 5%; animation-delay: -5s; transform: rotate(4deg); }
619629
.hero-float-mark.hf-cursor { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 30%; right: 5%; animation-delay: -7s; transform: rotate(-5deg); }
630+
.hero-float-mark.hf-grok { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 52%; left: 6%; animation-delay: -1.2s; transform: rotate(3deg); }
620631

621632
@media (max-width: 1180px) {
622633
.hero-float-mark { width: 76px; height: 76px; border-radius: 20px; }
@@ -625,6 +636,7 @@ const mobileEndorsementRows = [
625636
.hero-float-mark.hf-codex { top: 2%; right: 3%; }
626637
.hero-float-mark.hf-opencode { top: 26%; left: 2%; }
627638
.hero-float-mark.hf-cursor { top: 24%; right: 2%; }
639+
.hero-float-mark.hf-grok { top: 48%; left: 2%; transform: rotate(3deg); }
628640
}
629641

630642
@media (max-width: 820px) {
@@ -659,17 +671,18 @@ const mobileEndorsementRows = [
659671
height: 44px;
660672
}
661673

662-
/* Two above the title, two flanking the call-to-action area. */
674+
/* Three stacked on the left, two on the right — keeps the center CTA clear. */
663675
.hero-float-mark.hf-claude {
664676
top: 44px;
665677
left: 10px;
666678
transform: rotate(-10deg);
667679
}
668680

669-
.hero-float-mark.hf-codex {
670-
top: 44px;
671-
right: 10px;
672-
transform: rotate(8deg);
681+
.hero-float-mark.hf-grok {
682+
top: 240px;
683+
left: 4px;
684+
right: auto;
685+
transform: rotate(-4deg);
673686
}
674687

675688
.hero-float-mark.hf-opencode {
@@ -679,6 +692,12 @@ const mobileEndorsementRows = [
679692
transform: rotate(-6deg);
680693
}
681694

695+
.hero-float-mark.hf-codex {
696+
top: 44px;
697+
right: 10px;
698+
transform: rotate(8deg);
699+
}
700+
682701
.hero-float-mark.hf-cursor {
683702
top: 474px;
684703
right: 4px;
@@ -688,6 +707,14 @@ const mobileEndorsementRows = [
688707
}
689708

690709
@media (max-width: 340px) {
710+
.hero-float-mark.hf-grok {
711+
top: 220px;
712+
left: 0;
713+
width: 52px;
714+
height: 52px;
715+
border-radius: 14px;
716+
}
717+
691718
.hero-float-mark.hf-opencode,
692719
.hero-float-mark.hf-cursor {
693720
top: 580px;
@@ -704,6 +731,7 @@ const mobileEndorsementRows = [
704731
right: 0;
705732
}
706733

734+
.hero-float-mark.hf-grok img,
707735
.hero-float-mark.hf-opencode img,
708736
.hero-float-mark.hf-cursor img {
709737
width: 30px;
@@ -750,7 +778,7 @@ const mobileEndorsementRows = [
750778

751779
.harness-grid {
752780
display: grid;
753-
grid-template-columns: repeat(4, 1fr);
781+
grid-template-columns: repeat(5, 1fr);
754782
gap: 0;
755783
border: 1px solid var(--border);
756784
border-radius: var(--radius);
@@ -1205,6 +1233,10 @@ const mobileEndorsementRows = [
12051233
.harness-grid { grid-template-columns: 1fr 1fr; }
12061234
.harness { border-right: 0; border-bottom: 1px solid var(--border); }
12071235
.harness:nth-child(odd) { border-right: 1px solid var(--border); }
1236+
.harness:nth-child(5):last-child {
1237+
grid-column: 1 / -1;
1238+
border-right: 0;
1239+
}
12081240
.git-inner { grid-template-columns: 1fr; gap: 40px; }
12091241
.open-grid { grid-template-columns: 1fr; }
12101242
}

apps/mobile/src/Stack.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { DynamicColorIOS, Platform, Pressable, ScrollView, StyleSheet } from "re
1515
import { useResolveClassNames } from "uniwind";
1616

1717
import { AppText as Text } from "./components/AppText";
18+
import { renderCompactBrandTitle } from "./components/CompactBrandTitle";
1819
import { ArchivedThreadsRouteScreen } from "./features/archive/ArchivedThreadsRouteScreen";
1920
import { useAgentNotificationNavigation } from "./features/agent-awareness/notificationNavigation";
2021
import { ClerkSettingsSheetDetentProvider } from "./features/cloud/ClerkSettingsSheetDetent";
@@ -377,7 +378,8 @@ export const RootStack = createNativeStackNavigator({
377378
...GLASS_HEADER_OPTIONS,
378379
contentStyle: { backgroundColor: "transparent" },
379380
headerBackVisible: false,
380-
title: "Threads",
381+
headerTitle: renderCompactBrandTitle,
382+
title: "T3 Code",
381383
},
382384
}),
383385
Thread: createNativeStackScreen({

0 commit comments

Comments
 (0)