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
78a0ea5
feat(web): copy branch name via right-click in the branch selector (#…
t3dotgg Jul 22, 2026
ab4a883
Add remote server updates and standalone service management (#4286)
juliusmarminge Jul 22, 2026
593289c
Refine light-mode sidebar surfaces (#4268)
juliusmarminge Jul 22, 2026
bc9428a
fix(mobile): don't mark Android VPN/Tailscale as offline when connect…
Wraient Jul 22, 2026
2d31cb0
improve and prevent silent thread branch drift and PR fetching (#2284)
justsomelegs Jul 22, 2026
c5ff51e
feat(web): refresh application surfaces
maria-rcks Jul 22, 2026
b6a2563
style(web): polish dark mode dialogs
maria-rcks Jul 22, 2026
14b6bfd
fix(web): unify and tune glass surfaces
juliusmarminge Jul 23, 2026
29b1abc
fix(web): apply glass opacity to thread tooltip
maria-rcks Jul 23, 2026
6e5df67
fix(web): override thread tooltip background
maria-rcks Jul 23, 2026
10da67b
fix(web): apply glass opacity to model picker
maria-rcks Jul 23, 2026
0b1ce58
style(web): polish glass opacity slider
maria-rcks Jul 23, 2026
5961d36
fix(web): apply glass opacity to command palette
maria-rcks Jul 23, 2026
d330759
fix(web): add glass composer alerts
maria-rcks Jul 23, 2026
160d97f
fix(web): honor composer glass opacity
maria-rcks Jul 23, 2026
39cd15b
test(web): stabilize timeline module setup
maria-rcks Jul 23, 2026
c38225e
style(web): fade settings content beneath navbar
maria-rcks Jul 23, 2026
936394b
test(desktop): include glass opacity in settings fixture
maria-rcks Jul 23, 2026
9c9916a
fix(web): address redesign review findings
maria-rcks Jul 23, 2026
b44ed83
fix(web): sync provider banner dismissal
maria-rcks Jul 23, 2026
432f6fe
sync upstream main
tarik02 Jul 23, 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ There's no public docs site yet, checkout the miscellaneous markdown files in [d
## Documentation

- [Getting started](./docs/getting-started/quick-start.md)
- [Remote access](./docs/user/remote-access.md)
- [Keeping T3 Code in sync](./docs/user/server-updates.md)
- [Architecture overview](./docs/architecture/overview.md)
- [Provider guides](./docs/providers/codex.md)
- [Operations](./docs/operations/ci.md)
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 @@ -20,6 +20,7 @@ const clientSettings: ClientSettings = {
dismissedProviderUpdateNotificationKeys: [],
diffIgnoreWhitespace: true,
favorites: [],
glassOpacity: 80,
providerModelPreferences: {},
sidebarAutoSettleAfterDays: 3,
sidebarProjectGroupingMode: "repository_path",
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/connection/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts";
import { connectionStorageLayer } from "./storage";

function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "online" {
if (state.isConnected === false || state.isInternetReachable === false) {
if (state.isConnected === false) {
return "offline";
}
if (state.isConnected === true) {
Expand Down
149 changes: 119 additions & 30 deletions apps/mobile/src/features/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {
SidebarThreadSortOrder,
} from "@t3tools/contracts";
import type { MenuAction } from "@react-native-menu/menu";
import { useAtomValue } from "@effect/atom-react";
import { AsyncResult } from "effect/unstable/reactivity";
import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { useCallback, useMemo, useRef } from "react";
import { Platform, Pressable, Text as RNText, TextInput, View } from "react-native";
Expand All @@ -14,13 +16,15 @@ import { ControlPillMenu } from "../../components/ControlPill";
import { SymbolView } from "../../components/AppSymbol";
import { T3Wordmark } from "../../components/T3Wordmark";
import { useThemeColor } from "../../lib/useThemeColor";
import { mobilePreferencesAtom } from "../../state/preferences";
import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items";
import { createNativeMailSearchToolbarItem } from "../layout/native-mail-search-toolbar";
import type { HomeProjectSortOrder } from "./homeThreadList";
import {
buildHomeListFilterMenu,
type HomeListFilterMenuEnvironment,
type HomeListFilterMenuProject,
} from "./home-list-filter-menu";
import {
hasCustomHomeListOptions,
Expand All @@ -33,13 +37,16 @@ export type HomeHeaderEnvironment = HomeListFilterMenuEnvironment;

export function HomeHeader(props: {
readonly environments: ReadonlyArray<HomeHeaderEnvironment>;
readonly projects: ReadonlyArray<HomeListFilterMenuProject>;
readonly searchQuery: string;
readonly selectedEnvironmentId: EnvironmentId | null;
readonly selectedProjectKey: string | null;
readonly projectSortOrder: HomeProjectSortOrder;
readonly threadSortOrder: SidebarThreadSortOrder;
readonly projectGroupingMode: SidebarProjectGroupingMode;
readonly onSearchQueryChange: (query: string) => void;
readonly onEnvironmentChange: (environmentId: EnvironmentId | null) => void;
readonly onProjectChange: (projectKey: string | null) => void;
readonly onProjectSortOrderChange: (sortOrder: HomeProjectSortOrder) => void;
readonly onThreadSortOrderChange: (sortOrder: SidebarThreadSortOrder) => void;
readonly onProjectGroupingModeChange: (mode: SidebarProjectGroupingMode) => void;
Expand All @@ -59,11 +66,24 @@ function checkedMenuState(checked: boolean) {
return checked ? ("on" as const) : undefined;
}

/** Thread List v2 lays the list out in fixed creation order, so the
sort/group filter controls would be silently ignored — hide them and
key the "customized" icon state off the environment filter alone. */
function useThreadListV2FilterGate() {
const preferencesResult = useAtomValue(mobilePreferencesAtom);
return (
AsyncResult.isSuccess(preferencesResult) && preferencesResult.value.threadListV2Enabled === true
);
}

function AndroidHomeHeader(props: HomeHeaderProps) {
const insets = useSafeAreaInsets();
const iconColor = useThemeColor("--color-icon");
const mutedColor = useThemeColor("--color-foreground-muted");
const hasCustomListOptions = hasCustomHomeListOptions(props);
const threadListV2Enabled = useThreadListV2FilterGate();
const hasCustomListOptions = threadListV2Enabled
? props.selectedEnvironmentId !== null || props.selectedProjectKey !== null
: hasCustomHomeListOptions(props);
const menuActions = useMemo<MenuAction[]>(
() => [
{
Expand All @@ -82,40 +102,67 @@ function AndroidHomeHeader(props: HomeHeaderProps) {
})),
],
},
{
id: "project-sort",
title: "Sort projects",
subactions: PROJECT_SORT_OPTIONS.map((option) => ({
id: `project-sort:${option.value}`,
title: option.label,
state: checkedMenuState(props.projectSortOrder === option.value),
})),
},
{
id: "thread-sort",
title: "Sort threads",
subactions: THREAD_SORT_OPTIONS.map((option) => ({
id: `thread-sort:${option.value}`,
title: option.label,
state: checkedMenuState(props.threadSortOrder === option.value),
})),
},
{
id: "project-grouping",
title: "Group projects",
subactions: PROJECT_GROUPING_OPTIONS.map((option) => ({
id: `project-grouping:${option.value}`,
title: option.label,
state: checkedMenuState(props.projectGroupingMode === option.value),
})),
},
...(props.projects.length === 0
? []
: ([
{
id: "project",
title: "Project",
subactions: [
{
id: "project:all",
title: "All projects",
state: checkedMenuState(props.selectedProjectKey === null),
},
...props.projects.map((project) => ({
id: `project:${project.key}`,
title: project.label,
state: checkedMenuState(props.selectedProjectKey === project.key),
})),
],
},
] satisfies MenuAction[])),
...(threadListV2Enabled
? []
: ([
{
id: "project-sort",
title: "Sort projects",
subactions: PROJECT_SORT_OPTIONS.map((option) => ({
id: `project-sort:${option.value}`,
title: option.label,
state: checkedMenuState(props.projectSortOrder === option.value),
})),
},
{
id: "thread-sort",
title: "Sort threads",
subactions: THREAD_SORT_OPTIONS.map((option) => ({
id: `thread-sort:${option.value}`,
title: option.label,
state: checkedMenuState(props.threadSortOrder === option.value),
})),
},
{
id: "project-grouping",
title: "Group projects",
subactions: PROJECT_GROUPING_OPTIONS.map((option) => ({
id: `project-grouping:${option.value}`,
title: option.label,
state: checkedMenuState(props.projectGroupingMode === option.value),
})),
},
] satisfies MenuAction[])),
],
[
props.environments,
props.projectGroupingMode,
props.projectSortOrder,
props.projects,
props.selectedEnvironmentId,
props.selectedProjectKey,
props.threadSortOrder,
threadListV2Enabled,
],
);
const handleMenuAction = useCallback(
Expand All @@ -137,6 +184,19 @@ function AndroidHomeHeader(props: HomeHeaderProps) {
return;
}

if (id === "project:all") {
props.onProjectChange(null);
return;
}

if (id.startsWith("project:")) {
const projectKey = id.slice("project:".length);
if (props.projects.some((project) => project.key === projectKey)) {
props.onProjectChange(projectKey);
}
return;
}

const projectSort = PROJECT_SORT_OPTIONS.find(
(option) => id === `project-sort:${option.value}`,
);
Expand Down Expand Up @@ -255,17 +315,24 @@ function AndroidHomeHeader(props: HomeHeaderProps) {
function IosHomeHeader(props: HomeHeaderProps) {
const searchBarRef = useRef<SearchBarCommands>(null);
const iconColor = useThemeColor("--color-icon");
const hasCustomListOptions = hasCustomHomeListOptions(props);
const threadListV2Enabled = useThreadListV2FilterGate();
const hasCustomListOptions = threadListV2Enabled
? props.selectedEnvironmentId !== null || props.selectedProjectKey !== null
: hasCustomHomeListOptions(props);
const focusSearch = useCallback(() => {
searchBarRef.current?.focus();
return searchBarRef.current !== null;
}, []);
useHardwareKeyboardCommand("focusSearch", focusSearch);
const filterMenu = buildHomeListFilterMenu(props);
const filterMenu = buildHomeListFilterMenu({
...props,
listOrganization: !threadListV2Enabled,
});

return (
<>
<NativeStackScreenOptions
optionsVersion={filterMenu.items}
options={{
// Static header config (glass, title, fonts) lives in Stack.tsx
// (GLASS_HEADER_OPTIONS). Only dynamic values are set here.
Expand Down Expand Up @@ -366,6 +433,28 @@ function IosHomeHeader(props: HomeHeaderProps) {
))}
</NativeHeaderToolbar.Menu>

{props.projects.length > 0 ? (
<NativeHeaderToolbar.Menu title="Project">
<NativeHeaderToolbar.Label>Project</NativeHeaderToolbar.Label>
<NativeHeaderToolbar.MenuAction
isOn={props.selectedProjectKey === null}
onPress={() => props.onProjectChange(null)}
subtitle="Show threads from every project"
>
<NativeHeaderToolbar.Label>All projects</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
{props.projects.map((project) => (
<NativeHeaderToolbar.MenuAction
key={project.key}
isOn={props.selectedProjectKey === project.key}
onPress={() => props.onProjectChange(project.key)}
>
<NativeHeaderToolbar.Label>{project.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
) : null}

<NativeHeaderToolbar.Menu title="Sort projects">
<NativeHeaderToolbar.Label>Sort projects</NativeHeaderToolbar.Label>
{PROJECT_SORT_OPTIONS.map((option) => (
Expand Down
30 changes: 29 additions & 1 deletion apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as Arr from "effect/Array";
import * as Order from "effect/Order";
import { useNavigation } from "@react-navigation/native";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";

import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { scopedProjectKey } from "../../lib/scopedEntities";
import { useProjects, useThreadShells } from "../../state/entities";
import { usePendingNewTasks } from "../../state/use-pending-new-tasks";
import { useWorkspaceState } from "../../state/workspace";
Expand Down Expand Up @@ -58,6 +59,28 @@ export function HomeRouteScreen() {
setThreadSortOrder,
} = useHomeListOptions(availableEnvironmentIds);
const selectedEnvironmentId = listOptions.selectedEnvironmentId;
const [selectedProjectKey, setSelectedProjectKey] = useState<string | null>(null);
const projectFilterOptions = useMemo(
() =>
projects
.filter(
(project) =>
selectedEnvironmentId === null || project.environmentId === selectedEnvironmentId,
)
.map((project) => ({
key: scopedProjectKey(project.environmentId, project.id),
label: project.title,
})),
[projects, selectedEnvironmentId],
);
useEffect(() => {
if (
selectedProjectKey !== null &&
!projectFilterOptions.some((project) => project.key === selectedProjectKey)
) {
setSelectedProjectKey(null);
}
}, [projectFilterOptions, selectedProjectKey]);

// In split layouts the persistent sidebar IS the thread list — Home becomes
// an empty detail pane so selecting a thread never transitions layouts.
Expand Down Expand Up @@ -90,12 +113,15 @@ export function HomeRouteScreen() {
<NativeStackScreenOptions options={{ title: "Threads", headerTitle: "Threads" }} />
<HomeHeader
environments={environments}
projects={projectFilterOptions}
searchQuery={searchQuery}
selectedEnvironmentId={selectedEnvironmentId}
selectedProjectKey={selectedProjectKey}
projectSortOrder={listOptions.projectSortOrder}
threadSortOrder={listOptions.threadSortOrder}
projectGroupingMode={listOptions.projectGroupingMode}
onEnvironmentChange={setSelectedEnvironmentId}
onProjectChange={setSelectedProjectKey}
onOpenSettings={() => navigation.navigate("SettingsSheet", { screen: "Settings" })}
onProjectGroupingModeChange={setProjectGroupingMode}
onProjectSortOrderChange={setProjectSortOrder}
Expand All @@ -115,6 +141,7 @@ export function HomeRouteScreen() {
onSettleThread={settleThread}
onUnsettleThread={unsettleThread}
onEnvironmentChange={setSelectedEnvironmentId}
onProjectChange={setSelectedProjectKey}
onOpenEnvironments={() =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" })
}
Expand Down Expand Up @@ -151,6 +178,7 @@ export function HomeRouteScreen() {
savedConnectionsById={savedConnectionsById}
searchQuery={searchQuery}
selectedEnvironmentId={selectedEnvironmentId}
selectedProjectKey={selectedProjectKey}
threads={threads}
threadSortOrder={listOptions.threadSortOrder}
/>
Expand Down
Loading
Loading