Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
41 changes: 15 additions & 26 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,40 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

MX Admin (admin-vue3) is the dashboard for MX Space, a personal blog management system. Built with Vue 3, Naive UI, and UnoCSS. This is the v4.0 admin interface for Mix Space Server v5.0.
MX Admin is the dashboard for MX Space, a personal blog management system. The active admin app is a React application built with Base UI primitives, React Router, TanStack Query, Sonner, and UnoCSS.

## Development Commands

```bash
pnpm install # Install dependencies
pnpm dev # Start development server (opens browser automatically)
pnpm build # Build for production
pnpm lint # Lint code with Biome
pnpm lint # Lint code with oxlint
pnpm lint:fix # Lint and auto-fix
npx tsc --noEmit # Type check (use this instead of build for validation)
pnpm -C apps/admin exec tsc --noEmit --pretty false
```

## Architecture Overview

### Technology Stack

- **Vue 3** with Composition API and TSX (JSX via `@vitejs/plugin-vue-jsx`)
- **Naive UI** - Component library with Vercel-style neutral theme
- **React** with TSX
- **Base UI** - Headless component primitives
- **React Router** - Route rendering and shell navigation
- **UnoCSS** (preset-wind4) - Tailwind-compatible utility classes
- **Pinia** - State management
- **TanStack Query** (`@tanstack/vue-query`) - Server state management with localStorage persistence
- **TanStack Query** (`@tanstack/react-query`) - Server state management
- **Sonner** - Toast notifications
- **Socket.IO** - Real-time WebSocket updates
- **CodeMirror/Monaco** - Code editors

### Path Aliases

```typescript
import { something } from '~/utils/...' // ~ maps to ./src
```

### API Layer (`src/api/`)
### API Layer (`src/app/api/`)

API services use the custom request layer built on `ofetch`. The backend wraps array responses as `{ data: [...] }`, which is automatically unwrapped by the request layer.
React app API services use the fetch-based helpers in `src/app/api/http.ts`.

When using TanStack Query, extract arrays with:
```typescript
Expand All @@ -48,14 +48,6 @@ select: (res: any) => Array.isArray(res) ? res : res?.data ?? []
- `BusinessError` - Application-level errors (4xx responses)
- `SystemError` - Network/server errors (5xx responses, network failures)

### State Management

**Pinia Stores (`src/stores/`):**
- `useUIStore` - Theme mode (light/dark/system), viewport dimensions, sidebar state
- `useUserStore` - User authentication state
- `useAppStore` - Global application state
- `useCategoryStore` - Category data

### Responsive Breakpoints (UnoCSS)

- `phone:` - max-width: 768px
Expand All @@ -66,7 +58,7 @@ select: (res: any) => Array.isArray(res) ? res : res?.data ?? []

### Validation

After modifying code, run type check only (`npx tsc --noEmit`). Do not run `pnpm build` for validation.
After modifying code, run focused type checking and linting. Run production build before reporting completion for broad application changes.

### Gray Scale Colors

Expand All @@ -92,8 +84,8 @@ See `docs/typography.md` for full guidelines.
## Configuration Files

- `uno.config.ts` - UnoCSS configuration with custom breakpoints and theme colors
- `src/utils/color.ts` - Naive UI theme overrides (Vercel-style neutral gray palette)
- `biome.json` - Linter/formatter configuration with Vue globals
- `src/app/theme.ts` - CSS token installation for the React shell
- `src/app/` - React routes, shell, API helpers, UI primitives, and migrated views
- `.env` - Local dev API endpoint (`VITE_APP_BASE_API`)

## Related Projects
Expand All @@ -102,9 +94,6 @@ See `docs/typography.md` for full guidelines.
- **Shiroi** — Next.js frontend (blog), located at `../Shiroi`
- **haklex** — Rich editor packages (`@haklex/*`), located at `../haklex` (standalone) or `../Shiroi/haklex` (original host)

### Rich Editor Integration (React-in-Vue)
### Rich Editor Integration

admin-vue3 is a Vue 3 project but embeds the React-based haklex editor via a bridge pattern in `src/components/editor/rich/RichEditor.tsx`:
- Uses `createRoot()` from `react-dom/client` inside Vue `defineComponent` to mount the local `ShiroEditor` (`packages/rich-react/src/shiro/`)
- Local Shiro lives in `packages/rich-react/src/shiro/` — composes `@haklex/rich-editor` + per-feature `@haklex/rich-ext-*` / `@haklex/rich-renderer-*` packages directly
- All `@haklex/*` packages are pinned npm versions (not workspace links). After haklex releases, update versions here.
The admin app no longer mounts rich editor surfaces through a framework bridge. React editor work should be integrated as ordinary React components and kept out of compatibility shims.
10 changes: 5 additions & 5 deletions apps/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
link.href = favicon
document.head.appendChild(link)
</script>
<title>Mx Space Admin Vue 3 v2</title>
<title>Mx Space Admin</title>
<script>
window.injectData = {}
window.version = 'N/A'
Expand Down Expand Up @@ -98,15 +98,15 @@
</div>
<noscript>
<strong
>We're sorry but MX Space Admin Vue 3 doesn't work properly without
JavaScript enabled. Please enable it to continue.</strong
>We're sorry but MX Space Admin doesn't work properly without JavaScript
enabled. Please enable it to continue.</strong
>
<strong>
It may be a network problem that caused the failure to load the JS file.
</strong>
</noscript>
<script>
// Initialize theme before Vue loads to prevent flash
// Initialize theme before the application loads to prevent flash
;(function () {
var themeMode = localStorage.getItem('theme-mode')
// Remove quotes if stored as JSON string
Expand All @@ -124,6 +124,6 @@
}
})()
</script>
<script type="module" src="/src/main.ts"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
70 changes: 13 additions & 57 deletions apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,100 +12,57 @@
},
"dependencies": {
"@antv/g2": "^5.4.8",
"@base-ui/react": "1.5.0",
"@better-auth/passkey": "1.4.18",
"@bytebase/vue-kbar": "0.1.8",
"@codemirror/commands": "6.10.3",
"@codemirror/lang-markdown": "6.5.0",
"@codemirror/language": "6.12.3",
"@codemirror/language-data": "6.5.2",
"@codemirror/search": "6.7.0",
"@codemirror/state": "6.6.0",
"@codemirror/theme-one-dark": "6.1.3",
"@codemirror/view": "6.42.1",
"@ddietr/codemirror-themes": "1.5.2",
"@emoji-mart/data": "1.2.1",
"@excalidraw/excalidraw": "^0.18.0",
"@haklex/rich-agent-chat": "0.8.0",
"@haklex/rich-agent-core": "0.8.0",
"@haklex/rich-diff": "0.8.0",
"@haklex/rich-editor": "0.8.0",
"@haklex/rich-ext-ai-agent": "0.8.0",
"@haklex/rich-ext-nested-doc": "0.8.0",
"@haklex/rich-style-token": "0.8.0",
"@lexical/code-core": "^0.44.0",
"@lexical/markdown": "^0.44.0",
"@lezer/highlight": "1.2.3",
"@mx-admin/rich-react": "workspace:*",
"@pierre/diffs": "1.1.3",
"@simplewebauthn/browser": "13.3.0",
"@tanstack/query-async-storage-persister": "5.95.0",
"@tanstack/query-persist-client-core": "5.95.0",
"@tanstack/vue-query": "5.95.0",
"@tanstack/react-query": "5.100.13",
"@types/canvas-confetti": "1.9.0",
"@typescript/ata": "0.9.8",
"@vicons/utils": "0.1.4",
"@vueuse/core": "14.2.1",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"ansi_up": "6.0.6",
"better-auth": "1.4.18",
"blurhash": "2.0.5",
"buffer": "6.0.3",
"canvas-confetti": "1.9.4",
"date-fns": "4.1.0",
"ejs": "4.0.1",
"emoji-mart": "5.6.0",
"ejs": "5.0.2",
"es-toolkit": "1.45.1",
"event-source-polyfill": "1.0.31",
"fuse.js": "7.1.0",
"highlight.js": "11.11.1",
"js-cookie": "3.0.5",
"js-yaml": "4.1.1",
"json5": "2.2.3",
"jsondiffpatch": "0.7.3",
"katex": "0.16.40",
"lexical": "^0.44.0",
"lit": "3.3.2",
"lodash.transform": "4.6.0",
"lucide-vue-next": "0.574.0",
"markdown-escape": "2.0.0",
"lucide-react": "1.8.0",
"marked": "17.0.5",
"monaco-editor": "0.55.1",
"naive-ui": "2.44.1",
"octokit": "5.0.5",
"ofetch": "1.5.1",
"openai": "6.32.0",
"os-browserify": "0.3.0",
"path-browserify": "1.0.1",
"pinia": "3.0.4",
"qier-progress": "1.0.4",
"qs": "6.15.0",
"shiki": "3.21.0",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-resizable-panels": "4.11.1",
"react-router": "7.15.1",
"socket.io-client": "4.8.3",
"sortablejs": "1.15.7",
"umi-request": "1.4.0",
"sonner": "2.0.7",
"validator": "13.15.26",
"vue": "3.5.30",
"vue-router": "4.6.4",
"vue-sonner": "2.0.9",
"xss": "1.0.15",
"xterm-theme": "1.1.0",
"zod": "4.3.6"
},
"devDependencies": {
"@types/ejs": "3.1.5",
"@types/event-source-polyfill": "1.0.5",
"@types/js-yaml": "4.0.9",
"@types/markdown-escape": "1.1.3",
"@types/qs": "6.15.0",
"@types/sortablejs": "1.15.9",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/validator": "13.15.10",
"@unocss/postcss": "^66.6.8",
"@unocss/preset-typography": "66.6.7",
"@vitejs/plugin-vue": "6.0.5",
"@vitejs/plugin-vue-jsx": "5.1.5",
"@vue/compiler-sfc": "3.5.30",
"@vue/test-utils": "^2.4.0",
"@vitejs/plugin-react": "6.0.2",
"code-inspector-plugin": "1.5.1",
"cors": "2.8.6",
"happy-dom": "^15.11.0",
"postcss": "8.5.8",
Expand All @@ -117,7 +74,6 @@
"vite": "8.0.1",
"vite-plugin-checker": "0.12.0",
"vite-plugin-mkcert": "1.17.10",
"vite-plugin-vue-inspector": "5.4.0",
"vitest": "^4.1.5"
}
}
Loading
Loading