Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
476590d
feat(desktop): back/forward buttons and trackpad swipe navigation (#2…
juliusmarminge Oct 30, 2026
5ef5685
feat(web): review local commits in the Diff pane (#19941)
Bil0000 Oct 30, 2026
305a90d
fix(relay): show the account's real tunnel cap in T3 Connect settings…
juliusmarminge Oct 30, 2026
5cbc207
fix(web): streaming replies no longer re-render every visible timelin…
juliusmarminge Oct 30, 2026
d8c926c
perf(web): streaming replies re-render only their last Markdown block…
juliusmarminge Oct 30, 2026
aaee774
perf(web): the git actions panel no longer re-renders on every stream…
juliusmarminge Oct 30, 2026
3cbc982
perf(web): streamed assistant text re-renders only its own row (#27419)
juliusmarminge Oct 30, 2026
32ecd71
fix(server): `t3 service protect` can ask for your sudo password (#27…
juliusmarminge Oct 30, 2026
0f6de8c
fix(web): preserve Ctrl+Arrow terminal key sequences (#23480)
UtkarshUsername Oct 30, 2026
d1f1650
fix(web): keep PowerShell prompt paths with spaces in one link (#23527)
UtkarshUsername Oct 30, 2026
8cb9182
perf(client): find unread terminal chunks from the tail (#23779)
UtkarshUsername Oct 30, 2026
6575ce3
fix(pi): explain when Pi's default model has no credentials (#27513)
StiensWout Oct 30, 2026
616c426
fix(shared): TypeScript files no longer show as videos (#27372)
maria-rcks Oct 30, 2026
e4ecaf6
feat(mobile): message media, file cards and grouped steers match web …
maria-rcks Oct 30, 2026
f012fa2
fix(web): back-to-back messages from you stack as one burst with a si…
maria-rcks Oct 30, 2026
afb8619
fix(pi): clear Pi's native queue before Stop aborts (#27514)
StiensWout Oct 30, 2026
9479168
feat(pi): import terminal Pi sessions during setup (#27515)
StiensWout Oct 30, 2026
77336a5
fix(pi): nest tools called through another tool under their caller (#…
StiensWout Oct 30, 2026
e00ebb6
fix(pi): explain when an untrusted folder makes Pi skip project resou…
StiensWout Oct 30, 2026
a077fdd
fix(mobile): Android Thread settings rows no longer vanish with anima…
shivamhwp Oct 30, 2026
35dcdd6
fix(server): phone taps, typing and scrolling reach desktop browser t…
shivamhwp Oct 30, 2026
b8397e5
feat(web): images and videos sent with text sit above the bubble as t…
maria-rcks Oct 30, 2026
1da41f9
feat(web): files sent without text show as file cards (#27324)
maria-rcks Oct 30, 2026
e57ae5a
feat(web): pasted text, voice notes, snapshots and sending files get …
maria-rcks Oct 30, 2026
78fba05
perf(web): revisiting a thread reuses its rendered Markdown instead o…
juliusmarminge Oct 30, 2026
5b357b3
perf(web): chat Markdown parses about twice as fast with the same out…
juliusmarminge Oct 30, 2026
33d27e9
fix(server): Pull Requests says which GitHub credential cannot see a …
shivamhwp Oct 30, 2026
028ed85
fix(server): Codex generated images show in chat, including on Androi…
shivamhwp Oct 30, 2026
8d012a6
fix(web): a new thread's first reply shows without reloading (#27522)
juliusmarminge Oct 30, 2026
6fc9144
fix: image-only messages no longer leak the attachment placeholder (#…
maria-rcks Oct 30, 2026
ae521d1
fix(ci): keep desktop launch fixtures from auto-settling
juliusmarminge Oct 30, 2026
e367b65
Merge tag 'v0.1.6-nightly.20261030.3940' into custom-nightly
chamaquita-d3[bot] Oct 30, 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
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const SET_DICTATION_KEY_ENABLED_CHANNEL = "desktop:set-dictation-key-enab
export const DICTATION_KEY_CHANNEL = "desktop:dictation-key";
export const READ_FN_KEY_SETUP_CHANNEL = "desktop:read-fn-key-setup";
export const APPLY_FN_KEY_SETUP_CHANNEL = "desktop:apply-fn-key-setup";
export const TRACKPAD_SCROLL_BEGIN_CHANNEL = "desktop:trackpad-scroll-begin";
export const TRACKPAD_SCROLL_END_CHANNEL = "desktop:trackpad-scroll-end";
export const GET_WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:get-window-fullscreen-state";
export const WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:window-fullscreen-state";
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ contextBridge.exposeInMainWorld("desktopBridge", {
return () => ipcRenderer.removeListener(IpcChannels.SET_NOTIFICATION_BADGE_CHANNEL, handler);
},
setDockMood: (mood) => ipcRenderer.invoke(IpcChannels.SET_DOCK_MOOD_CHANNEL, mood),
onTrackpadScrollBegin: (listener) => {
const handler = () => listener();
ipcRenderer.on(IpcChannels.TRACKPAD_SCROLL_BEGIN_CHANNEL, handler);
return () => ipcRenderer.removeListener(IpcChannels.TRACKPAD_SCROLL_BEGIN_CHANNEL, handler);
},
onTrackpadScrollEnd: (listener) => {
const handler = () => listener();
ipcRenderer.on(IpcChannels.TRACKPAD_SCROLL_END_CHANNEL, handler);
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/window/DesktopWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
MENU_ACTION_CHANNEL,
QUIT_SHORTCUT_CHANNEL,
SNAP_SHOT_EVENT_CHANNEL,
TRACKPAD_SCROLL_BEGIN_CHANNEL,
TRACKPAD_SCROLL_END_CHANNEL,
WINDOW_FULLSCREEN_STATE_CHANNEL,
} from "../ipc/channels.ts";
Expand Down Expand Up @@ -652,7 +653,11 @@ export const make = Effect.gen(function* () {
}
});
window.webContents.on("input-event", (_event, input) => {
if (input.type === "gestureScrollEnd") window.webContents.send(TRACKPAD_SCROLL_END_CHANNEL);
if (input.type === "gestureScrollBegin") {
window.webContents.send(TRACKPAD_SCROLL_BEGIN_CHANNEL);
} else if (input.type === "gestureScrollEnd") {
window.webContents.send(TRACKPAD_SCROLL_END_CHANNEL);
}
});
// Ctrl+wheel bypasses menus; guest contents emit their own zoom-changed event.
window.webContents.on("zoom-changed", (_event, direction) => {
Expand Down
6 changes: 5 additions & 1 deletion apps/mobile/src/components/MediaVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ interface MediaVideoPlayerProps {
readonly paused?: boolean;
readonly onPlaybackStart?: () => void;
readonly actionsSource?: MediaActionsSource;
readonly borderRadius?: number;
}

function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {
Expand All @@ -152,7 +153,10 @@ function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {
<View
collapsable={false}
className="overflow-hidden rounded-[10px] bg-black"
style={props.expanded ? { flex: 1 } : { width: "100%", maxWidth: 480, aspectRatio: 16 / 9 }}
style={[
props.expanded ? { flex: 1 } : { width: "100%", maxWidth: 480, aspectRatio: 16 / 9 },
props.borderRadius === undefined ? null : { borderRadius: props.borderRadius },
]}
>
{playbackUri ? (
<LoadedMediaVideo
Expand Down
64 changes: 39 additions & 25 deletions apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useAtomValue } from "@effect/atom-react";
import {
Keyboard,
Platform,
Pressable,
RefreshControl,
ScrollView,
View,
Expand All @@ -20,6 +21,7 @@ import { NativeScrollViewMarker as ScrollViewMarker } from "../../native/NativeS
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AppText as Text } from "../../components/ui/AppText";
import { SymbolView } from "../../components/ui/AppSymbol";
import { EmptyState } from "../../components/ui/EmptyState";
import { NativePrimaryColumnContext } from "../../native/v5-workspace-context";
import { useNativeColumnLayoutMetrics } from "../../native/native-layout-metrics";
Expand All @@ -32,7 +34,6 @@ import {
usePullRequestListFilters,
type PullRequestFilterMenu,
} from "./PullRequestListFilters";
import { PullRequestCardRow } from "./PullRequestControls";
import { usePullRequestList } from "./usePullRequestList";
import { usePullRequestListControls } from "./usePullRequestListControls";
import { usePullRequestListActions } from "./usePullRequestListActions";
Expand All @@ -58,26 +59,38 @@ function PullRequestScrollView(props: ScrollViewProps) {
);
}

function PullRequestListErrors(props: { readonly messages: readonly string[] }) {
function PullRequestListError(props: { readonly message: string }) {
const [expanded, setExpanded] = useState(false);
const title = `${props.messages.length} ${props.messages.length === 1 ? "error" : "errors"} loading pull requests`;
return (
<View>
<PullRequestCardRow
title={title}
icon="exclamationmark.triangle"
compact
last
expanded={expanded}
accessibilityLabel={`${title}. ${expanded ? "Hide" : "Show"} details`}
onPress={() => setExpanded((value) => !value)}
<Pressable
accessibilityRole="button"
accessibilityLabel={props.message}
accessibilityState={{ expanded }}
onPress={() => setExpanded((value) => !value)}
// A long press selects the text; collapsing under that selection crashes Android's
// smart-select animation, so it must not also count as a press.
onLongPress={() => undefined}
className="min-h-11 flex-row items-center gap-3 px-4 py-2.5 active:opacity-70"
>
<SymbolView
name="exclamationmark.triangle"
size={18}
tintColorClassName="accent-icon-muted"
/>
{expanded ? (
<Text selectable className="px-4 pb-3 text-sm text-foreground-muted">
{props.messages.join("\n")}
</Text>
) : null}
</View>
<Text
selectable={expanded}
numberOfLines={expanded ? undefined : 1}
ellipsizeMode="tail"
className="min-w-0 flex-1 text-sm text-foreground-muted"
>
{props.message}
</Text>
<SymbolView
name={expanded ? "chevron.up" : "chevron.down"}
size={11}
tintColorClassName="accent-icon-muted"
/>
</Pressable>
);
}

Expand Down Expand Up @@ -334,13 +347,14 @@ export function PullRequestsScreen(
<>
<PullRequestListFilters sidebar={sidebar} filters={filters} />
{result.data?.errors.length ? (
<PullRequestListErrors
key={JSON.stringify(targets)}
messages={result.data.errors.map(
(error) =>
`${error.projectTitle || environments.find((environment) => environment.environmentId === error.environmentId)?.environmentLabel || "Environment"}: ${error.message}`,
)}
/>
<View key={JSON.stringify(targets)}>
{result.data.errors.map((error) => (
<PullRequestListError
key={`${error.environmentId} ${error.projectTitle} ${error.message}`}
message={`${error.projectTitle || environments.find((environment) => environment.environmentId === error.environmentId)?.environmentLabel || "Environment"}: ${error.message}`}
/>
))}
</View>
) : null}
</>
}
Expand Down
27 changes: 16 additions & 11 deletions apps/mobile/src/features/review/reviewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,17 +534,22 @@ export function buildReviewSectionItems(input: {
},
);

const gitItems = input.gitSections.map<ReviewSectionItem>((section) => ({
id: `git:${section.kind}`,
kind: section.kind,
title: section.title,
subtitle: gitSubtitle(section),
diff: section.diff,
source: section,
...(section.files ? { files: section.files } : {}),
truncated: section.truncated,
isLoading: false,
}));
const gitItems = input.gitSections
.filter(
(section): section is typeof section & { kind: ReviewSectionKind } =>
section.kind !== "commit",
)
.map<ReviewSectionItem>((section) => ({
id: `git:${section.kind}`,
kind: section.kind,
title: section.title,
subtitle: gitSubtitle(section),
diff: section.diff,
source: section,
...(section.files ? { files: section.files } : {}),
truncated: section.truncated,
isLoading: false,
}));
const hasChangesItem = gitItems.some((item) => item.id === CHANGES_SECTION_ID);
const visibleGitItems =
input.loadingGitSections && !hasChangesItem
Expand Down
Loading
Loading