Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
018be98
sync: upstream e16b8b059c..0f602b3372 (13 commits)
github-actions[bot] Sep 10, 2026
d3d0cf4
feat(ios): add linked PR collections, stack actions and image zoom
github-actions[bot] Sep 10, 2026
b4231a1
feat: port upstream diff and navigation controls across clients
github-actions[bot] Sep 10, 2026
24ee217
feat: add V2 web PR controls and native Codex result cards
github-actions[bot] Sep 10, 2026
734c281
feat: add PR label editing and resilient media previews
github-actions[bot] Sep 10, 2026
9e5005e
feat: add custom model pricing across web and Swift
github-actions[bot] Sep 10, 2026
206ec36
feat: attach V2 task progress and notices to the composer
github-actions[bot] Sep 10, 2026
23bceac
feat(web): compare provider quotas across environments
github-actions[bot] Sep 10, 2026
32960f3
feat(web): navigate adjacent turns from the timeline minimap
github-actions[bot] Sep 10, 2026
cba146f
feat: send larger attachments through signed uploads
github-actions[bot] Sep 10, 2026
5a36c38
feat: port Codex app-access approvals onto V2
github-actions[bot] Sep 10, 2026
85c1d93
feat: share environment machine icons across web and Swift
github-actions[bot] Sep 10, 2026
56f30b2
fix: preserve terminal replay and pause hidden rendering
github-actions[bot] Sep 10, 2026
5702677
feat: separate provider accounts from their settings editors
github-actions[bot] Sep 10, 2026
abb5071
feat(models): edit custom model names and options across clients
github-actions[bot] Sep 10, 2026
c7b8f23
test(mobile): use cursor terminal state in menu fixtures
github-actions[bot] Sep 10, 2026
1912f2a
feat(chat): quote assistant responses across web and Swift
github-actions[bot] Sep 10, 2026
d1aacd9
fix(chat): retain setup feedback until V2 starts and mute tool failures
github-actions[bot] Sep 10, 2026
9d1ba16
feat(web): attach sidebar file drops to the correct thread draft
github-actions[bot] Sep 10, 2026
b19fb49
feat(web): attach composer tasks requests and stash tabs on V2
github-actions[bot] Sep 10, 2026
d0e0c4b
feat(ios): preserve multiple drafts in an atomic stash queue
github-actions[bot] Sep 10, 2026
2abffe7
feat(web): fold interim responses within completed V2 runs
github-actions[bot] Sep 10, 2026
d085008
feat(web): retain review filters and rank pull requests by readiness
github-actions[bot] Sep 10, 2026
0ba463e
feat(web): expose V2 thread actions in the chat header
github-actions[bot] Sep 10, 2026
34a7c22
feat(ios): accept file drops in the composer
github-actions[bot] Sep 10, 2026
49b6c63
fix(web): preserve manual panel choices during V2 runs
github-actions[bot] Sep 10, 2026
a7264b4
fix(web): use accessible tooltips in provider limits
github-actions[bot] Sep 10, 2026
bc50315
perf(web): start shared diff workers only for code views
github-actions[bot] Sep 10, 2026
27fffed
feat(files): browse folders from web and native previews
github-actions[bot] Sep 10, 2026
25402c0
fix(ios): preserve dropped attachment names and media types
github-actions[bot] Sep 10, 2026
f8b46d7
feat(ios): fold completed-run commentary with source navigation
github-actions[bot] Sep 10, 2026
3c24fb0
feat(projects): sync custom icons across web and native clients
github-actions[bot] Sep 10, 2026
e7d22eb
fix(web): restore composer focus when returning to the window
github-actions[bot] Sep 10, 2026
b4b9129
feat(ios): browse pull requests across connected environments
github-actions[bot] Sep 10, 2026
fa47a91
feat(ios): read paged pull request code and commit diffs
github-actions[bot] Sep 10, 2026
a809655
feat(ios): save and submit host pull request reviews
github-actions[bot] Sep 10, 2026
2811719
feat(ios): reply to and resolve pull request conversations
github-actions[bot] Sep 10, 2026
645de98
feat(ios): expand host pull request file context
github-actions[bot] Sep 10, 2026
27c5664
feat(ios): manage pull request state and refresh host data
github-actions[bot] Sep 10, 2026
d7ff24d
feat(ios): edit pull request text and owned comments
github-actions[bot] Sep 10, 2026
5cb4e69
feat(ios): react to pull requests and review comments
github-actions[bot] Sep 10, 2026
70e27b0
feat(ios): request and withdraw pull request reviews
github-actions[bot] Sep 10, 2026
828a371
feat(ios): prepare PR checkouts and stage agent tasks
github-actions[bot] Sep 10, 2026
708f687
feat(chat): focus live V2 tool activity across web and iOS
github-actions[bot] Sep 10, 2026
8044e29
fix(composer): preserve multiline drafts and loading layout
github-actions[bot] Sep 10, 2026
56e267b
feat(web): open PR and diff panels from V2 run state
github-actions[bot] Sep 10, 2026
6275e9f
feat(ios): manage provider accounts and connection settings
github-actions[bot] Sep 10, 2026
f5f095c
feat(chat): summarize completed V2 tool activity
github-actions[bot] Sep 10, 2026
dfa03b5
feat(ios): hand selected PR findings and diff lines to agents
github-actions[bot] Sep 10, 2026
ab3983e
feat(ios): highlight exact assistant citation source text
github-actions[bot] Sep 10, 2026
181cbba
feat(web): persist sidebar drag ordering through V2
github-actions[bot] Sep 10, 2026
1a8b083
feat(chat): retain expanded tool history reading positions
github-actions[bot] Sep 10, 2026
ed5a9f1
feat(ios): stage sidebar file drops in destination threads
github-actions[bot] Sep 10, 2026
eb7612d
feat(ios): render and edit PR review context chips
github-actions[bot] Sep 10, 2026
6c32743
feat(web): compact the composer while reading a conversation
github-actions[bot] Sep 10, 2026
20ddb12
feat(web): add scroll controls to overflowing panel tabs
github-actions[bot] Sep 10, 2026
b466689
feat(server): retain recent PR reads across restarts
github-actions[bot] Sep 10, 2026
dca2f79
feat(web): add opt-in panel animations across workspaces
github-actions[bot] Sep 10, 2026
02411f4
feat: add guided setup and import CLI history into V2
github-actions[bot] Sep 10, 2026
453c300
feat(web): browse Markdown images as a gallery
github-actions[bot] Sep 10, 2026
5582880
feat: preview host files and document attachments across clients
github-actions[bot] Sep 10, 2026
fa21e02
feat: automatically pull clean default branches with project overrides
github-actions[bot] Sep 10, 2026
caa7dc6
feat: apply project browser access overrides to V2 sessions
github-actions[bot] Sep 10, 2026
7c3d8eb
feat: scope project model and workspace defaults to their machine
github-actions[bot] Sep 10, 2026
7e3ee03
feat: inherit machine actions across project clients and V2 worktrees
github-actions[bot] Sep 10, 2026
d812173
feat: open account-scoped provider setup from client model pickers
github-actions[bot] Sep 10, 2026
2f667bd
feat: resolve model catalogs and picker badges through V2
github-actions[bot] Sep 10, 2026
2c8ac7e
fix(web): preserve explicit draft models when defaults change
github-actions[bot] Sep 10, 2026
8a58836
fix(web): show when server settings cannot be saved
github-actions[bot] Sep 10, 2026
945fa1d
feat: reserve markdown image sizes from signed asset metadata
github-actions[bot] Sep 10, 2026
6bafe55
feat(server): stream account usage limits from V2 providers
github-actions[bot] Sep 10, 2026
018e30c
fix(ios): restore reading offsets inside tool history
github-actions[bot] Sep 10, 2026
bc13130
feat: present V2 tool activity by action and status
github-actions[bot] Sep 11, 2026
75e4691
feat: show browser and app sources in V2 tool activity
github-actions[bot] Sep 11, 2026
ea0e608
feat: track pull request sources and stacks on V2
github-actions[bot] Sep 11, 2026
3dcb890
feat: show saved branch PR status across clients
github-actions[bot] Sep 11, 2026
f4ab6f5
feat: show PR stack counts in sidebar controls
github-actions[bot] Sep 11, 2026
45764f6
feat: settle threads automatically on V2
github-actions[bot] Sep 11, 2026
1e88986
fix: refresh V2 threads after confirmed PR merges
github-actions[bot] Sep 11, 2026
823a647
feat: link created pull requests to V2 threads
github-actions[bot] Sep 11, 2026
632de90
fix: resume V2 Codex threads without decoding old history
github-actions[bot] Sep 11, 2026
fecf02d
feat: continue interrupted V2 threads after restarts
github-actions[bot] Sep 11, 2026
b6d4819
feat(web): add the V2 linked pull-request panel
github-actions[bot] Sep 12, 2026
5e83c7a
feat(ios): render and cache SVG and inline tool logos
github-actions[bot] Sep 12, 2026
8a91342
feat: share user preferences across connected machines
github-actions[bot] Sep 12, 2026
cfd958c
chore(ios): bundle the SVGView license notice
github-actions[bot] Sep 12, 2026
e89efe4
feat: measure whole-host capacity for automatic routing
github-actions[bot] Sep 12, 2026
bb0ebdd
feat: balance new V2 tasks across eligible machines
github-actions[bot] Sep 12, 2026
57a8678
feat(ios): refresh open files after V2 workspace mutations
github-actions[bot] Sep 12, 2026
8248bb6
fix(web): preserve edited file highlights and wrapped layout
github-actions[bot] Sep 12, 2026
a690cfa
feat: unify refresh feedback and live V2 activity motion
github-actions[bot] Sep 12, 2026
87eb37a
feat: complete usage history refresh and native contract parity
github-actions[bot] Sep 12, 2026
1e8bf62
feat: redeem Codex reset credits through V2 provider accounts
github-actions[bot] Sep 12, 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
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ jobs:
- name: Check the generated Swift contract fixture is current
# The SwiftUI client mirrors packages/contracts by hand, so nothing else
# makes a contract change fail its build. This is that signal.
run: node scripts/generate-swift-contract-fixtures.ts --check
run: |
node scripts/generate-swift-contract-fixtures.ts --check
node scripts/generate-swift-provider-settings.ts --check

- name: Build desktop pipeline
run: vp run build:desktop
Expand Down
915 changes: 835 additions & 80 deletions PATCH.md

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion apps/desktop/src/electron/ElectronShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ describe("ElectronShell", () => {
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens Zed's ssh deep link", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const result = yield* electronShell.openExternal("zed://ssh/example.com/home/user/project");

assert.equal(result, true);
assert.deepEqual(openExternalMock.mock.calls, [["zed://ssh/example.com/home/user/project"]]);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("does not open editor URLs that mix up link shapes", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const results = yield* Effect.all([
electronShell.openExternal("zed://extension/attacker"),
electronShell.openExternal("vscode://ssh/example.com/home/user/project"),
]);

assert.deepEqual(results, [false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("does not open remote editor URLs with userinfo", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);
Expand All @@ -64,9 +91,10 @@ describe("ElectronShell", () => {
electronShell.openExternal(
"vscode://:secret@vscode-remote/ssh-remote+example.com/home/user/project",
),
electronShell.openExternal("zed://ssh/user@example.com/home/user/project"),
]);

assert.deepEqual(results, [false, false]);
assert.deepEqual(results, [false, false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);
Expand Down
15 changes: 10 additions & 5 deletions apps/desktop/src/electron/ElectronShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import * as Option from "effect/Option";

import * as Electron from "electron";

// Remote open-in-editor deep links (`vscode://vscode-remote/ssh-remote+…`)
// must reach the OS handler; every other non-web scheme stays blocked.
// Remote editor links use VS Code’s vscode-remote shape or Zed’s ssh shape.
// Other non-web schemes stay blocked.
const SAFE_WEB_PROTOCOLS = new Set(["http:", "https:"]);
const REMOTE_EDITOR_PROTOCOLS = new Set(
REMOTE_CAPABLE_EDITOR_IDS.flatMap((id) => {
Expand All @@ -16,13 +16,18 @@ const REMOTE_EDITOR_PROTOCOLS = new Set(
}),
);

// Zed's host sits in the first path segment, so it needs its own userinfo ban.
const ZED_SSH_PATHNAME = /^\/[^/@:]+\/.+$/;

const isRemoteEditorUrl = (url: URL) =>
REMOTE_EDITOR_PROTOCOLS.has(url.protocol) &&
url.username.length === 0 &&
url.password.length === 0 &&
url.host === "vscode-remote" &&
url.pathname.startsWith("/ssh-remote+") &&
url.pathname.length > "/ssh-remote+".length;
(url.protocol === "zed:"
? url.host === "ssh" && ZED_SSH_PATHNAME.test(url.pathname)
: url.host === "vscode-remote" &&
url.pathname.startsWith("/ssh-remote+") &&
url.pathname.length > "/ssh-remote+".length);

export function parseSafeExternalUrl(rawUrl: unknown): Option.Option<string> {
if (typeof rawUrl !== "string") {
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
import * as DesktopClientSettings from "./DesktopClientSettings.ts";

const clientSettings: ClientSettings = {
onboardingCompletedAt: null,
composerCollapseOnScroll: true,
panelAnimationDurationMs: 0,
loadBalancingEnabled: false,
loadBalancingWeights: { "environment-1": 75, "environment-2": 0 },
diffColorScheme: "red-green",
alwaysExpandActivity: false,
autoOpenPlanSidebar: false,
proactivePanelsEnabled: false,
appearanceContrast: 100,
browserDefaultViewport: { _tag: "preset", width: 1024, height: 600, presetId: "nest-hub" },
browserDefaultZoomFactor: 1.25,
Expand Down
13 changes: 11 additions & 2 deletions apps/desktop/src/updates/DesktopUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,9 +722,18 @@ export const make = Effect.gen(function* () {
}

const checkedAt = yield* currentIsoTimestamp;
const releaseNotes = normalizeDesktopUpdateReleaseNotes(info.releaseNotes, info.version);
const { releaseNotes, omittedReleaseCount } = normalizeDesktopUpdateReleaseNotes(
info.releaseNotes,
info.version,
);
yield* setState(
reduceDesktopUpdateStateOnUpdateAvailable(state, info.version, checkedAt, releaseNotes),
reduceDesktopUpdateStateOnUpdateAvailable(
state,
info.version,
checkedAt,
releaseNotes,
omittedReleaseCount,
),
);
yield* Ref.set(lastLoggedDownloadMilestoneRef, -1);
yield* logUpdaterInfo("update available", {
Expand Down
157 changes: 129 additions & 28 deletions apps/desktop/src/updates/releaseNotes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,163 @@ import { describe, expect, it } from "vite-plus/test";
import { normalizeDesktopUpdateReleaseNotes } from "./releaseNotes.ts";

describe("normalizeDesktopUpdateReleaseNotes", () => {
it("splits a plain string note into items under the fallback version", () => {
const notes = normalizeDesktopUpdateReleaseNotes(
"## What's changed\n- First fix\n- Second fix",
it("shows the newest changes and counts all real changes", () => {
const result = normalizeDesktopUpdateReleaseNotes(
[
"- feat: first change",
"- fix: second change",
"- fix: third change",
"- fix: fourth change",
"- fix: fifth change",
"- fix: sixth change",
"- fix: seventh change",
"- fix: eighth change",
"- fix(web): keep long task drawers usable on small screens by @human in #8313",
"- fix(opencode): handle child approvals, stops, and model catalogs by @human in #8480",
"## New Contributors",
"- @human made their first contribution in #8435",
"**Full Changelog**: https://github.com/pingdotgg/t3code/compare/old...new",
].join("\n"),
"0.0.36-nightly.20260828.1213",
);

expect(result).toEqual({
releaseNotes: [
{
version: "0.0.36-nightly.20260828.1213",
items: [
"fix(opencode): handle child approvals, stops, and model catalogs by @human in #8480",
"fix(web): keep long task drawers usable on small screens by @human in #8313",
"fix: eighth change",
"fix: seventh change",
"fix: sixth change",
"fix: fifth change",
"fix: fourth change",
"fix: third change",
],
totalItems: 10,
},
],
omittedReleaseCount: 0,
});
});

it("excludes a GitHub HTML contributor section", () => {
const result = normalizeDesktopUpdateReleaseNotes(
"<h2>What's Changed</h2><ul><li>Older fix</li><li>Newer fix</li></ul>" +
"<h2>New Contributors</h2><ul><li>@human made their first contribution</li></ul>" +
"<h2>Full Changelog</h2>",
"1.2.3",
);
expect(notes).toEqual([{ version: "1.2.3", items: ["First fix", "Second fix"] }]);

expect(result).toEqual({
releaseNotes: [{ version: "1.2.3", items: ["Newer fix", "Older fix"], totalItems: 2 }],
omittedReleaseCount: 0,
});
});

it("does not count Markdown or HTML section headings as changes", () => {
const changes = Array.from({ length: 8 }, (_, index) => `Change ${index + 1}`);
const result = normalizeDesktopUpdateReleaseNotes(
[
{ version: "1.2.4", note: ["### Features", ...changes].join("\n- ") },
{
version: "1.2.3",
note: `<h3>Fixes</h3><ul>${changes.map((change) => `<li>${change}</li>`).join("")}</ul>`,
},
],
"1.2.4",
);

expect(result.releaseNotes).toEqual([
{ version: "1.2.4", items: changes.toReversed(), totalItems: 8 },
{ version: "1.2.3", items: changes.toReversed(), totalItems: 8 },
]);
});

it("keeps per-version groups and drops empty ones", () => {
const notes = normalizeDesktopUpdateReleaseNotes(
it("keeps per-version order and drops empty groups", () => {
const result = normalizeDesktopUpdateReleaseNotes(
[
{ version: "1.2.3", note: "- Newer change" },
{ version: "1.2.3", note: "- Newer release" },
{ version: "1.2.2", note: "Full changelog: https://example.com/compare/x...y" },
{ version: "1.2.1", note: "- Older change" },
{ version: "1.2.1", note: "- Older release" },
],
"1.2.3",
);
expect(notes).toEqual([
{ version: "1.2.3", items: ["Newer change"] },
{ version: "1.2.1", items: ["Older change"] },

expect(result).toEqual({
releaseNotes: [
{ version: "1.2.3", items: ["Newer release"], totalItems: 1 },
{ version: "1.2.1", items: ["Older release"], totalItems: 1 },
],
omittedReleaseCount: 0,
});
});

it("counts valid groups before applying the six-release limit", () => {
const releaseNotes = [
{ version: "1.3.9", note: "- Change 9" },
{ version: "1.3.8", note: "Full changelog: https://example.com/compare/x...y" },
{ version: "1.3.7", note: "- Change 7" },
{ version: "1.3.6", note: "- Change 6" },
{ version: "1.3.5", note: "- Change 5" },
{ version: "1.3.4", note: "- Change 4" },
{ version: "1.3.3", note: "- Change 3" },
{ version: "1.3.2", note: "- Change 2" },
];

const result = normalizeDesktopUpdateReleaseNotes(releaseNotes, "1.3.9");

expect(result.releaseNotes.map(({ version }) => version)).toEqual([
"1.3.9",
"1.3.7",
"1.3.6",
"1.3.5",
"1.3.4",
"1.3.3",
]);
expect(result.omittedReleaseCount).toBe(1);
});

it("decodes valid HTML entities", () => {
const notes = normalizeDesktopUpdateReleaseNotes("- Fix &amp; polish &#128512;", "1.0.0");
expect(notes).toEqual([{ version: "1.0.0", items: ["Fix & polish 😀"] }]);
const result = normalizeDesktopUpdateReleaseNotes("- Fix &amp; polish &#128512;", "1.0.0");
expect(result).toEqual({
releaseNotes: [{ version: "1.0.0", items: ["Fix & polish 😀"], totalItems: 1 }],
omittedReleaseCount: 0,
});
});

it("ignores malformed entries instead of throwing", () => {
const notes = normalizeDesktopUpdateReleaseNotes(
it("ignores malformed and empty entries instead of throwing", () => {
const result = normalizeDesktopUpdateReleaseNotes(
[
{ version: "1.2.3", note: "- Valid change" },
{ version: "1.2.2", note: "" },
{ version: 42, note: "- Bad version type" },
{ version: "1.2.1", note: { html: "<p>object note</p>" } },
"not an object",
null,
],
"1.2.3",
);
expect(notes).toEqual([{ version: "1.2.3", items: ["Valid change"] }]);

expect(result).toEqual({
releaseNotes: [{ version: "1.2.3", items: ["Valid change"], totalItems: 1 }],
omittedReleaseCount: 0,
});
});

it("returns non-empty groups even when preceded by many boilerplate-only groups", () => {
const boilerplate = Array.from({ length: 7 }, (_, index) => ({
version: `1.3.${9 - index}`,
note: "Full changelog: https://example.com/compare/x...y",
}));
const notes = normalizeDesktopUpdateReleaseNotes(
[...boilerplate, { version: "1.3.2", note: "- Older but real change" }],
"1.3.9",
);
expect(notes).toEqual([{ version: "1.3.2", items: ["Older but real change"] }]);
it("returns an empty result for an invalid payload", () => {
expect(normalizeDesktopUpdateReleaseNotes({ note: "- Invalid" }, "1.0.0")).toEqual({
releaseNotes: [],
omittedReleaseCount: 0,
});
});

it("does not throw on out-of-range numeric entities and keeps the literal", () => {
const notes = normalizeDesktopUpdateReleaseNotes("- Broken entity &#9999999999;", "1.0.0");
expect(notes).toEqual([{ version: "1.0.0", items: ["Broken entity &#9999999999;"] }]);
const result = normalizeDesktopUpdateReleaseNotes("- Broken entity &#9999999999;", "1.0.0");
expect(result).toEqual({
releaseNotes: [{ version: "1.0.0", items: ["Broken entity &#9999999999;"], totalItems: 1 }],
omittedReleaseCount: 0,
});
});
});
Loading
Loading