refactor: 统一应用架构 — 消除前后台割裂,引入全局侧边栏、命令面板和 Motion 动效

- 移除独立的 /admin 路由层,路由扁平化为 /bookmarks
- 用 AppSidebar 替代 AdminSidebar,SidebarProvider 提升至 _protected 全局布局
- 新增 ⌘K 命令面板(cmdk + @tanstack/react-hotkeys),支持书签搜索、搜索引擎跳转和页面导航
- 书签页查看/管理一体化,通过编辑模式开关切换,AnimatePresence 平滑过渡
- 总览驾驶舱:Motion stagger 入场动画、常用书签快捷区、模块概览卡片
- 统一设计语言:BookmarkCard/CategoryGrid 用 design token 替代硬编码 stone 色
- ModuleMetadata.adminRoute 重命名为 route
- 同步更新 AGENTS.md 文档
This commit is contained in:
2026-03-31 21:36:44 +08:00
parent 588df9f143
commit a369fe853e
19 changed files with 973 additions and 283 deletions
+67 -13
View File
@@ -15,6 +15,9 @@ TanStack Start fullstack web app with ORPC (contract-first RPC) and shadcn/ui.
- **CLI**: citty (server-side admin commands)
- **DnD**: @dnd-kit/react + @dnd-kit/helpers (`move()` for sortable)
- **Virtualization**: @tanstack/react-virtual (`useVirtualizer`)
- **Hotkeys**: @tanstack/react-hotkeys (`useHotkey` for type-safe keyboard shortcuts)
- **Animation**: motion (page transitions, staggered entrance, layout animation)
- **Command Palette**: cmdk (via shadcn Command component, triggered by ⌘K)
- **Build**: Vite + Nitro
## Architecture Overview
@@ -22,22 +25,21 @@ TanStack Start fullstack web app with ORPC (contract-first RPC) and shadcn/ui.
### Route Architecture
```
/ → Dashboard homepage (bookmark display, daily use)
/admin → Admin panel overview (module cards)
/admin/bookmarks → Bookmark management (CRUD, DnD, Dialog forms)
/ → Dashboard overview (greeting, quick bookmarks, module summary)
/bookmarks → Bookmarks page (view mode + edit mode toggle)
/setup → One-time owner setup (first visit only, redirects to /login after)
/login → Login page (redirects to /setup if no owner exists)
```
- **Unified shell**: All authenticated pages share a global sidebar (`AppSidebar`) and command palette (`⌘K`). There is NO separate admin panel — view and management are integrated in each module page.
- **Single-owner model**: Kairos is a self-hosted Life OS. Only ONE user (the owner) exists. There is NO registration page — `/setup` is a one-time wizard shown on first visit.
- **Display pages** (`/`): Clean, no management UI. What users see daily.
- **Admin pages** (`/admin/*`): Full CRUD, management, configuration. Sidebar navigation.
- All authenticated routes under `_protected` layout (auth guard → redirect to `/login`).
- **Module pages** (`/bookmarks`, etc.): Each module page has a view mode (clean display) and an edit mode (CRUD, DnD). Toggle via a button in the page header.
- All authenticated routes under `_protected` layout (auth guard + SidebarProvider + CommandPalette → redirect to `/login`).
### Module System
Modules are directory-based under `src/modules/`. Each module provides:
- `index.ts``ModuleMetadata` (id, name, icon, adminRoute)
- `index.ts``ModuleMetadata` (id, name, icon, route)
- `schema.ts` — Drizzle tables
- `contract.ts` — ORPC contracts (input/output Zod schemas)
- `router.ts` — ORPC handlers (business logic)
@@ -54,7 +56,8 @@ src/
├── client/
│ └── orpc.ts # ORPC client (isomorphic: SSR direct call / CSR fetch)
├── components/
│ ├── AdminSidebar.tsx # Admin sidebar (reads module registry)
│ ├── AppSidebar.tsx # Unified sidebar (reads module registry, collapsible)
│ ├── CommandPalette.tsx # ⌘K command palette (search bookmarks, engines, navigation)
│ ├── Error.tsx # Error boundary fallback
│ ├── NotFound.tsx # 404 fallback
│ └── ui/ # shadcn/ui components (可自由修改,添加新组件用 bunx shadcn@latest add)
@@ -70,11 +73,10 @@ src/
│ └── commands/auth.ts # auth reset-password command
├── routes/ # TanStack Router file routes
│ ├── __root.tsx # Root layout (HTML shell, Toaster)
│ ├── _protected.tsx # Auth guard layout
│ ├── _protected.tsx # Auth guard + unified shell (SidebarProvider, AppSidebar, CommandPalette)
│ ├── _protected/
│ │ ├── index.tsx # Dashboard homepage
│ │ ── admin.tsx # Admin layout (SidebarProvider)
│ │ └── admin/bookmarks.tsx
│ │ ├── index.tsx # Dashboard overview (greeting, quick bookmarks, module cards)
│ │ ── bookmarks.tsx # Bookmarks page (view/edit toggle, Motion animations)
│ ├── login.tsx, setup.tsx
│ └── api/ # $.ts (OpenAPI), auth.$.ts, health.ts, rpc.$.ts
├── server/
@@ -171,7 +173,7 @@ shadcn/ui uses `@base-ui/react`. The `render` prop replaces Radix's `asChild`:
```typescript
// ✅ CORRECT
<DialogTrigger render={<Button />} />
<SidebarMenuButton render={<Link to="/admin" />}>
<SidebarMenuButton render={<Link to="/bookmarks" />}>
// ❌ WRONG — asChild does NOT exist
<DialogTrigger asChild><Button /></DialogTrigger>
@@ -244,6 +246,55 @@ toast.success('操作成功')
toast.error('操作失败')
```
### Motion Animations
Use `motion` for page transitions, staggered entrance, and layout animations:
```typescript
import { AnimatePresence } from 'motion/react'
import * as motion from 'motion/react-client'
const containerVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.06, delayChildren: 0.1 } },
}
const itemVariants = {
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0, transition: { duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] as const } },
}
<motion.div variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.div key={item.id} variants={itemVariants}>...</motion.div>
))}
</motion.div>
// AnimatePresence for mode switching (e.g., view ↔ edit)
<AnimatePresence mode="wait">
{editing ? (
<motion.div key="edit" initial={{ opacity: 0, scale: 0.98 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.98 }}>
...
</motion.div>
) : (
<motion.div key="view" ...>...</motion.div>
)}
</AnimatePresence>
```
### Keyboard Shortcuts (TanStack Hotkeys)
```typescript
import { useHotkey } from '@tanstack/react-hotkeys'
useHotkey('Mod+K', () => openCommandPalette()) // ⌘K on Mac, Ctrl+K on Windows
useHotkey('Mod+S', () => save())
```
### Command Palette (⌘K)
Global command palette in `_protected` layout. Uses shadcn `CommandDialog` + `@tanstack/react-hotkeys`:
- Search bookmarks by name/URL/category
- Search engine shortcuts: `/g query`, `/gh query`, `/yt query`
- Page navigation: 总览, 书签导航
- Quick actions: 管理书签
## Database (Drizzle ORM 0.45.x)
- **Driver**: `drizzle-orm/postgres-js` (NOT `bun-sql`)
@@ -300,6 +351,8 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- Use callback syntax for `orderBy` and `where` in relational queries
- Use `move()` from `@dnd-kit/helpers` for DnD reordering
- Use `useState` callback ref for virtualizer scroll elements inside Dialogs
- Use `motion` for page transitions and staggered entrance animations
- Use `useHotkey` from `@tanstack/react-hotkeys` for keyboard shortcuts
**DON'T:**
- Add new `src/components/ui/*.tsx` without CLI (use `bunx shadcn@latest add` to scaffold, then freely customize)
@@ -313,3 +366,4 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- Use `db:push` — always use `db:generate``db:migrate`
- Use `@/*` aliases in Drizzle schema files (drizzle-kit can't resolve them)
- Add registration/signup functionality (single-owner model, enforced by `databaseHooks`)
- Create separate admin pages — integrate view/edit modes in each module page