Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
df13391
fix(azure-devops): a fork's same-named branch no longer hides the bra…
saphid Nov 6, 2026
8a942c2
perf(server): provider-thread reads start from owned rows, not every …
juliusmarminge Nov 6, 2026
50f0dac
perf(server): stop tracing the per-event background-work probe (#31584)
juliusmarminge Nov 6, 2026
8ee2f35
perf(server): stop tracing each transcript file in usage scans (#31586)
juliusmarminge Nov 6, 2026
c76f94e
perf(cursor): stop tracing the per-event account usage hash (#31017)
juliusmarminge Nov 6, 2026
9db5d56
fix(muse): recover from unavailable session views (#32154)
Sidreyas Nov 6, 2026
c09060b
feat(web): sort and filter transcription models (#32045)
voltcrash Nov 6, 2026
6fe0fdf
fix(azure-devops): existing pull requests are found for branches cont…
aotradovec Nov 6, 2026
bdb8d5d
perf(server): background PR discovery no longer delays git status aft…
juliusmarminge Nov 6, 2026
8bf1b64
fix(web): the citation comment box opens at the quote instead of slid…
juliusmarminge Nov 6, 2026
40dc291
fix(clients): dictating a citation comment or preview annotation uses…
juliusmarminge Nov 6, 2026
32a0531
fix(web): PR bodies keep before/after video tables intact (#25988)
bartlomiejbloniarz Nov 6, 2026
05b7226
perf(release): fetch the nightly Android APK in a background step (#3…
t3dotgg Nov 6, 2026
423254e
perf(release): macOS CLI notarization no longer blocks the desktop bu…
t3dotgg Nov 6, 2026
cb8e0de
ci(release): npm publish no longer checks out and installs the worksp…
t3dotgg Nov 6, 2026
b90e554
ci(release): macOS arm64 stops compressing a pnpm store it cannot sav…
t3dotgg Nov 6, 2026
d5fc29f
fix(web): relay-connected microphone destinations authorize speech re…
juliusmarminge Nov 6, 2026
240446e
fix(server): a usage-limit auto-resume survives a server restart (#31…
shivamhwp Nov 7, 2026
e0ec11b
fix(mobile): review diff no longer goes blank after switching section…
juliusmarminge Nov 7, 2026
0bba368
fix(mobile): iOS filter menus close back into their pill without clip…
juliusmarminge Nov 7, 2026
23bdb9e
fix(mobile): Android composer renders DM Sans (#30230)
juliusmarminge Nov 7, 2026
d944e2a
fix(desktop): open Teams web in the preview browser (#31489)
shivamhwp Nov 7, 2026
07508ff
fix(web): popovers over the desktop header respond to the pointer (#3…
shivamhwp Nov 7, 2026
b19c9f0
fix(web): add installable icons for Firefox on Android (#30420)
shivamhwp Nov 7, 2026
08a3909
fix: show latest reply age in active thread rows (#31825)
shivamhwp Nov 7, 2026
4cb6945
fix(mobile): keep settled status pills readable (#31635)
shivamhwp Nov 7, 2026
9f2bb98
fix(mobile): preserve line breaks in Markdown table cells (#29355)
shivamhwp Nov 7, 2026
006d7e3
fix(mobile): stop the work-row shimmer from updating every frame (#28…
shivamhwp Nov 7, 2026
33f8584
fix(server): t3 service install no longer moves a --base-dir service …
shivamhwp Nov 7, 2026
fe901ac
feat(voice): opt into cloud dictation with OpenAI, Grok, or OpenRoute…
juliusmarminge Nov 7, 2026
4bd82b2
fix(web): make first-run dictation setup discoverable (#32197)
juliusmarminge Nov 7, 2026
15b7275
fix(web): first-run voice setup offers a model for the user's languag…
juliusmarminge Nov 7, 2026
d4247a5
feat: auto-settle inactive threads after 7 days by default (#32325)
t3dotgg Nov 7, 2026
faeed8c
docs(updates): say when Update all leaves a provider out (#32269)
Djordje-Stojanovic Nov 7, 2026
d5c2c12
test(server): three more tests stop assuming a POSIX host (#32263)
MikaMcFlurry Nov 7, 2026
f901c37
fix(server): git failures are read in English when macOS git follows …
jason5545 Nov 7, 2026
f4c8854
fix(marketing): balance fork and download heading text (#32194)
extoci Nov 7, 2026
984fe2d
perf(shared): scan usage-limit support once per provider catalog (#32…
luke2x Nov 7, 2026
257e407
fix(pi): the T3 MCP extension file is written atomically (#32231)
rishabhx29 Nov 7, 2026
f8c7c75
fix(web): the main sidebar stays collapsed after relaunch (#24202)
shivamhwp Nov 7, 2026
0f4a130
fix(codex): explain speed limits on managed connections (#30322)
shivamhwp Nov 7, 2026
83a3674
fix(web): isolate scrollbar styles from iframe owners (#32398)
Yash-Singh1 Nov 7, 2026
fad24fc
fix(azure-devops): default-branch lookup no longer fails for missing …
voltcrash Nov 7, 2026
bc46c0f
perf(server): avoid lsof polling when Linux proc is usable (#30011)
voltcrash Nov 7, 2026
1b95213
fix(providers): remove incomplete ChatGPT connections (#29280)
voltcrash Nov 7, 2026
fd1adc8
fix(acp): retain streamed child tool output (#32411)
StiensWout Nov 7, 2026
7f7bf26
fix(web): recover ACP settings when projects change (#32409)
StiensWout Nov 7, 2026
48bb784
fix(shared): inline code like `allocator.cc:217` becomes a file chip …
felixlaga Nov 7, 2026
ce9b806
fix(server): theme files saved with a UTF-8 BOM are published instead…
felixlaga Nov 7, 2026
a975217
fix(grok): long command titles no longer end in a broken character (#…
felixlaga Nov 7, 2026
95e7dbf
fix(github): keep review-required until required reviewers approve (#…
wSedlacek Nov 7, 2026
c864b1c
feat(web): delete archived threads from the archive page (#22160)
voltcrash Nov 7, 2026
2d0a9df
fix(provider-acp): preserve UTF-8 across interleaved terminal streams…
StiensWout Nov 7, 2026
5cb2a7d
fix(pi): preserve explicit thinking when switching models (#32389)
StiensWout Nov 7, 2026
beaeeff
fix(acp): bound unread raw notification retention (#19585)
StiensWout Nov 7, 2026
eeb6e23
fix(web): keep Pi thinking controls visible before selection (#27397)
voltcrash Nov 7, 2026
67fdc53
fix(desktop): published themes reach the app when it uses this machin…
not-compromised Nov 7, 2026
e344153
fix(antigravity): check account access after installation (#32446)
Yash-Singh1 Nov 7, 2026
f016a16
fix(acp): preserve native form questions and typed answers (#19659)
StiensWout Nov 7, 2026
9f00ced
fix(antigravity): explain missing SOCKS dependency during refresh (#1…
StiensWout Nov 7, 2026
4c3a9d5
fix(cursor): use the SDK account for usage data (#32448)
Yash-Singh1 Nov 7, 2026
917ce99
fix(web): agent sign-in defaults to Auto access (#32186)
flamboh Nov 7, 2026
a601caa
fix(gitlab): `glab mr accept` counts as a shell merge (#32400)
felixlaga Nov 7, 2026
97d04c3
fix(forgejo): tea's `pull` and `m` aliases count as shell merges (#32…
felixlaga Nov 7, 2026
e455200
fix: side chats start with the parent's permission mode and reasoning…
Iweisc Nov 7, 2026
25bf84a
fix(mobile): center the Choose project empty-state buttons (#27450)
juliusmarminge Nov 7, 2026
1230254
Merge remote-tracking branch 'upstream/main' into coil/sync-42381758060
radroid Nov 7, 2026
ec8acc3
chore(coil): the PWA manifest uses upstream's web icons
radroid Nov 7, 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
  •  
  •  
  •  
8 changes: 7 additions & 1 deletion .github/actions/setup-ci-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ inputs:
description: Arguments for `vp install`, such as filters. Empty installs the whole workspace.
required: false
default: ""
save-pnpm-store:
description: Set to "false" when a concurrent job with the same install args saves the pnpm store
required: false
default: "true"
runs:
using: composite
steps:
Expand Down Expand Up @@ -125,9 +129,11 @@ runs:
# A filtered install that started from the current full store adds nothing
# worth a second copy of it. A stacked PR, based on a branch other than the
# default, saves a ~1 GB store only its own reruns can read, so it only restores.
# Concurrent jobs with the same install args reserve one key, so the losers
# compress the store for nothing; all but one pass save-pnpm-store: false.
- name: Save pnpm store
if: >-
inputs.install == 'true' &&
inputs.install == 'true' && inputs.save-pnpm-store == 'true' &&
(github.event_name != 'pull_request' || github.base_ref == github.event.repository.default_branch) &&
steps.pnpm_cache.outputs.cache-hit != 'true' &&
(steps.pnpm_store.outputs.namespace == 'full' ||
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const layerElectronApp = (calls: ElectronAppCalls) =>
metadata: Effect.die("unexpected metadata read"),
name: Effect.succeed("T3 Code"),
systemLocale: Effect.succeed("en-US"),
preferredSystemLanguages: Effect.succeed(["en-US"]),
whenReady: Effect.void,
requestSingleInstanceLock: Effect.succeed(true),
quit: Effect.void,
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopLifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function layerElectronApp(
metadata: Effect.die("unexpected metadata read"),
name: Effect.succeed("T3 Code"),
systemLocale: Effect.succeed("en-US"),
preferredSystemLanguages: Effect.succeed(["en-US"]),
whenReady: Effect.void,
requestSingleInstanceLock: Effect.succeed(true),
quit,
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/electron/ElectronApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class ElectronApp extends Context.Service<
readonly name: Effect.Effect<string>;
/** Packaged builds ship only en-US; app.getLocale() and Intl defaults do not reflect the OS locale. */
readonly systemLocale: Effect.Effect<string>;
readonly preferredSystemLanguages: Effect.Effect<ReadonlyArray<string>>;
readonly whenReady: Effect.Effect<void, ElectronAppWhenReadyError>;
readonly requestSingleInstanceLock: Effect.Effect<boolean>;
readonly quit: Effect.Effect<void>;
Expand Down Expand Up @@ -155,6 +156,7 @@ export const make = ElectronApp.of({
}
return toSystemLocaleTag(identifier, hourCycle);
}),
preferredSystemLanguages: Effect.sync(() => Electron.app.getPreferredSystemLanguages()),
whenReady: Effect.gen(function* () {
const isPackaged = Electron.app.isPackaged;
yield* Effect.tryPromise({
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { installNotificationBadge } from "./methods/notificationBadge.ts";
import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts";
import { getConnectionCatalog, setConnectionCatalog } from "./methods/connectionCatalog.ts";
import {
getHostEnvironmentId,
getLocalEnvironmentEnabled,
setLocalEnvironmentEnabled,
} from "./methods/localEnvironment.ts";
Expand Down Expand Up @@ -40,6 +41,7 @@ import {
getLocalEnvironmentBearerToken,
installRelayTelemetryEnabled,
getSystemLocale,
getPreferredSystemLanguages,
getWindowFullscreenState,
openExternal,
readMedia,
Expand Down Expand Up @@ -96,10 +98,12 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
yield* ipc.handleSync(getAppBranding);
yield* installRelayTelemetryEnabled();
yield* ipc.handleSync(getSystemLocale);
yield* ipc.handleSync(getPreferredSystemLanguages);
yield* ipc.handleSync(getWindowFullscreenState);
yield* ipc.handleSync(getLocalEnvironmentBootstraps);
yield* ipc.handleSync(getLocalEnvironmentEnabled);
yield* ipc.handle(setLocalEnvironmentEnabled);
yield* ipc.handle(getHostEnvironmentId);
yield* ipc.handle(getLocalEnvironmentBearerToken);

yield* ipc.handle(getClientSettings);
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ export const CLI_COMMAND_UNINSTALL_CHANNEL = "desktop:cli-command-uninstall";
export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";
export const GET_RELAY_TELEMETRY_ENABLED_CHANNEL = "desktop:get-relay-telemetry-enabled";
export const GET_SYSTEM_LOCALE_CHANNEL = "desktop:get-system-locale";
export const GET_PREFERRED_SYSTEM_LANGUAGES_CHANNEL = "desktop:get-preferred-system-languages";
export const GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL = "desktop:get-local-environment-bootstraps";
export const GET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL = "desktop:get-local-environment-enabled";
export const SET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL = "desktop:set-local-environment-enabled";
export const GET_HOST_ENVIRONMENT_ID_CHANNEL = "desktop:get-host-environment-id";
export const GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL =
"desktop:get-local-environment-bearer-token";
export const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";
Expand Down
29 changes: 28 additions & 1 deletion apps/desktop/src/ipc/methods/localEnvironment.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { assert, describe, it } from "@effect/vitest";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";

Expand All @@ -10,7 +13,11 @@ import * as ElectronApp from "../../electron/ElectronApp.ts";
import * as ElectronTheme from "../../electron/ElectronTheme.ts";
import * as DesktopAppSettings from "../../settings/DesktopAppSettings.ts";
import * as DesktopWindow from "../../window/DesktopWindow.ts";
import { getLocalEnvironmentEnabled, setLocalEnvironmentEnabled } from "./localEnvironment.ts";
import {
getHostEnvironmentId,
getLocalEnvironmentEnabled,
setLocalEnvironmentEnabled,
} from "./localEnvironment.ts";

const layerUnusedLifecycleRuntime = Layer.mergeAll(
DesktopShutdown.layer,
Expand All @@ -27,6 +34,26 @@ const layerUnusedLifecycleRuntime = Layer.mergeAll(
);

describe("local environment IPC", () => {
it.effect("reads the shared home identity without creating one", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const stateDir = yield* fs.makeTempDirectoryScoped();
const environment = DesktopEnvironment.DesktopEnvironment.of({
path,
stateDir,
} as DesktopEnvironment.DesktopEnvironment["Service"]);
const read = getHostEnvironmentId
.handler(undefined)
.pipe(Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment));
assert.strictEqual(yield* read, null);
assert.isFalse(yield* fs.exists(path.join(stateDir, "environment-id")));
yield* fs.writeFileString(path.join(stateDir, "environment-id"), " service-id\n");
assert.strictEqual(yield* read, "service-id");
yield* fs.writeFileString(path.join(stateDir, "environment-id"), "\n");
assert.strictEqual(yield* read, null);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);
it.effect("relaunches only when the setting changes and keeps other settings", () => {
const relaunchReasons: Array<string> = [];
const layer = Layer.mergeAll(
Expand Down
17 changes: 17 additions & 0 deletions apps/desktop/src/ipc/methods/localEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Schema from "effect/Schema";

import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts";
import * as DesktopLifecycle from "../../app/DesktopLifecycle.ts";
import * as DesktopAppSettings from "../../settings/DesktopAppSettings.ts";
import * as IpcChannels from "../channels.ts";
Expand Down Expand Up @@ -28,3 +30,18 @@ export const setLocalEnvironmentEnabled = makeIpcMethod({
}
}),
});

// A background service sharing this app's T3 home records its environment ID in the same state dir.
export const getHostEnvironmentId = makeIpcMethod({
channel: IpcChannels.GET_HOST_ENVIRONMENT_ID_CHANNEL,
payload: Schema.Void,
result: Schema.NullOr(Schema.String),
handler: Effect.fn("desktop.ipc.localEnvironment.getHostEnvironmentId")(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const contents = yield* fileSystem
.readFileString(environment.path.join(environment.stateDir, "environment-id"))
.pipe(Effect.orElseSucceed(() => ""));
return contents.trim() || null;
}),
});
9 changes: 9 additions & 0 deletions apps/desktop/src/ipc/methods/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ export const getSystemLocale = DesktopIpc.makeSyncIpcMethod({
}),
});

export const getPreferredSystemLanguages = DesktopIpc.makeSyncIpcMethod({
channel: IpcChannels.GET_PREFERRED_SYSTEM_LANGUAGES_CHANNEL,
result: Schema.Array(Schema.String),
handler: Effect.fn("desktop.ipc.window.getPreferredSystemLanguages")(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
return yield* electronApp.preferredSystemLanguages;
}),
});

export const getWindowFullscreenState = DesktopIpc.makeSyncIpcMethod({
channel: IpcChannels.GET_WINDOW_FULLSCREEN_STATE_CHANNEL,
result: Schema.Boolean,
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ contextBridge.exposeInMainWorld("desktopBridge", {
const result = ipcRenderer.sendSync(IpcChannels.GET_SYSTEM_LOCALE_CHANNEL);
return typeof result === "string" ? result : null;
},
getPreferredSystemLanguages: () => {
const result: unknown = ipcRenderer.sendSync(
IpcChannels.GET_PREFERRED_SYSTEM_LANGUAGES_CHANNEL,
);
return Array.isArray(result) ? result.filter((tag) => typeof tag === "string") : [];
},
getLocalEnvironmentBootstraps: () => {
const result = ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL);
if (!Array.isArray(result)) {
Expand All @@ -120,6 +126,7 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL) !== false,
setLocalEnvironmentEnabled: (enabled) =>
ipcRenderer.invoke(IpcChannels.SET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL, enabled),
getHostEnvironmentId: () => ipcRenderer.invoke(IpcChannels.GET_HOST_ENVIRONMENT_ID_CHANNEL),
getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL),
setClientSettings: (settings) =>
ipcRenderer.invoke(IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, settings),
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/preview/AnnotationStyles.generated.ts

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion apps/desktop/src/preview/BrowserSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const { fromPartition, sessions } = vi.hoisted(() => ({
readonly setPermissionRequestHandler: ReturnType<typeof vi.fn>;
readonly setPermissionCheckHandler: ReturnType<typeof vi.fn>;
readonly setUserAgent: ReturnType<typeof vi.fn>;
readonly webRequest: { readonly onBeforeSendHeaders: ReturnType<typeof vi.fn> };
}
>(),
}));
Expand Down Expand Up @@ -62,6 +63,7 @@ describe("BrowserSession", () => {
setPermissionRequestHandler: vi.fn(),
setPermissionCheckHandler: vi.fn(),
setUserAgent: vi.fn(),
webRequest: { onBeforeSendHeaders: vi.fn() },
};
sessions.set(partition, browserSession);
return browserSession;
Expand Down Expand Up @@ -115,10 +117,12 @@ describe("BrowserSession", () => {
}).pipe(Effect.provide(layer)),
);

it.effect("keeps the guest's effective User-Agent equal to Electron's native one", () =>
it.effect("keeps the native User-Agent except for the Electron token sent to Teams", () =>
Effect.gen(function* () {
const nativeUserAgent =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) T3Code(Alpha)/0.0.33 Chrome/146.0.7680.216 Electron/41.5.0 Safari/537.36";
const teamsUserAgent =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) T3Code(Alpha)/0.0.33 Chrome/146.0.7680.216 Safari/537.36";
fromPartition.mockReset();
fromPartition.mockImplementation((partition: string) => {
let userAgent = nativeUserAgent;
Expand All @@ -131,6 +135,7 @@ describe("BrowserSession", () => {
setUserAgent: vi.fn((next: string) => {
userAgent = next;
}),
webRequest: { onBeforeSendHeaders: vi.fn() },
};
sessions.set(partition, browserSession);
return browserSession;
Expand All @@ -143,6 +148,29 @@ describe("BrowserSession", () => {
const browserSession = sessions.get(partition);
assert.isDefined(browserSession);
assert.strictEqual(browserSession.getUserAgent(), nativeUserAgent);

// Teams redirects Electron to an error page before sign-in; every other host sees the native one.
const calls = browserSession.webRequest.onBeforeSendHeaders.mock.calls;
assert.strictEqual(calls.length, 1);
const [filter, listener] = calls[0] ?? [];
assert.deepStrictEqual(filter, {
urls: ["https://teams.cloud.microsoft/*", "https://teams.microsoft.com/*"],
});
const send = (requestHeaders: Record<string, string>) => {
let response: { requestHeaders?: Record<string, string> } | undefined;
listener({ requestHeaders }, (next: typeof response) => {
response = next;
});
return response;
};
for (const key of ["User-Agent", "user-agent"]) {
const requestHeaders = { [key]: nativeUserAgent, Accept: "*/*" };
assert.deepStrictEqual(send(requestHeaders), {
requestHeaders: { [key]: teamsUserAgent, Accept: "*/*" },
});
assert.strictEqual(requestHeaders[key], nativeUserAgent);
}
assert.deepStrictEqual(send({ Accept: "*/*" }), {});
}).pipe(Effect.provide(layer)),
);

Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/src/preview/BrowserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const externalProtocolPromptUrl = (rawUrl: string | undefined): string | null =>
}
};

// Teams sends Electron User-Agents to an error page before sign-in. Electron allows one listener per session.
const TEAMS_URL_PATTERNS = ["https://teams.cloud.microsoft/*", "https://teams.microsoft.com/*"];

const MAX_PROMPT_URL_LENGTH = 300;

const truncateForPrompt = (text: string): string =>
Expand Down Expand Up @@ -342,6 +345,22 @@ export const make = Effect.gen(function* BrowserSessionMake() {
? clipboardReadDecisions.get(permissionOrigin(requestingOrigin) ?? "") === true
: ALLOWED_PREVIEW_PERMISSIONS.has(permission),
);
browserSession.webRequest.onBeforeSendHeaders(
{ urls: TEAMS_URL_PATTERNS },
({ requestHeaders }, callback) => {
const userAgent = Object.entries(requestHeaders).find(
([name]) => name.toLowerCase() === "user-agent",
);
if (userAgent === undefined) {
callback({});
return;
}
const [name, value] = userAgent;
callback({
requestHeaders: { ...requestHeaders, [name]: value.replace(/ Electron\/\S+/, "") },
});
},
);
const next = new Map(sessions);
next.set(partition, browserSession);
return [browserSession, next] as const;
Expand Down
17 changes: 15 additions & 2 deletions apps/desktop/src/preview/PickPreload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ function startAnnotation(
" h-7 w-7 shrink-0 rounded-full border-primary bg-primary p-0 text-primary-foreground hover:bg-primary/90";
const voiceSkip = createButton("Skip", "Skip transcript cleanup");
voiceSkip.className += " h-7 shrink-0 rounded-full px-2.5";
// createButton's rounded-md sorts after rounded-full in the generated CSS, so it would win.
for (const button of [voiceCancel, voiceFinish, voiceSkip]) button.classList.remove("rounded-md");
const voiceStatus = document.createElement("div");
voiceStatus.className = "flex h-7 min-w-0 flex-1 items-center gap-2";
voiceStatus.setAttribute("role", "status");
Expand Down Expand Up @@ -744,16 +746,27 @@ function startAnnotation(
}
const recording = state.phase === "recording";
const active = state.phase !== "idle" && state.phase !== "error";
const wasActive = voicePill.parentElement === composerRow;
voiceButton.hidden = !state.available || active;
voiceButton.disabled = voiceBusy;
const label = voiceSettings ? "Open voice settings" : "Start voice input";
voiceButton.title =
!voiceSettings && state.shortcutLabel ? `${label} (${state.shortcutLabel})` : label;
voiceButton.setAttribute("aria-label", label);
voiceRow.hidden = state.phase === "idle";
// An active dictation takes the textarea's place; it takes focus once (sr-only) so Enter still attaches.
voiceRow.hidden = state.phase === "idle" || active;
comment.classList.toggle("sr-only", active);
if (active && !wasActive) comment.focus({ preventScroll: true });
submit.hidden = active;
// Re-inserting on every level update would blur a focused Finish or Cancel.
if (active && voicePill.parentElement !== composerRow) comment.after(voicePill);
else if (!active && voicePill.parentElement !== voiceRow) voiceRow.appendChild(voicePill);
voicePill.className = active
? "flex h-9 w-64 min-w-0 items-center gap-2 rounded-full border border-border bg-background p-1"
? "flex h-8 min-w-0 flex-1 items-center gap-2"
: "flex min-w-0 items-center gap-1";
// A draft that arrived while the textarea was sr-only was measured at 1px wide.
if (wasActive && !active) resizeComment();
else if (phaseChanged) queueEditorLayout();
voiceCancel.title =
state.phase === "error" ? "Dismiss voice input error" : "Cancel voice input";
voiceCancel.setAttribute("aria-label", voiceCancel.title);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function layerElectronApp(
metadata: Effect.die("unexpected metadata read"),
name: Effect.succeed("T3 Code"),
systemLocale: Effect.succeed("en-US"),
preferredSystemLanguages: Effect.succeed(["en-US"]),
whenReady: Effect.void,
requestSingleInstanceLock: Effect.succeed(true),
quit: Effect.void,
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const layerElectronApp = Layer.succeed(ElectronApp.ElectronApp, {
metadata: Effect.die("unexpected metadata read"),
name: Effect.succeed("T3 Code"),
systemLocale: Effect.succeed("en-US"),
preferredSystemLanguages: Effect.succeed(["en-US"]),
whenReady: Effect.void,
requestSingleInstanceLock: Effect.succeed(true),
quit: Effect.void,
Expand Down
4 changes: 4 additions & 0 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,10 @@ const mobileEndorsementRows = [
.cta-title {
font-size: clamp(40px, 6vw, 72px);
}
.open-head h2,
.cta-title {
text-wrap: balance;
}
.cta-sub {
color: var(--fg-muted);
font-size: 18px;
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ const config: ExpoConfig = {
],
extra: {
appVariant: APP_VARIANT,
secretsFeatureEnabled: ["1", "true"].includes(
process.env.T3CODE_FEATURE_SECRETS?.trim().toLowerCase() ?? "",
),
iosPersonalTeamBuild: isIosPersonalTeamBuild,
relay: {
url: repoEnv.T3CODE_RELAY_URL ?? null,
Expand Down
Loading
Loading