Skip to content

Commit f874e37

Browse files
feat(platform): optimize logs page navigation for instant feedback (vm0-ai#2577)
* test(platform): add skeleton loading state test for logs page Add test case to verify skeleton loading state is displayed while fetching logs data, ensuring smooth transition from loading state to actual content. Closes vm0-ai#2505 Co-authored-by: Cursor <cursoragent@cursor.com> * feat(platform): optimize logs page navigation for instant feedback Move page rendering before scope validation to provide immediate visual feedback when navigating to logs page. This eliminates the perceived delay by showing the logs page with skeleton loading states while scope validation and data fetching happen in the background. Changes: - Render LogsPage immediately in setupLogsPage$ before data initialization - Move scope check in setupScopeRequiredPageWrapper to run after page display - Maintain existing validation logic and redirect behavior Closes vm0-ai#2505 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(platform): enable horizontal scroll for logs table on mobile Remove table-fixed layout and add overflow-x-auto to enable horizontal scrolling on small screens. Standardize all column min-width to 120px for better content visibility. Changes: - Remove table-fixed from Table component to allow natural width - Add overflow-x-auto to table-wrapper for horizontal scrolling - Update all column min-width from 80px/70px to 120px - Apply changes to logs table, skeleton, and table rows Addresses vm0-ai#2537 (P0 issue #1) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(platform): improve onboarding modal mobile layout and spacing Optimize modal for mobile devices by adjusting height constraints, padding, and font sizes: - Change mobile max-height from 100dvh to 90dvh for better content space - Reduce padding on mobile (px-4 py-4) while maintaining desktop spacing - Add responsive padding and sizing for close button and logo - Add border-top to footer for visual separation - Reduce gap between form elements on mobile (gap-4 vs gap-6) Addresses vm0-ai#2537 (P0 issue vm0-ai#2) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): restore table-fixed layout for proportional column scaling Restore table-fixed layout to maintain proportional column scaling while keeping overflow-x-auto for horizontal scroll support. This allows tables to scale proportionally on larger screens and scroll horizontally when columns reach their minimum width constraints on small screens. Related to vm0-ai#2537 Co-authored-by: Cursor <cursoragent@cursor.com> * style(ui): add subtle scrollbar styling for table component Add elegant, minimal scrollbar styling for horizontal scroll: - Thin scrollbar (6px height) - Subtle color (muted-foreground 20% opacity) - Transparent track for clean appearance - Smooth hover transition (30% opacity) - Rounded corners (3px) for refined look Related to vm0-ai#2537 Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 00456d8 commit f874e37

8 files changed

Lines changed: 111 additions & 36 deletions

File tree

turbo/apps/platform/src/signals/bootstrap.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ function setupScopeRequiredPageWrapper(
7777
command(async ({ get, set }, signal: AbortSignal) => {
7878
L.debug("enter setupScopeRequiredPageWrapper");
7979

80+
// First, immediately render the page to provide instant visual feedback
81+
// The page components will show loading skeletons while data fetches
82+
await set(fn, signal);
83+
signal.throwIfAborted();
84+
85+
// Then check scope in background (after page is already displayed)
8086
const scopeExists = await get(hasScope$);
8187
signal.throwIfAborted();
8288
L.debug("scopeExists", scopeExists);
@@ -86,9 +92,6 @@ function setupScopeRequiredPageWrapper(
8692
set(navigateInReact$, "/");
8793
return;
8894
}
89-
90-
await set(fn, signal);
91-
signal.throwIfAborted();
9295
}),
9396
);
9497
}

turbo/apps/platform/src/signals/logs-page/logs-page.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { updatePage$ } from "../react-router.ts";
55
import { initLogs$ } from "./logs-signals.ts";
66

77
export const setupLogsPage$ = command(({ set }, signal: AbortSignal) => {
8+
// Immediately update page to show LogsPage with skeleton loading
9+
// This provides instant visual feedback while data is being fetched
810
set(updatePage$, createElement(LogsPage));
911

12+
// Initialize logs data in background (non-blocking)
1013
set(initLogs$, signal);
1114
});

turbo/apps/platform/src/views/home/onboarding-modal.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@ export function OnboardingModal() {
9595
return (
9696
<Dialog open={isOpen} onOpenChange={(open) => !open && closeModal()}>
9797
<DialogContent
98-
className="flex max-h-[100dvh] flex-col gap-0 overflow-hidden sm:max-h-[85dvh] sm:max-w-[600px] p-0 border-border rounded-[10px] [&>button[aria-label=Close]:last-child]:hidden"
98+
className="flex max-h-[90dvh] flex-col gap-0 overflow-hidden sm:max-h-[85dvh] sm:max-w-[600px] p-0 border-border rounded-[10px] [&>button[aria-label=Close]:last-child]:hidden"
9999
style={{
100100
backgroundImage: backgroundGradient,
101101
}}
102102
>
103103
{/* Close button - top row */}
104-
<div className="shrink-0 flex justify-end pr-4 pt-4">
104+
<div className="shrink-0 flex justify-end pr-3 pt-3 sm:pr-4 sm:pt-4">
105105
<DialogClose asChild>
106106
<button
107107
className="icon-button opacity-70 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
@@ -113,32 +113,32 @@ export function OnboardingModal() {
113113
</div>
114114

115115
{/* Fixed Header - Logo and Title */}
116-
<div className="shrink-0 px-6 pb-4">
116+
<div className="shrink-0 px-4 pb-3 sm:px-6 sm:pb-4">
117117
{/* Logo */}
118-
<div className="flex items-center justify-center gap-2 mb-4">
118+
<div className="flex items-center justify-center gap-2 mb-3 sm:mb-4">
119119
<img
120120
src={theme === "dark" ? "/logo_dark.svg" : "/logo_light.svg"}
121121
alt="VM0"
122-
className="h-[40px] w-auto"
122+
className="h-[32px] sm:h-[40px] w-auto"
123123
/>
124-
<span className="text-4xl font-normal text-foreground">
124+
<span className="text-3xl sm:text-4xl font-normal text-foreground">
125125
Platform
126126
</span>
127127
</div>
128128

129129
{/* Header */}
130130
<div className="text-center">
131-
<DialogTitle className="text-lg font-medium leading-7 text-foreground">
131+
<DialogTitle className="text-base sm:text-lg font-medium leading-6 sm:leading-7 text-foreground">
132132
Define your model provider
133133
</DialogTitle>
134-
<DialogDescription className="text-sm text-foreground mt-[10px]">
134+
<DialogDescription className="text-sm text-foreground mt-2">
135135
Your model provider is required for sandboxed execution.
136136
</DialogDescription>
137137
</div>
138138
</div>
139139

140140
{/* Scrollable content area */}
141-
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-6 flex flex-col gap-6 dialog-scrollable">
141+
<div className="flex-1 min-h-0 overflow-y-auto px-4 py-4 sm:px-6 sm:py-6 flex flex-col gap-4 sm:gap-6 dialog-scrollable">
142142
{/* Provider Type Selector */}
143143
<div className="flex flex-col gap-2">
144144
<label className="px-1 text-sm font-medium text-foreground">
@@ -203,7 +203,7 @@ export function OnboardingModal() {
203203
</div>
204204

205205
{/* Fixed Footer - Action Buttons */}
206-
<div className="shrink-0 flex justify-end gap-2 px-6 pb-6 pt-4">
206+
<div className="shrink-0 flex justify-end gap-2 px-4 pb-4 pt-3 sm:px-6 sm:pb-6 sm:pt-4 border-t border-border/50">
207207
<Button variant="outline" onClick={() => closeModal()}>
208208
Cancel
209209
</Button>

turbo/apps/platform/src/views/logs-page/__tests__/logs-page.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,53 @@ describe("logs page", () => {
223223
expect(screen.getByText(/Failed to fetch logs/)).toBeInTheDocument();
224224
});
225225
});
226+
227+
it("should show skeleton loading state while fetching data", async () => {
228+
let resolveRequest: ((value: unknown) => void) | undefined;
229+
const requestPromise = new Promise((resolve) => {
230+
resolveRequest = resolve;
231+
});
232+
233+
server.use(
234+
http.get("*/api/platform/logs", async () => {
235+
await requestPromise;
236+
return HttpResponse.json({
237+
data: [
238+
{
239+
id: "run_test",
240+
sessionId: "session_test",
241+
agentName: "Test Agent",
242+
framework: "claude-code",
243+
status: "completed",
244+
createdAt: "2024-01-01T00:00:00Z",
245+
},
246+
],
247+
pagination: { hasMore: false, nextCursor: null },
248+
});
249+
}),
250+
);
251+
252+
await setupPage({
253+
context,
254+
path: "/logs",
255+
});
256+
257+
// Should show skeleton while loading
258+
// Skeleton rows have specific test structure - check for multiple skeleton elements
259+
const table = screen.getByRole("table");
260+
const skeletonElements = within(table).getAllByRole("row");
261+
// Should have header row + multiple skeleton rows (8 in LogsTableSkeleton)
262+
expect(skeletonElements.length).toBeGreaterThan(1);
263+
264+
// Resolve the request to show actual data
265+
resolveRequest?.(true);
266+
267+
// Wait for actual data to appear
268+
await waitFor(() => {
269+
expect(screen.getByText("Test Agent")).toBeInTheDocument();
270+
});
271+
272+
// Verify skeleton is replaced with actual data
273+
expect(screen.getByText("session_test")).toBeInTheDocument();
274+
});
226275
});

turbo/apps/platform/src/views/logs-page/logs-table-row.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,25 @@ export function LogsTableRow({ entry }: LogsTableRowProps) {
4141
className="h-[53px] cursor-pointer hover:bg-muted/50"
4242
onClick={handleRowClick}
4343
>
44-
<TableCell className="px-3 py-2 text-sm font-medium w-[20%] min-w-[80px]">
44+
<TableCell className="px-3 py-2 text-sm font-medium w-[20%] min-w-[120px]">
4545
<span className="block truncate whitespace-nowrap">{entry.id}</span>
4646
</TableCell>
47-
<TableCell className="px-3 py-2 text-sm w-[20%] min-w-[80px]">
47+
<TableCell className="px-3 py-2 text-sm w-[20%] min-w-[120px]">
4848
<span className="block truncate whitespace-nowrap">
4949
{entry.sessionId ?? "-"}
5050
</span>
5151
</TableCell>
52-
<TableCell className="px-3 py-2 text-sm w-[15%] min-w-[80px]">
52+
<TableCell className="px-3 py-2 text-sm w-[15%] min-w-[120px]">
5353
<span className="block truncate whitespace-nowrap">
5454
{entry.agentName}
5555
</span>
5656
</TableCell>
57-
<TableCell className="px-3 py-2 text-sm w-[12%] min-w-[70px]">
57+
<TableCell className="px-3 py-2 text-sm w-[12%] min-w-[120px]">
5858
<span className="block truncate whitespace-nowrap">
5959
{entry.framework ?? "-"}
6060
</span>
6161
</TableCell>
62-
<TableCell className="px-3 py-2 w-[13%] min-w-[80px]">
62+
<TableCell className="px-3 py-2 w-[13%] min-w-[120px]">
6363
<div className="truncate whitespace-nowrap">
6464
<StatusBadge status={entry.status} />
6565
</div>

turbo/apps/platform/src/views/logs-page/logs-table-skeleton.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ function LogsTableHeader() {
1212
return (
1313
<TableHeader className="bg-muted">
1414
<TableRow className="hover:bg-transparent">
15-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[80px]">
15+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[120px]">
1616
<span className="block truncate whitespace-nowrap">Run ID</span>
1717
</TableHead>
18-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[80px]">
18+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[120px]">
1919
<span className="block truncate whitespace-nowrap">Session ID</span>
2020
</TableHead>
21-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[80px]">
21+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[120px]">
2222
<span className="block truncate whitespace-nowrap">Agent</span>
2323
</TableHead>
24-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[12%] min-w-[70px]">
24+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[12%] min-w-[120px]">
2525
<span className="block truncate whitespace-nowrap">Framework</span>
2626
</TableHead>
27-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[13%] min-w-[80px]">
27+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[13%] min-w-[120px]">
2828
<span className="block truncate whitespace-nowrap">Status</span>
2929
</TableHead>
3030
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[120px]">
@@ -45,22 +45,22 @@ export function LogsTableSkeleton() {
4545
<TableBody>
4646
{Array.from({ length: 8 }, (_, i) => (
4747
<TableRow key={`skeleton-${i}`} className="h-[49px]">
48-
<TableCell className="px-3 py-2">
48+
<TableCell className="px-3 py-2 min-w-[120px]">
4949
<Skeleton className="h-4 w-24" />
5050
</TableCell>
51-
<TableCell className="px-3 py-2">
51+
<TableCell className="px-3 py-2 min-w-[120px]">
5252
<Skeleton className="h-4 w-28" />
5353
</TableCell>
54-
<TableCell className="px-3 py-2">
54+
<TableCell className="px-3 py-2 min-w-[120px]">
5555
<Skeleton className="h-4 w-20" />
5656
</TableCell>
57-
<TableCell className="px-3 py-2">
57+
<TableCell className="px-3 py-2 min-w-[120px]">
5858
<Skeleton className="h-4 w-16" />
5959
</TableCell>
60-
<TableCell className="px-3 py-2">
60+
<TableCell className="px-3 py-2 min-w-[120px]">
6161
<Skeleton className="h-6 w-20 rounded-full" />
6262
</TableCell>
63-
<TableCell className="px-3 py-2">
63+
<TableCell className="px-3 py-2 min-w-[120px]">
6464
<Skeleton className="h-4 w-32" />
6565
</TableCell>
6666
<TableCell className="px-2 py-2">

turbo/apps/platform/src/views/logs-page/logs-table.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ function LogsTableHeader() {
1111
return (
1212
<TableHeader className="bg-muted">
1313
<TableRow className="hover:bg-transparent">
14-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[80px]">
14+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[120px]">
1515
<span className="block truncate whitespace-nowrap">Run ID</span>
1616
</TableHead>
17-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[80px]">
17+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[20%] min-w-[120px]">
1818
<span className="block truncate whitespace-nowrap">Session ID</span>
1919
</TableHead>
20-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[80px]">
20+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[120px]">
2121
<span className="block truncate whitespace-nowrap">Agent</span>
2222
</TableHead>
23-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[12%] min-w-[70px]">
23+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[12%] min-w-[120px]">
2424
<span className="block truncate whitespace-nowrap">Framework</span>
2525
</TableHead>
26-
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[13%] min-w-[80px]">
26+
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[13%] min-w-[120px]">
2727
<span className="block truncate whitespace-nowrap">Status</span>
2828
</TableHead>
2929
<TableHead className="h-10 px-3 text-sm font-medium text-foreground w-[15%] min-w-[120px]">

turbo/packages/ui/src/components/ui/table.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,33 @@ const Table = React.forwardRef<
1919
.table-wrapper thead tr {
2020
border-bottom: 0 !important;
2121
}
22+
.table-wrapper {
23+
scrollbar-width: thin;
24+
scrollbar-color: hsl(var(--muted-foreground) / 0.2) transparent;
25+
}
26+
.table-wrapper::-webkit-scrollbar {
27+
height: 6px;
28+
}
29+
.table-wrapper::-webkit-scrollbar-track {
30+
background: transparent;
31+
}
32+
.table-wrapper::-webkit-scrollbar-thumb {
33+
background-color: hsl(var(--muted-foreground) / 0.2);
34+
border-radius: 3px;
35+
}
36+
.table-wrapper::-webkit-scrollbar-thumb:hover {
37+
background-color: hsl(var(--muted-foreground) / 0.3);
38+
}
2239
`,
2340
}}
2441
/>
25-
<div className="table-wrapper">
42+
<div className="table-wrapper overflow-x-auto">
2643
<table
2744
ref={ref}
28-
className={cn("w-full caption-bottom text-sm table-fixed", className)}
45+
className={cn(
46+
"w-full min-w-[764px] caption-bottom text-sm table-fixed",
47+
className,
48+
)}
2949
{...props}
3050
/>
3151
</div>

0 commit comments

Comments
 (0)