fix: show deck skeletons while loading connections

This commit is contained in:
2026-09-28 20:59:53 +09:00
parent 7a7bc983e6
commit f1914a5d0a
3 changed files with 54 additions and 13 deletions
+7
View File
@@ -1,6 +1,7 @@
import { Icon } from "#/components/icon"; import { Icon } from "#/components/icon";
import { Badge } from "#/components/ui/badge"; import { Badge } from "#/components/ui/badge";
import { Button } from "#/components/ui/button"; import { Button } from "#/components/ui/button";
import { Skeleton } from "#/components/ui/skeleton";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -22,6 +23,7 @@ import { describeSource } from "./source-description";
export function ResearchColumn({ export function ResearchColumn({
column, column,
connectionLabel, connectionLabel,
connectionLoading = false,
onEdit, onEdit,
onRemove, onRemove,
onMove, onMove,
@@ -32,6 +34,7 @@ export function ResearchColumn({
}: { }: {
column: DeckColumn; column: DeckColumn;
connectionLabel?: string; connectionLabel?: string;
connectionLoading?: boolean;
onEdit: () => void; onEdit: () => void;
onRemove: () => void; onRemove: () => void;
onMove: (direction: -1 | 1) => void; onMove: (direction: -1 | 1) => void;
@@ -112,9 +115,13 @@ export function ResearchColumn({
</DropdownMenu> </DropdownMenu>
</div> </div>
<div className="deck-column-label"> <div className="deck-column-label">
{connectionLoading ? (
<Skeleton className="h-3 w-24 shrink-0" aria-hidden="true" />
) : (
<span title={`Connection profile: ${connectionLabel ?? "Connection not found"}`}> <span title={`Connection profile: ${connectionLabel ?? "Connection not found"}`}>
{connectionLabel ?? "Connection not found"} {connectionLabel ?? "Connection not found"}
</span> </span>
)}
<Badge variant="outline" className="deck-source-label"> <Badge variant="outline" className="deck-source-label">
<Icon name={column.source.kind} /> <Icon name={column.source.kind} />
{sourceDescription.label} {sourceDescription.label}
+13 -6
View File
@@ -15,6 +15,7 @@ import {
EmptyTitle, EmptyTitle,
} from "#/components/ui/empty"; } from "#/components/ui/empty";
import { Input } from "#/components/ui/input"; import { Input } from "#/components/ui/input";
import { Skeleton } from "#/components/ui/skeleton";
import { loadConnections } from "#/features/connections/server-functions"; import { loadConnections } from "#/features/connections/server-functions";
import { syncResearchDeck } from "#/features/research/research-deck-sync"; import { syncResearchDeck } from "#/features/research/research-deck-sync";
import { ResearchPanel } from "#/features/research/research-panel"; import { ResearchPanel } from "#/features/research/research-panel";
@@ -27,6 +28,7 @@ import { type ColumnRegistry, useColumnTools } from "./column-tools";
import { ResearchColumn } from "./deck-column"; import { ResearchColumn } from "./deck-column";
import { DeckSwitcher } from "./deck-switcher"; import { DeckSwitcher } from "./deck-switcher";
import { type DeckColumn, MAX_COLUMNS, moveColumn } from "./model"; import { type DeckColumn, MAX_COLUMNS, moveColumn } from "./model";
import { ResearchDeckSkeleton } from "./research-deck-skeleton";
import { useDeck } from "./use-deck"; import { useDeck } from "./use-deck";
import { useDeckTools } from "./webmcp-tools"; import { useDeckTools } from "./webmcp-tools";
@@ -202,10 +204,19 @@ export function DeckPage() {
> >
<div className="deck-heading"> <div className="deck-heading">
<div> <div>
{ready ? (
<>
<h1>{deck.title}</h1> <h1>{deck.title}</h1>
<span className="deck-count"> <span className="deck-count">
{deck.columns.length} / {MAX_COLUMNS} columns {deck.columns.length} / {MAX_COLUMNS} columns
</span> </span>
</>
) : (
<div className="space-y-2" aria-hidden="true">
<Skeleton className="h-5 w-40" />
<Skeleton className="h-3 w-20" />
</div>
)}
</div> </div>
<div className="deck-actions"> <div className="deck-actions">
{deck.persisted ? ( {deck.persisted ? (
@@ -260,11 +271,6 @@ export function DeckPage() {
</Button> </Button>
</p> </p>
) : null} ) : null}
{!ready ? (
<p className="deck-save-status" role="status">
Loading decks…
</p>
) : null}
{profiles.isError ? ( {profiles.isError ? (
<p role="alert" className="deck-error"> <p role="alert" className="deck-error">
Unable to load connection profiles. Unable to load connection profiles.
@@ -288,7 +294,6 @@ export function DeckPage() {
{profiles.data.relayError} {profiles.data.relayError}
</p> </p>
) : null} ) : null}
{profiles.isPending ? <p role="status">Loading connection profiles…</p> : null}
{!renaming && editing === null && !switcherEditing ? storageErrorNotice : null} {!renaming && editing === null && !switcherEditing ? storageErrorNotice : null}
{renaming ? ( {renaming ? (
<Dialog title="Rename deck" onClose={() => setRenaming(false)}> <Dialog title="Rename deck" onClose={() => setRenaming(false)}>
@@ -355,6 +360,7 @@ export function DeckPage() {
</Button> </Button>
</div> </div>
) : null} ) : null}
{!ready && !storageError ? <ResearchDeckSkeleton /> : null}
{ready && deck.columns.length === 0 && editing === null ? ( {ready && deck.columns.length === 0 && editing === null ? (
<Empty> <Empty>
<EmptyHeader> <EmptyHeader>
@@ -388,6 +394,7 @@ export function DeckPage() {
key={`${deck.id}:${column.id}`} key={`${deck.id}:${column.id}`}
registry={registry} registry={registry}
column={column} column={column}
connectionLoading={profiles.isPending}
citation={ citation={
citationNavigation.target?.deckId === deck.id && citationNavigation.target?.deckId === deck.id &&
citationNavigation.target.column.id === column.id && citationNavigation.target.column.id === column.id &&
@@ -0,0 +1,27 @@
import { Skeleton } from "#/components/ui/skeleton";
import { ResearchPostSkeleton } from "./research-post-skeleton";
export function ResearchDeckSkeleton() {
return (
<div className="deck-board" role="status" aria-label="Loading research">
{["first", "second", "third"].map((column) => (
<div key={column} className="deck-column" aria-hidden="true">
<div className="deck-column-header space-y-3">
<div className="flex items-center gap-2">
<Skeleton className="size-4 shrink-0" />
<Skeleton className="h-4 w-32" />
</div>
<Skeleton className="h-3 w-24" />
<Skeleton className="h-3 w-40" />
<Skeleton className="h-3 w-20" />
</div>
<div className="deck-column-results">
<ResearchPostSkeleton />
<ResearchPostSkeleton />
<ResearchPostSkeleton />
</div>
</div>
))}
</div>
);
}