Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 10 additions & 11 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useNavigation } from "@react-navigation/native";
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 All @@ -16,6 +15,7 @@ import { AndroidHomeFabLayout } from "./AndroidHomeFab";
import { HomeScreen } from "./HomeScreen";
import { HomeHeader } from "./HomeHeader";
import { useHomeListOptions } from "./home-list-options";
import { buildHomeProjectScopes } from "./homeThreadList";
import { usePendingTaskListActions } from "./usePendingTaskListActions";
import { useThreadListActions } from "./useThreadListActions";

Expand Down Expand Up @@ -61,16 +61,15 @@ export function HomeRouteScreen() {
const [selectedProjectKey, setSelectedProjectKey] = useState<string | null>(null);
const projectFilterOptions = useMemo(
Comment thread
juliusmarminge marked this conversation as resolved.
() =>
projects
.filter(
(project) =>
selectedEnvironmentId === null || project.environmentId === selectedEnvironmentId,
)
.map((project) => ({
key: scopedProjectKey(project.environmentId, project.id),
label: project.title,
})),
[projects, selectedEnvironmentId],
buildHomeProjectScopes({
projects,
environmentId: selectedEnvironmentId,
projectGroupingMode: listOptions.projectGroupingMode,
}).map((scope) => ({
key: scope.key,
label: scope.title,
})),
[listOptions.projectGroupingMode, projects, selectedEnvironmentId],
);
useEffect(() => {
if (
Expand Down
99 changes: 70 additions & 29 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,43 +245,69 @@ export function HomeScreen(props: HomeScreenProps) {
onScrollBeginDrag: handleScrollBeginDrag,
});

const scopedProject = useMemo(
const projectScopes = useMemo(
() =>
buildHomeProjectScopes({
projects: props.projects,
environmentId: props.selectedEnvironmentId,
projectGroupingMode: props.projectGroupingMode,
}),
[props.projectGroupingMode, props.projects, props.selectedEnvironmentId],
);
const selectedProjectScope = useMemo(
() =>
props.selectedProjectKey === null
? null
: (props.projects.find(
(project) =>
scopedProjectKey(project.environmentId, project.id) === props.selectedProjectKey &&
(props.selectedEnvironmentId === null ||
project.environmentId === props.selectedEnvironmentId),
: (projectScopes.find(
(scope) =>
scope.key === props.selectedProjectKey ||
scope.projectRefs.some(
(projectRef) =>
scopedProjectKey(projectRef.environmentId, projectRef.projectId) ===
props.selectedProjectKey,
),
) ?? null),
[props.projects, props.selectedEnvironmentId, props.selectedProjectKey],
[projectScopes, props.selectedProjectKey],
);
const selectedProjectRefKeys = useMemo(
() =>
selectedProjectScope === null
? null
: new Set(
selectedProjectScope.projectRefs.map((projectRef) =>
scopedProjectKey(projectRef.environmentId, projectRef.projectId),
),
),
[selectedProjectScope],
);
const scopedProjects = useMemo(
() => (scopedProject === null ? props.projects : [scopedProject]),
[props.projects, scopedProject],
() =>
selectedProjectRefKeys === null
? props.projects
: props.projects.filter((project) =>
selectedProjectRefKeys.has(scopedProjectKey(project.environmentId, project.id)),
),
[props.projects, selectedProjectRefKeys],
);
const scopedThreads = useMemo(
() =>
scopedProject === null
selectedProjectRefKeys === null
? props.threads
: props.threads.filter(
(thread) =>
thread.environmentId === scopedProject.environmentId &&
thread.projectId === scopedProject.id,
: props.threads.filter((thread) =>
selectedProjectRefKeys.has(scopedProjectKey(thread.environmentId, thread.projectId)),
),
[props.threads, scopedProject],
[props.threads, selectedProjectRefKeys],
);
const scopedPendingTasks = useMemo(
() =>
scopedProject === null
selectedProjectRefKeys === null
? props.pendingTasks
: props.pendingTasks.filter(
(pendingTask) =>
pendingTask.message.environmentId === scopedProject.environmentId &&
pendingTask.creation.projectId === scopedProject.id,
: props.pendingTasks.filter((pendingTask) =>
selectedProjectRefKeys.has(
scopedProjectKey(pendingTask.message.environmentId, pendingTask.creation.projectId),
),
),
[props.pendingTasks, scopedProject],
[props.pendingTasks, selectedProjectRefKeys],
);

const projectGroups = useMemo(
Expand Down Expand Up @@ -339,22 +365,18 @@ export function HomeScreen(props: HomeScreenProps) {
const v2ScopeProjects = useMemo(
() =>
sortHomeProjectScopes({
scopes: buildHomeProjectScopes({
projects: props.projects,
environmentId: props.selectedEnvironmentId,
projectGroupingMode: props.projectGroupingMode,
}),
scopes: projectScopes,
threads: props.threads,
pendingTasks: props.pendingTasks,
projectSortOrder: props.projectSortOrder,
}),
[
props.pendingTasks,
props.projectGroupingMode,
props.projects,
props.projectSortOrder,
props.selectedEnvironmentId,
props.threads,
projectScopes,
],
);
const v2ScopedProjectGroup = useMemo(
Expand All @@ -372,6 +394,21 @@ export function HomeScreen(props: HomeScreenProps) {
) ?? null),
[v2ProjectScopeKey, v2ScopeProjects],
);
const v2ProjectTitleByProjectKey = useMemo(
Comment thread
juliusmarminge marked this conversation as resolved.
() =>
new Map(
v2ScopeProjects.flatMap((scope) =>
scope.projectRefs.map(
(projectRef) =>
[
scopedProjectKey(projectRef.environmentId, projectRef.projectId),
scope.title,
] as const,
),
),
),
[v2ScopeProjects],
);
const v2ScopedProjectKeys = useMemo(
() =>
v2ScopedProjectGroup === null
Expand Down Expand Up @@ -493,6 +530,9 @@ export function HomeScreen(props: HomeScreenProps) {
projectByKey.get(scopedProjectKey(item.thread.environmentId, item.thread.projectId)) ??
null
}
projectTitle={v2ProjectTitleByProjectKey.get(
scopedProjectKey(item.thread.environmentId, item.thread.projectId),
)}
providerDriver={
serverConfigs
.get(item.thread.environmentId)
Expand Down Expand Up @@ -536,6 +576,7 @@ export function HomeScreen(props: HomeScreenProps) {
props.savedConnectionsById,
serverConfigs,
settlementEnvironmentIds,
v2ProjectTitleByProjectKey,
],
);
const v2KeyExtractor = useCallback(
Expand Down Expand Up @@ -755,9 +796,9 @@ export function HomeScreen(props: HomeScreenProps) {
const listEmpty = !hasResults ? (
hasSearchQuery ? (
<EmptyState title="No results" detail={`No threads matching "${props.searchQuery}".`} />
) : scopedProject !== null ? (
) : selectedProjectScope !== null ? (
<EmptyState
title={`No threads in ${scopedProject.title}`}
title={`No threads in ${selectedProjectScope.title}`}
detail="Choose another project or create a new task."
/>
) : selectedEnvironmentLabel ? (
Expand Down
38 changes: 38 additions & 0 deletions apps/mobile/src/features/home/homeThreadList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ describe("buildHomeThreadGroups", () => {
});

expect(scopes).toHaveLength(1);
expect(scopes[0]?.title).toBe("t3code");
expect(scopes[0]?.projects).toEqual(projects);
expect(scopes[0]?.projectRefs).toEqual(
projects.map((project) => ({
Expand Down Expand Up @@ -373,6 +374,43 @@ describe("buildHomeThreadGroups", () => {
).toHaveLength(2);
});

it("uses the repository label for a singleton repository scope", () => {
const project = makeProject({
environmentId: EnvironmentId.make("environment-1"),
id: ProjectId.make("project-1"),
title: "local-worktree-name",
repositoryIdentity: {
canonicalKey: "github.com/pingdotgg/t3code",
displayName: "codething-mvp",
locator: {
source: "git-remote" as const,
remoteName: "origin",
remoteUrl: "git@github.com:pingdotgg/t3code.git",
},
},
});

const scopes = buildHomeProjectScopes({
projects: [project],
environmentId: null,
projectGroupingMode: "repository",
});
const groups = buildGroups(
[project],
[
makeThread({
environmentId: project.environmentId,
id: ThreadId.make("thread-1"),
projectId: project.id,
title: "Thread",
}),
],
);

expect(scopes[0]?.title).toBe("codething-mvp");
expect(groups[0]?.title).toBe("codething-mvp");
});

it("sorts the newest thread first regardless of snapshot order", () => {
const environmentId = EnvironmentId.make("environment-1");
const project = makeProject({
Expand Down
10 changes: 2 additions & 8 deletions apps/mobile/src/features/home/homeThreadList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,7 @@ export function buildHomeProjectScopes(input: {
const representative = projects[0]!;
return {
key,
title:
projects.length > 1
? deriveProjectGroupLabel({ representative, members: projects })
: representative.title,
Comment thread
juliusmarminge marked this conversation as resolved.
title: deriveProjectGroupLabel({ representative, members: projects }),
representative,
projects,
projectRefs: projectRefsByGroup.get(key) ?? [],
Expand Down Expand Up @@ -346,10 +343,7 @@ export function buildHomeThreadGroups(input: {
continue;
}

const title =
group.projects.length > 1
? deriveProjectGroupLabel({ representative, members: group.projects })
: representative.title;
const title = deriveProjectGroupLabel({ representative, members: group.projects });
const groupMatches =
query.length === 0 ||
title.toLocaleLowerCase().includes(query) ||
Expand Down
17 changes: 17 additions & 0 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,21 @@ function ThreadNavigationSidebarPane(
})),
[projectScopes],
);
const projectTitleByProjectKey = useMemo(
() =>
new Map(
projectScopes.flatMap((scope) =>
scope.projectRefs.map(
(projectRef) =>
[
scopedProjectKey(projectRef.environmentId, projectRef.projectId),
scope.title,
] as const,
),
),
),
[projectScopes],
);
const selectedProjectScope = useMemo(
() =>
selectedProjectKey === null
Expand Down Expand Up @@ -728,6 +743,7 @@ function ThreadNavigationSidebarPane(
variant={item.item.variant}
showSettledDivider={item.item.showSettledDivider}
project={projectByKey.get(scopeKey) ?? null}
projectTitle={projectTitleByProjectKey.get(scopeKey)}
providerDriver={
serverConfigs
.get(thread.environmentId)
Expand Down Expand Up @@ -857,6 +873,7 @@ function ThreadNavigationSidebarPane(
openPendingTask,
projectByKey,
projectCwdByKey,
projectTitleByProjectKey,
props.onNewThreadInProject,
props.selectedThreadKey,
props.width,
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/features/threads/thread-list-v2-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
readonly variant: "card" | "slim";
readonly showSettledDivider: boolean;
readonly project: EnvironmentProject | null;
readonly projectTitle?: string;
readonly providerDriver: string | null;
/** Which machine hosts the thread. Null when only one environment is
connected — repeating the same label on every row is noise. Mirrors
Expand Down Expand Up @@ -219,7 +220,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
<ProjectFavicon
environmentId={thread.environmentId}
size={15}
projectTitle={props.project.title}
projectTitle={props.projectTitle ?? props.project.title}
workspaceRoot={props.project.workspaceRoot}
/>
) : null}
Expand All @@ -230,7 +231,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
)}
numberOfLines={1}
>
{props.project?.title ?? ""}
{props.projectTitle ?? props.project?.title ?? ""}
</Text>
<Text
className={cn(
Expand Down Expand Up @@ -393,7 +394,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
<ProjectFavicon
environmentId={thread.environmentId}
size={15}
projectTitle={props.project.title}
projectTitle={props.projectTitle ?? props.project.title}
workspaceRoot={props.project.workspaceRoot}
/>
</View>
Expand Down
Loading