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:
+67
-13
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user