Skip to content

Commit 38e5721

Browse files
committed
cp dines
1 parent ffc011b commit 38e5721

1 file changed

Lines changed: 71 additions & 21 deletions

File tree

src/commands/devbox/list.tsx

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ interface ListOptions {
4545
output?: string;
4646
}
4747

48-
const MAX_FETCH = 100;
4948
const DEFAULT_PAGE_SIZE = 10;
5049

5150
const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
@@ -65,6 +64,10 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
6564
const [refreshIcon, setRefreshIcon] = React.useState(0);
6665
const [searchMode, setSearchMode] = React.useState(false);
6766
const [searchQuery, setSearchQuery] = React.useState('');
67+
const [totalCount, setTotalCount] = React.useState(0);
68+
const [hasMore, setHasMore] = React.useState(false);
69+
const pageCache = React.useRef<Map<number, any[]>>(new Map());
70+
const lastIdCache = React.useRef<Map<number, string>>(new Map());
6871

6972
// Calculate responsive dimensions
7073
const terminalWidth = stdout?.columns || 120;
@@ -121,24 +124,63 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
121124
if (isInitialLoad) {
122125
setRefreshing(true);
123126
}
127+
128+
// Check if we have cached data for this page
129+
if (!isInitialLoad && pageCache.current.has(currentPage)) {
130+
setDevboxes(pageCache.current.get(currentPage) || []);
131+
setLoading(false);
132+
return;
133+
}
134+
124135
const client = getClient();
125-
const allDevboxes: any[] = [];
136+
const pageDevboxes: any[] = [];
126137

138+
// Get starting_after cursor from previous page's last ID
139+
const startingAfter = currentPage > 0 ? lastIdCache.current.get(currentPage - 1) : undefined;
140+
141+
// Build query params
142+
const queryParams: any = {
143+
limit: PAGE_SIZE,
144+
};
145+
if (startingAfter) {
146+
queryParams.starting_after = startingAfter;
147+
}
148+
if (status) {
149+
queryParams.status = status as 'provisioning' | 'initializing' | 'running' | 'suspending' | 'suspended' | 'resuming' | 'failure' | 'shutdown';
150+
}
151+
152+
// Fetch only the current page
153+
const page = await client.devboxes.list(queryParams);
154+
155+
// Collect items from the page - only get PAGE_SIZE items, don't auto-paginate
127156
let count = 0;
128-
for await (const devbox of client.devboxes.list()) {
129-
if (!status || devbox.status === status) {
130-
allDevboxes.push(devbox);
131-
}
157+
for await (const devbox of page) {
158+
pageDevboxes.push(devbox);
132159
count++;
133-
if (count >= MAX_FETCH) {
160+
// Break after getting PAGE_SIZE items to prevent auto-pagination
161+
if (count >= PAGE_SIZE) {
134162
break;
135163
}
136164
}
137165

138-
// Only update if data actually changed
166+
// Update pagination metadata from the page object
167+
// These properties are on the page object itself
168+
const total = (page as any).total_count || pageDevboxes.length;
169+
const more = (page as any).has_more || false;
170+
171+
setTotalCount(total);
172+
setHasMore(more);
173+
174+
// Cache the page data and last ID
175+
if (pageDevboxes.length > 0) {
176+
pageCache.current.set(currentPage, pageDevboxes);
177+
lastIdCache.current.set(currentPage, pageDevboxes[pageDevboxes.length - 1].id);
178+
}
179+
180+
// Update devboxes for current page
139181
setDevboxes((prev) => {
140-
const hasChanged = JSON.stringify(prev) !== JSON.stringify(allDevboxes);
141-
return hasChanged ? allDevboxes : prev;
182+
const hasChanged = JSON.stringify(prev) !== JSON.stringify(pageDevboxes);
183+
return hasChanged ? pageDevboxes : prev;
142184
});
143185
} catch (err) {
144186
setError(err as Error);
@@ -156,12 +198,15 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
156198
// Poll every 3 seconds (increased from 2), but only when in list view
157199
const interval = setInterval(() => {
158200
if (!showDetails && !showCreate && !showActions) {
201+
// Clear cache on refresh to get latest data
202+
pageCache.current.clear();
203+
lastIdCache.current.clear();
159204
list(false);
160205
}
161206
}, 3000);
162207

163208
return () => clearInterval(interval);
164-
}, [showDetails, showCreate, showActions]);
209+
}, [showDetails, showCreate, showActions, currentPage]);
165210

166211
// Animate refresh icon only when in list view
167212
React.useEffect(() => {
@@ -282,7 +327,7 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
282327
}
283328
});
284329

285-
// Filter devboxes based on search query
330+
// Filter devboxes based on search query (client-side only for current page)
286331
const filteredDevboxes = React.useMemo(() => {
287332
if (!searchQuery.trim()) return devboxes;
288333

@@ -301,12 +346,15 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
301346
['stopped', 'suspended'].includes(d.status)
302347
).length;
303348

304-
const totalPages = Math.ceil(filteredDevboxes.length / PAGE_SIZE);
305-
const startIndex = currentPage * PAGE_SIZE;
306-
const endIndex = Math.min(startIndex + PAGE_SIZE, filteredDevboxes.length);
307-
const currentDevboxes = filteredDevboxes.slice(startIndex, endIndex);
349+
// Current page is already fetched, no need to slice
350+
const currentDevboxes = filteredDevboxes;
308351
const selectedDevbox = currentDevboxes[selectedIndex];
309352

353+
// Calculate pagination info
354+
const totalPages = Math.ceil(totalCount / PAGE_SIZE);
355+
const startIndex = currentPage * PAGE_SIZE;
356+
const endIndex = startIndex + currentDevboxes.length;
357+
310358
// Filter operations based on devbox status
311359
const operations = selectedDevbox ? allOperations.filter(op => {
312360
const status = selectedDevbox.status;
@@ -384,7 +432,7 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
384432
data={currentDevboxes}
385433
keyExtractor={(devbox: any) => devbox.id}
386434
selectedIndex={selectedIndex}
387-
title={`devboxes[${devboxes.length}]`}
435+
title={`devboxes[${totalCount}]`}
388436
columns={[
389437
{
390438
key: 'statusIcon',
@@ -524,7 +572,7 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
524572
data={currentDevboxes}
525573
keyExtractor={(devbox: any) => devbox.id}
526574
selectedIndex={selectedIndex}
527-
title={`devboxes[${devboxes.length}]`}
575+
title={`devboxes[${totalCount}]`}
528576
columns={[
529577
{
530578
key: 'statusIcon',
@@ -605,8 +653,7 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
605653
{/* Statistics Bar */}
606654
<Box marginTop={1} paddingX={1}>
607655
<Text color="cyan" bold>
608-
{figures.hamburger} {devboxes.length}
609-
{devboxes.length >= MAX_FETCH && '+'}
656+
{figures.hamburger} {totalCount}
610657
</Text>
611658
<Text color="gray" dimColor> total</Text>
612659
{totalPages > 1 && (
@@ -619,8 +666,11 @@ const ListDevboxesUI: React.FC<{ status?: string }> = ({ status }) => {
619666
)}
620667
<Text color="gray" dimColor></Text>
621668
<Text color="gray" dimColor>
622-
Showing {startIndex + 1}-{endIndex} of {devboxes.length}
669+
Showing {startIndex + 1}-{endIndex} of {totalCount}
623670
</Text>
671+
{hasMore && (
672+
<Text color="gray" dimColor> (more available)</Text>
673+
)}
624674
<Text> </Text>
625675
{refreshing ? (
626676
<Text color="cyan">

0 commit comments

Comments
 (0)