Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b91e466
feat(marketing): replace the screenshot crop with a designed social c…
flamboh Oct 1, 2026
a3fb539
docs: update user count in AGENTS.md (#11413)
Kamkmgamer Oct 1, 2026
5a574a7
test(web): remove duplicate sidebar ordering tests (#14558)
t3-code[bot] Oct 1, 2026
a97a4a9
Update marketing stats for GitHub stars and users
juliusmarminge Oct 1, 2026
6ea01f8
fix: cloned projects show their favicon instead of a monogram (#14635)
t3dotgg Oct 1, 2026
094fb23
docs: server features are services, and handlers stay thin (#14613)
juliusmarminge Oct 1, 2026
a3abb52
fix(ci): pin eas-cli so mobile PR previews deploy again (#14659)
juliusmarminge Oct 1, 2026
10ac2f2
fix(server): background GitHub polling uses ~74% fewer calls with bat…
t3dotgg Oct 1, 2026
67f6400
fix(web): show the Docker icon on dockerfile code blocks (#14675)
chrisdeeming Oct 1, 2026
0a04cc5
feat(web): update providers on every machine with one click (#14678)
t3dotgg Oct 2, 2026
b33eda1
feat(web): beta Working section hides busy threads until they need yo…
t3dotgg Oct 2, 2026
ced865c
fix(web): PR checks status collapses to its icon instead of wrapping …
flamboh Oct 2, 2026
fa6262d
fix: remove outdated restart setting update advice (#14710)
t3-code[bot] Oct 2, 2026
20012eb
fix(web): keep the terminal toggle clear of the last header action (#…
Mnigos Oct 2, 2026
2731929
fix(web): group composer undo the way the Lexical composer did (#14674)
chrisdeeming Oct 2, 2026
3e60fec
fix(desktop): markdown pages no longer render invisible in the dark-m…
shivamhwp Oct 2, 2026
39405f1
fix(mobile): one No project row at the top of the project picker (#14…
juliusmarminge Oct 2, 2026
99e0852
Remove unused isCloudDebugEnabled and isTerminalDebugEnabled (#14367)
kridaydave Oct 2, 2026
54084ae
fix(mobile): update Expo 58 to restore widgets and Live Activities (#…
juliusmarminge Oct 2, 2026
a4aee2b
merge: include upstream main through 54084ae1e6
yvesoddly Oct 2, 2026
2df0e88
fix(web): preserve idle inbox threads and readable PR checks
eyeveil Oct 2, 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
16 changes: 15 additions & 1 deletion apps/desktop/src/preview/Manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ describe("PreviewManager", () => {
),
);

effectIt.effect("emulates prefers-color-scheme and re-applies it across webview swaps", () =>
effectIt.effect("re-applies opaque base and color scheme across webview swaps", () =>
withManager((manager) =>
Effect.gen(function* () {
const makeWebContents = (id: number) => {
Expand Down Expand Up @@ -1650,6 +1650,16 @@ describe("PreviewManager", () => {
yield* manager.registerWebview("tab_scheme", 42);
yield* Effect.yieldNow;

// Guests start with a transparent base; dark-scheme pages need an opaque
// one or Chromium skips their dark canvas.
const opaqueBase = {
color: { r: 255, g: 255, b: 255, a: 1 },
};
expect(first.sendCommand).toHaveBeenCalledWith(
"Emulation.setDefaultBackgroundColorOverride",
opaqueBase,
);

yield* manager.setColorScheme("tab_scheme", "dark");

expect(first.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {
Expand All @@ -1662,6 +1672,10 @@ describe("PreviewManager", () => {
yield* manager.registerWebview("tab_scheme", 43);
yield* Effect.yieldNow;

expect(replacement.sendCommand).toHaveBeenCalledWith(
"Emulation.setDefaultBackgroundColorOverride",
opaqueBase,
);
expect(replacement.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {
features: [{ name: "prefers-color-scheme", value: "dark" }],
});
Expand Down
13 changes: 13 additions & 0 deletions apps/desktop/src/preview/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,19 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
),
{ concurrency: "unbounded", discard: true },
);
// Electron gives `<webview>` guests a transparent base background, and
// Chromium only paints a dark canvas for dark color-scheme pages over an
// opaque base. Without this, dark-scheme pages with no background of
// their own (text/plain, e.g. .md files) render white text on white.
// White matches the webview's existing white backing, so light pages look
// the same; Chromium still swaps in its dark canvas for dark-scheme pages.
yield* attemptPromise(
{ operation: "initializeDebugger.defaultBackground", webContentsId: wc.id },
() =>
wcDebugger.sendCommand("Emulation.setDefaultBackgroundColorOverride", {
color: { r: 255, g: 255, b: 255, a: 1 },
}),
);
return [
control,
replaceMap(sessions, (copy) => {
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const clientSettings: ClientSettings = {
sidebarThreadSortOrder: "created_at",
sidebarThreadPreviewCount: 6,
legacySidebarEnabled: false,
sidebarWorkingShelfEnabled: false,
loadBalancingEnabled: false,
loadBalancingWeights: { "environment-1": 75, "environment-2": 0 },
pullRequestMergeMethodOverrides: {},
Expand Down
4 changes: 0 additions & 4 deletions apps/mobile/src/features/cloud/cloudDebugLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ const logger = createDebugLogger("cloud", {
legacyGlobalFlag: "__T3_CLOUD_DEBUG__",
});

export function isCloudDebugEnabled(): boolean {
return logger.isEnabled();
}

export function cloudDebugLog(event: string, data?: Record<string, unknown>): void {
logger.log(event, data);
}
4 changes: 0 additions & 4 deletions apps/mobile/src/features/terminal/terminalDebugLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ const logger = createDebugLogger("terminal", {
legacyGlobalFlag: "__T3_TERMINAL_DEBUG__",
});

export function isTerminalDebugEnabled(): boolean {
return logger.isEnabled();
}

export function terminalDebugLog(message: string, data?: Record<string, unknown>): void {
logger.log(message, data);
}
49 changes: 46 additions & 3 deletions apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { MaterialListRow } from "../../components/MaterialListRow";
import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
import type { VcsRef } from "@t3tools/client-runtime/state/vcs";
import { resolveEnvironmentMachineKind } from "@t3tools/contracts";
import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts";
import * as Cause from "effect/Cause";
import { AsyncResult } from "effect/unstable/reactivity";
import { LegendList } from "@legendapp/list/react-native";
import {
isAtomCommandInterrupted,
Expand Down Expand Up @@ -29,7 +31,8 @@ import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSym
import { ThemedSwitch } from "../../components/ThemedSwitch";
import { cn } from "../../lib/cn";
import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { useServerConfigs } from "../../state/entities";
import { useServerConfigs, waitForProject } from "../../state/entities";
import { projectEnvironment } from "../../state/projects";
import { useAtomCommand } from "../../state/use-atom-command";
import { vcsEnvironment } from "../../state/vcs";
import {
Expand Down Expand Up @@ -204,6 +207,38 @@ export function NewTaskEnvironmentPickerRouteScreen() {
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const serverConfigs = useServerConfigs();
const ensureScratch = useAtomCommand(projectEnvironment.ensureScratch, {
reportFailure: false,
});
const [movingToEnvironmentId, setMovingToEnvironmentId] = useState<EnvironmentId | null>(null);

// A thread without a project moves to the other machine's own Scratch
// project, which is created there first if it does not exist yet.
async function moveScratchDraft(environmentId: EnvironmentId): Promise<void> {
setMovingToEnvironmentId(environmentId);
try {
const result = await ensureScratch({ environmentId, input: {} });
if (AsyncResult.isFailure(result)) {
const error = Cause.squash(result.cause);
Alert.alert(
"Could not switch machine",
error instanceof Error
? error.message
: "The folder for threads without a project could not be created.",
);
return;
}
const project = await waitForProject({ environmentId, projectId: result.value.projectId });
if (project === null) {
Alert.alert("Could not switch machine", "It has not reached this device yet. Try again.");
return;
}
flow.setProject(project);
navigation.goBack();
} finally {
setMovingToEnvironmentId(null);
}
}

return (
<View className="flex-1 bg-sheet" collapsable={false}>
Expand Down Expand Up @@ -244,9 +279,17 @@ export function NewTaskEnvironmentPickerRouteScreen() {
/>
}
isLast={index === flow.environments.length - 1}
disabled={movingToEnvironmentId !== null}
onPress={() => {
void Haptics.selectionAsync();
flow.selectEnvironment(environment.environmentId);
if (flow.isScratchDraft) {
if (environment.environmentId !== flow.selectedEnvironmentId) {
void moveScratchDraft(environment.environmentId);
return;
}
} else {
flow.selectEnvironment(environment.environmentId);
}
navigation.goBack();
}}
selected={flow.selectedEnvironmentId === environment.environmentId}
Expand Down
64 changes: 44 additions & 20 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,49 @@ export function NewTaskDraftScreen(props: {
navigation.dispatch(StackActions.push(routeName));
};

const hero = (
const environmentControl = (
<ComposerInlineControl
accessibilityLabel={`Environment: ${selectedEnvironmentLabel}`}
chevronDirection="right"
disabled={isComposerInteractionLocked || voiceInput.isBusy}
renderIcon={(size) => (
<EnvironmentMachineSymbol
kind={resolveEnvironmentMachineKind(selectedEnvironmentServerConfig)}
size={size}
tintColorClassName="accent-icon-muted"
/>
)}
label={`on ${selectedEnvironmentLabel}`}
maxWidth={flow.isScratchDraft ? 170 : 260}
onPress={
flow.environments.length > 1 ? () => openContextPicker("NewTaskEnvironment") : undefined
}
showChevron={flow.environments.length > 1}
static={flow.environments.length <= 1}
/>
);
// A thread without a project has no project to name, so it asks plainly,
// like web, and puts the project picker beside the machine as a control.
const hero = flow.isScratchDraft ? (
<View className="items-center gap-2 px-6" testID="new-task-hero">
<Text className="text-center text-2xl font-t3-medium tracking-tight text-foreground">
What should we work on?
</Text>
{/* Wraps onto two lines only when a long machine name leaves no room. */}
<View className="flex-row flex-wrap items-center justify-center gap-x-1">
<ComposerInlineControl
accessibilityHint="Opens the project picker"
accessibilityLabel="Choose a project"
chevronDirection="right"
disabled={isComposerInteractionLocked}
icon="folder"
label="Choose a project"
onPress={chooseProject}
/>
{environmentControl}
</View>
</View>
) : (
<View className="items-center gap-6 px-6" testID="new-task-hero">
<View className="w-full items-center gap-1.5">
<Text className="text-center text-2xl font-t3-medium tracking-tight text-foreground">
Expand All @@ -1486,25 +1528,7 @@ export function NewTaskDraftScreen(props: {
</View>
</View>

<ComposerInlineControl
accessibilityLabel={`Environment: ${selectedEnvironmentLabel}`}
chevronDirection="right"
disabled={isComposerInteractionLocked || voiceInput.isBusy}
renderIcon={(size) => (
<EnvironmentMachineSymbol
kind={resolveEnvironmentMachineKind(selectedEnvironmentServerConfig)}
size={size}
tintColorClassName="accent-icon-muted"
/>
)}
label={`on ${selectedEnvironmentLabel}`}
maxWidth={260}
onPress={
flow.environments.length > 1 ? () => openContextPicker("NewTaskEnvironment") : undefined
}
showChevron={flow.environments.length > 1}
static={flow.environments.length <= 1}
/>
{environmentControl}
</View>
);
const heroViewport = (
Expand Down
Loading
Loading