diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx
index a7f54e76..f443aab4 100644
--- a/apps/web/src/app/[locale]/page.tsx
+++ b/apps/web/src/app/[locale]/page.tsx
@@ -25,7 +25,7 @@ import { OpenClawPanel } from '@/components/ai/openclaw-panel'
import { HostGrid, type HostInfo, type HostService } from '@/components/infra/host-grid'
import { AppLayout } from '@/components/layout'
import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
-import { LobsterLoading } from '@/components/shared/lobster-loading'
+import { PulseSkeleton, CardSkeleton } from '@/components/shared/pulse-skeleton'
import { ServiceTopology } from '@/components/topology'
import { BarChart3, Flame, Telescope, FlaskConical, Activity, GitBranch } from 'lucide-react'
import { DispositionMini } from '@/components/shared/disposition-mini'
@@ -56,7 +56,7 @@ function AlertsAndApprovalsTab() {
}).finally(() => setLoading(false))
}, [])
- if (loading) return
+ if (loading) return
return (
@@ -172,7 +172,7 @@ function DispositionTab() {
.finally(() => setLoading(false))
}, [])
- if (loading) return
+ if (loading) return
const s = data?.summary
if (!s || s.total === 0) return (
@@ -730,7 +730,7 @@ export default function Home({ params }: { params: { locale: string } }) {
{isIncidentsLoading ? (
-
+
) : incidentsError ? (
{incidentsError}
) : (incidents?.length ?? 0) === 0 ? (
diff --git a/apps/web/src/components/shared/pulse-skeleton.tsx b/apps/web/src/components/shared/pulse-skeleton.tsx
new file mode 100644
index 00000000..a0cbdd85
--- /dev/null
+++ b/apps/web/src/components/shared/pulse-skeleton.tsx
@@ -0,0 +1,50 @@
+'use client'
+
+/**
+ * PulseSkeleton — 脈動骨架屏 (取代「載入中...」文字)
+ * Sprint 5R G1: Gemini 建議 — AI 呼吸感知
+ * @created 2026-04-09 Claude Opus 4.6 Asia/Taipei
+ */
+
+export function PulseSkeleton({ width = '100%', height = 16, borderRadius = 4, count = 1 }: {
+ width?: string | number
+ height?: number
+ borderRadius?: number
+ count?: number
+}) {
+ return (
+ <>
+
+
+ {Array.from({ length: count }).map((_, i) => (
+
+ ))}
+
+ >
+ )
+}
+
+/** 卡片級骨架屏 — 模擬 .card 結構 */
+export function CardSkeleton({ lines = 3 }: { lines?: number }) {
+ return (
+
+ )
+}