Skip to content

Commit 04bc32c

Browse files
shivamhwpclaude
andcommitted
Merge upstream/main into android-dev-pr-3514
Resolves conflicts from #3687 (thread list/navigation rework), #3670 (pending tasks), and #3679 (Vite Plus upgrade): - ThreadNavigationDrawer.tsx: accept main's deletion; drawer navigation is replaced by native back-swipe and the reworked thread lists - ThreadRouteScreen.tsx: keep Android in-flow header alongside main's deep-link Home escape for the iOS compact header - HomeRouteScreen.tsx: keep AndroidHomeFabLayout wrapper, add main's pending-task props - NewTaskDraftScreen.tsx: keep shared promptEditor/startButton, port main's iOS headline text style and queue-task button states - thread-list-items.tsx: take main's header structure (chevron removed on all platforms); Android folder-state favicon already merged - ThreadDetailScreen.tsx: union imports, drop unused gesture-handler - pnpm-workspace.yaml/lockfile: keep expo-modules-jsi pin comment, regenerate lockfile via pnpm install Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 parents f38d3f1 + 6009720 commit 04bc32c

41 files changed

Lines changed: 2465 additions & 1194 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vite-hooks/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vp staged

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"react-native": "0.85.3",
100100
"react-native-gesture-handler": "~2.31.1",
101101
"react-native-image-viewing": "^0.2.2",
102-
"react-native-keyboard-controller": "1.21.6",
102+
"react-native-keyboard-controller": "1.21.13",
103103
"react-native-nitro-markdown": "^0.5.0",
104104
"react-native-nitro-modules": "0.35.9",
105105
"react-native-reanimated": "4.3.1",

apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ function ArchivedThreadRow(props: {
507507
</Text>
508508
<Text
509509
className="text-xs text-foreground-tertiary"
510-
style={{ fontVariant: ["tabular-nums"] }}
510+
style={{ fontVariant: ["tabular-nums"], minWidth: 30, textAlign: "right" }}
511511
>
512512
{timestamp}
513513
</Text>

apps/mobile/src/features/home/HomeRouteScreen.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useMemo, useState } from "react";
55

66
import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
77
import { useProjects, useThreadShells } from "../../state/entities";
8+
import { usePendingNewTasks } from "../../state/use-pending-new-tasks";
89
import { useWorkspaceState } from "../../state/workspace";
910
import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
1011
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
@@ -14,6 +15,7 @@ import { AndroidHomeFabLayout } from "./AndroidHomeFab";
1415
import { HomeScreen } from "./HomeScreen";
1516
import { HomeHeader } from "./HomeHeader";
1617
import { useHomeListOptions } from "./home-list-options";
18+
import { usePendingTaskListActions } from "./usePendingTaskListActions";
1719
import { useThreadListActions } from "./useThreadListActions";
1820

1921
/* ─── Route screen ───────────────────────────────────────────────────── */
@@ -27,6 +29,8 @@ export function HomeRouteScreen() {
2729
const navigation = useNavigation();
2830
const [searchQuery, setSearchQuery] = useState("");
2931
const { archiveThread, confirmDeleteThread } = useThreadListActions();
32+
const pendingTasks = usePendingNewTasks();
33+
const { openPendingTask, confirmDeletePendingTask } = usePendingTaskListActions();
3034
const environments = useMemo(
3135
() =>
3236
Arr.sort(
@@ -121,8 +125,21 @@ export function HomeRouteScreen() {
121125
threadId: thread.id,
122126
});
123127
}}
128+
onSelectPendingTask={openPendingTask}
129+
onDeletePendingTask={confirmDeletePendingTask}
130+
onNewThreadInProject={(project) => {
131+
navigation.navigate("NewTaskSheet", {
132+
screen: "NewTaskDraft",
133+
params: {
134+
environmentId: String(project.environmentId),
135+
projectId: String(project.id),
136+
title: project.title,
137+
},
138+
});
139+
}}
124140
onStartNewTask={() => navigation.navigate("NewTaskSheet", { screen: "NewTask" })}
125141
onThreadSortOrderChange={setThreadSortOrder}
142+
pendingTasks={pendingTasks}
126143
projectGroupingMode={listOptions.projectGroupingMode}
127144
projects={projects}
128145
projectSortOrder={listOptions.projectSortOrder}

apps/mobile/src/features/home/HomeScreen.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import { EmptyState } from "../../components/EmptyState";
2222
import type { WorkspaceState } from "../../state/workspaceModel";
2323
import type { SavedRemoteConnection } from "../../lib/connection";
2424
import { scopedProjectKey } from "../../lib/scopedEntities";
25+
import type { PendingNewTask } from "../../state/use-pending-new-tasks";
2526
import {
27+
PendingTaskListRow,
2628
ThreadListGroupHeader,
2729
ThreadListRow,
2830
ThreadListShowMoreRow,
@@ -47,6 +49,7 @@ import { shouldShowWorkspaceConnectionStatus } from "./workspace-connection-stat
4749
interface HomeScreenProps {
4850
readonly projects: ReadonlyArray<EnvironmentProject>;
4951
readonly threads: ReadonlyArray<EnvironmentThreadShell>;
52+
readonly pendingTasks: ReadonlyArray<PendingNewTask>;
5053
readonly catalogState: WorkspaceState;
5154
readonly savedConnectionsById: Readonly<Record<string, SavedRemoteConnection>>;
5255
readonly environments: ReadonlyArray<HomeListFilterMenuEnvironment>;
@@ -67,6 +70,9 @@ interface HomeScreenProps {
6770
readonly onSelectThread: (thread: EnvironmentThreadShell) => void;
6871
readonly onArchiveThread: (thread: EnvironmentThreadShell) => void;
6972
readonly onDeleteThread: (thread: EnvironmentThreadShell) => void;
73+
readonly onSelectPendingTask: (pendingTask: PendingNewTask) => void;
74+
readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void;
75+
readonly onNewThreadInProject: (project: EnvironmentProject) => void;
7076
}
7177

7278
/* ─── Layout constants ───────────────────────────────────────────────── */
@@ -194,13 +200,15 @@ export function HomeScreen(props: HomeScreenProps) {
194200
buildHomeThreadGroups({
195201
projects: props.projects,
196202
threads: props.threads,
203+
pendingTasks: props.pendingTasks,
197204
environmentId: props.selectedEnvironmentId,
198205
searchQuery: props.searchQuery,
199206
projectSortOrder: props.projectSortOrder,
200207
threadSortOrder: props.threadSortOrder,
201208
projectGroupingMode: props.projectGroupingMode,
202209
}),
203210
[
211+
props.pendingTasks,
204212
props.projectGroupingMode,
205213
props.projects,
206214
props.projectSortOrder,
@@ -246,11 +254,31 @@ export function HomeScreen(props: HomeScreenProps) {
246254
isFirst={item.isFirst}
247255
groupKey={item.group.key}
248256
onGroupAction={updateGroupDisplay}
257+
// Aggregated groups (same repo across machines) have no single
258+
// target project, and `pending-project:` groups hold a placeholder
259+
// built from queued-task metadata rather than a real project shell,
260+
// so the quick new-thread button is single-real-project only.
261+
newThreadTarget={item.group.newThreadTarget}
262+
onNewThread={props.onNewThreadInProject}
249263
project={item.group.representative}
250-
threadCount={item.group.threads.length}
264+
threadCount={item.group.threads.length + item.group.pendingTasks.length}
251265
title={item.group.title}
252266
/>
253267
);
268+
case "pending-task":
269+
return (
270+
<PendingTaskListRow
271+
variant="compact"
272+
pendingTask={item.pendingTask}
273+
environmentLabel={
274+
props.savedConnectionsById[item.pendingTask.message.environmentId]
275+
?.environmentLabel ?? null
276+
}
277+
isLast={item.isLast}
278+
onSelectPendingTask={props.onSelectPendingTask}
279+
onDeletePendingTask={props.onDeletePendingTask}
280+
/>
281+
);
254282
case "thread": {
255283
const thread = item.thread;
256284
return (
@@ -290,7 +318,10 @@ export function HomeScreen(props: HomeScreenProps) {
290318
handleSwipeableWillOpen,
291319
projectCwdByKey,
292320
props.onArchiveThread,
321+
props.onDeletePendingTask,
293322
props.onDeleteThread,
323+
props.onNewThreadInProject,
324+
props.onSelectPendingTask,
294325
props.onSelectThread,
295326
props.savedConnectionsById,
296327
updateGroupDisplay,
@@ -300,7 +331,8 @@ export function HomeScreen(props: HomeScreenProps) {
300331
const keyExtractor = useCallback((item: HomeListItem) => item.key, []);
301332

302333
/* Empty states */
303-
const hasAnyThreads = props.threads.some((thread) => thread.archivedAt === null);
334+
const hasAnyThreads =
335+
props.threads.some((thread) => thread.archivedAt === null) || props.pendingTasks.length > 0;
304336
const hasResults = projectGroups.length > 0;
305337
const selectedEnvironmentLabel =
306338
props.selectedEnvironmentId === null

apps/mobile/src/features/home/homeListItems.test.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,20 @@ function makeThread(id: string, projectId: ProjectId): EnvironmentThreadShell {
5757

5858
function makeGroup(key: string, threadCount: number): HomeThreadGroup {
5959
const project = makeProject(key, key);
60+
const threads = Array.from({ length: threadCount }, (_, index) =>
61+
makeThread(`${key}-thread-${index}`, project.id),
62+
);
6063
return {
6164
key,
6265
title: key,
6366
representative: project,
6467
projects: [project],
65-
threads: Array.from({ length: threadCount }, (_, index) =>
66-
makeThread(`${key}-thread-${index}`, project.id),
67-
),
68+
pendingTasks: [],
69+
threads,
70+
// All threads inside the recency window, so the baseline stays at the
71+
// initial page size and the pagination expectations below hold.
72+
recentThreads: threads,
73+
newThreadTarget: project,
6874
};
6975
}
7076

@@ -152,6 +158,48 @@ describe("buildHomeListLayout", () => {
152158
expect(reset.visibleCount).toBe(HOME_INITIAL_VISIBLE_THREADS);
153159
});
154160

161+
it("offers show-less after expanding a stale group whose baseline is below the page size", () => {
162+
// Stale project: 10 threads total but only 3 within the recency window.
163+
const project = makeProject("stale", "stale");
164+
const threads = Array.from({ length: 10 }, (_, index) =>
165+
makeThread(`stale-thread-${index}`, project.id),
166+
);
167+
const group: HomeThreadGroup = {
168+
key: "stale",
169+
title: "stale",
170+
representative: project,
171+
projects: [project],
172+
pendingTasks: [],
173+
threads,
174+
recentThreads: threads.slice(0, 3),
175+
newThreadTarget: project,
176+
};
177+
178+
const collapsedToRecent = buildHomeListLayout({
179+
groups: [group],
180+
displayStates: displayStates({}),
181+
});
182+
expect(collapsedToRecent.items.filter((item) => item.type === "thread")).toHaveLength(3);
183+
expect(collapsedToRecent.items.at(-1)).toMatchObject({
184+
type: "show-more",
185+
hiddenCount: 7,
186+
canShowLess: false,
187+
});
188+
189+
const expanded = buildHomeListLayout({
190+
groups: [group],
191+
displayStates: displayStates({
192+
stale: nextGroupDisplayState(DEFAULT_GROUP_DISPLAY_STATE, "show-more"),
193+
}),
194+
});
195+
expect(expanded.items.filter((item) => item.type === "thread")).toHaveLength(10);
196+
expect(expanded.items.at(-1)).toMatchObject({
197+
type: "show-more",
198+
hiddenCount: 0,
199+
canShowLess: true,
200+
});
201+
});
202+
155203
it("hides threads and the show-more row for collapsed groups", () => {
156204
const layout = buildHomeListLayout({
157205
groups: [makeGroup("alpha", 12), makeGroup("beta", 2)],

apps/mobile/src/features/home/homeListItems.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
22

3+
import type { PendingNewTask } from "../../state/use-pending-new-tasks";
34
import type { HomeThreadGroup } from "./homeThreadList";
45

56
/** Threads shown per project before the "Show more" affordance appears. */
@@ -33,6 +34,13 @@ export interface HomeThreadListItem {
3334
readonly isLast: boolean;
3435
}
3536

37+
export interface HomePendingTaskListItem {
38+
readonly type: "pending-task";
39+
readonly key: string;
40+
readonly pendingTask: PendingNewTask;
41+
readonly isLast: boolean;
42+
}
43+
3644
export interface HomeShowMoreListItem {
3745
readonly type: "show-more";
3846
readonly key: string;
@@ -43,7 +51,11 @@ export interface HomeShowMoreListItem {
4351
readonly canShowLess: boolean;
4452
}
4553

46-
export type HomeListItem = HomeHeaderListItem | HomeThreadListItem | HomeShowMoreListItem;
54+
export type HomeListItem =
55+
| HomeHeaderListItem
56+
| HomePendingTaskListItem
57+
| HomeThreadListItem
58+
| HomeShowMoreListItem;
4759

4860
export interface HomeListLayout {
4961
readonly items: ReadonlyArray<HomeListItem>;
@@ -82,6 +94,12 @@ export function homeListItemsAreEqual(previous: HomeListItem, item: HomeListItem
8294
previous.collapsed === item.collapsed &&
8395
previous.isFirst === item.isFirst
8496
);
97+
case "pending-task":
98+
return (
99+
previous.type === "pending-task" &&
100+
previous.pendingTask === item.pendingTask &&
101+
previous.isLast === item.isLast
102+
);
85103
case "thread":
86104
return (
87105
previous.type === "thread" &&
@@ -127,12 +145,40 @@ export function buildHomeListLayout(input: {
127145
}
128146

129147
const totalCount = group.threads.length;
148+
// Default to the group's recent-activity window (last few days, or a small
149+
// fallback for stale projects), capped at the initial page size. Until the
150+
// user taps "Show more", older threads stay hidden to save vertical space;
151+
// "Show less" resets visibleCount to the initial constant, which lands back
152+
// here at the recency baseline.
153+
const baselineCount = Math.min(
154+
group.recentThreads.length,
155+
HOME_INITIAL_VISIBLE_THREADS,
156+
totalCount,
157+
);
130158
const visibleCount = input.showAllThreads
131159
? totalCount
132-
: Math.min(Math.max(display.visibleCount, HOME_INITIAL_VISIBLE_THREADS), totalCount);
160+
: Math.min(
161+
display.visibleCount > HOME_INITIAL_VISIBLE_THREADS
162+
? display.visibleCount
163+
: baselineCount,
164+
totalCount,
165+
);
133166
const visibleThreads = group.threads.slice(0, visibleCount);
134167
const hiddenCount = totalCount - visibleCount;
135-
const hasShowMoreRow = !input.showAllThreads && totalCount > HOME_INITIAL_VISIBLE_THREADS;
168+
const hasShowMoreRow = !input.showAllThreads && totalCount > baselineCount;
169+
170+
// Pending (unsent) tasks lead the group and are never paginated away.
171+
for (const [pendingIndex, pendingTask] of group.pendingTasks.entries()) {
172+
items.push({
173+
type: "pending-task",
174+
key: `pending-task:${pendingTask.message.messageId}`,
175+
pendingTask,
176+
isLast:
177+
pendingIndex === group.pendingTasks.length - 1 &&
178+
visibleThreads.length === 0 &&
179+
!hasShowMoreRow,
180+
});
181+
}
136182

137183
for (const [threadIndex, thread] of visibleThreads.entries()) {
138184
items.push({
@@ -149,7 +195,10 @@ export function buildHomeListLayout(input: {
149195
key: `show-more:${group.key}`,
150196
groupKey: group.key,
151197
hiddenCount,
152-
canShowLess: visibleCount > HOME_INITIAL_VISIBLE_THREADS,
198+
// Compare against the group's own baseline, not the global page size:
199+
// stale projects start below HOME_INITIAL_VISIBLE_THREADS, and "Show
200+
// less" must be offered as soon as anything beyond the baseline shows.
201+
canShowLess: visibleCount > baselineCount,
153202
});
154203
}
155204
}

0 commit comments

Comments
 (0)