Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,13 @@ export function HomeScreen(props: HomeScreenProps) {
);

const v2ListEmpty =
hasSearchQuery && threadSearch.isPending ? undefined : hasSearchQuery ? (
hasSearchQuery && threadSearch.isPending ? (
<EmptyState
title="Searching thread messages…"
detail={`Looking for "${props.searchQuery}" in messages.`}
variant={Platform.OS === "android" ? "plain" : undefined}
/>
) : hasSearchQuery ? (
<EmptyState
title="No results"
detail={`No threads matching "${props.searchQuery}".`}
Expand Down
56 changes: 37 additions & 19 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FLOATING_TABS } from "../layout/workspace/floating-workspace-tabs";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AppText as Text } from "../../components/ui/AppText";
import { Button } from "../../components/ui/Button";
import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
import { useProjects, useNavigationThreadShells } from "../../state/entities";
import { useThreadSearch } from "../../state/queries";
Expand Down Expand Up @@ -552,26 +553,43 @@ function ThreadNavigationSidebarPane(props: ThreadNavigationSidebarProps) {
}}
/>
);
const showAddEnvironment =
!catalogState.isLoadingConnections &&
!catalogState.hasReadyEnvironment &&
!catalogState.hasConnectingEnvironment;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const listEmpty = (
<Text
className={
Platform.OS === "android"
? "px-4 py-4 text-center text-sm text-drawer-foreground-muted"
: "px-2 py-4 text-sm text-drawer-foreground-muted"
}
>
{catalogState.isLoadingConnections
? "Loading threads…"
: Platform.OS === "android" && !catalogState.hasConnections
? "No environments connected"
: props.searchQuery.trim().length > 0
? threadSearch.isPending
? "Searching thread messages…"
: "No matching threads"
: selectedProjectScope !== null
? `No threads in ${selectedProjectScope.title}`
: "No threads yet"}
</Text>
<View>
<Text
className={
Platform.OS === "android"
? "px-4 py-4 text-center text-sm text-drawer-foreground-muted"
: "px-2 py-4 text-sm text-drawer-foreground-muted"
}
>
{catalogState.isLoadingConnections
? "Loading threads…"
: showAddEnvironment
? "No environments connected"
: props.searchQuery.trim().length > 0
? threadSearch.isPending
? "Searching thread messages…"
: "No matching threads"
: selectedProjectScope !== null
? `No threads in ${selectedProjectScope.title}`
: "No threads yet"}
</Text>
{showAddEnvironment ? (
<View className={Platform.OS === "android" ? "self-center" : "px-2"}>
<Button
tone="primary"
size="compact"
icon="plus"
label="Add environment"
onPress={props.onOpenEnvironmentSettings}
/>
</View>
) : null}
</View>
);

if (Platform.OS === "ios") {
Expand Down
17 changes: 17 additions & 0 deletions packages/shared/src/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,23 @@ describe("external usage settings", () => {
expect(collectLimitAccounts(presentations)).toEqual([]);
expect(collectLimitNotices(presentations)).toEqual([]);
});

it("shows a notice once when environments with one label report the same failure", () => {
const failing = provider({
usageLimits: {
checkedAt: "2026-09-03T11:00:00.000Z",
windows: [],
unavailable: { reason: "probeFailed" },
},
});
const host = { entry: { target: { label: "Host" } }, serverConfig: { providers: [failing] } };
const presentations = new Map([
[EnvironmentId.make("a"), host],
[EnvironmentId.make("b"), host],
[EnvironmentId.make("c"), host],
]);
expect(collectLimitNotices(presentations)).toEqual(["Host · codex: Could not read limits."]);
});
});

describe("ChatGPT sharing presentation", () => {
Expand Down
10 changes: 5 additions & 5 deletions packages/shared/src/usageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,24 +392,24 @@ export function collectLimitAccounts(presentations: LimitPresentations): readonl
export function collectLimitNotices(presentations: LimitPresentations): readonly string[] {
const label = (environmentLabel: string, subject: string) =>
presentations.size > 1 ? `${environmentLabel} · ${subject}` : subject;
const notices: string[] = [];
const notices = new Set<string>();
for (const presentation of presentations.values()) {
const environmentLabel = presentation.entry.target.label;
for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) {
if (provider.usageLimits?.unavailable?.reason === "unsupported") continue;
const notice = provider.usageLimits ? limitsNotice(provider.usageLimits) : null;
const name = provider.displayName?.trim() || String(provider.driver);
if (notice) notices.push(`${label(environmentLabel, name)}: ${notice}`);
if (notice) notices.add(`${label(environmentLabel, name)}: ${notice}`);
}
for (const source of presentation.serverConfig?.usageLimitSources ?? []) {
if (source.error) {
notices.push(`${label(environmentLabel, source.label)}: ${source.error}`);
notices.add(`${label(environmentLabel, source.label)}: ${source.error}`);
} else if (source.accounts.length === 0) {
notices.push(`${label(environmentLabel, source.label)}: No accounts reported.`);
notices.add(`${label(environmentLabel, source.label)}: No accounts reported.`);
}
}
}
return notices;
return [...notices];
}

export interface LimitPoolMember {
Expand Down
Loading