Yuki Takei пре 2 месеци
родитељ
комит
2069620574

+ 105 - 0
apps/app/.claude/skills/app-architecture/SKILL.md

@@ -0,0 +1,105 @@
+---
+name: app-architecture
+description: GROWI main application (apps/app) architecture, directory structure, and design patterns. Auto-invoked when working in apps/app.
+user-invocable: false
+---
+
+# App Architecture (apps/app)
+
+The main GROWI application is a **full-stack Next.js application** with Express.js backend and MongoDB database.
+
+For technology stack details, see the global `tech-stack` skill.
+
+## Directory Structure
+
+```
+apps/app/src/
+├── pages/                 # Next.js Pages Router (*.page.tsx)
+├── features/             # Feature modules (recommended for new code)
+│   └── {feature-name}/
+│       ├── index.ts      # Public exports
+│       ├── interfaces/   # TypeScript types
+│       ├── server/       # models/, routes/, services/
+│       └── client/       # components/, states/, hooks/
+├── server/               # Express server (legacy)
+│   ├── models/           # Mongoose models
+│   ├── routes/apiv3/     # RESTful API v3
+│   └── services/         # Business logic
+├── components/           # React components (legacy)
+├── states/               # Jotai atoms
+└── stores-universal/     # SWR hooks
+```
+
+## Feature-Based Architecture
+
+Organize code by **business feature** rather than by technical layer:
+
+```
+❌ Layer-based (old):          ✅ Feature-based (new):
+├── models/User.ts             ├── features/user/
+├── routes/user.ts             │   ├── server/models/User.ts
+├── components/UserList.tsx    │   ├── server/routes/user.ts
+                               │   └── client/components/UserList.tsx
+```
+
+### Creating a New Feature
+
+1. Create `features/{feature-name}/`
+2. Define interfaces in `interfaces/`
+3. Implement server logic in `server/` (models, routes, services)
+4. Implement client logic in `client/` (components, hooks, states)
+5. Export public API through `index.ts`
+
+## Entry Points
+
+- **Server**: `server/app.ts` - Express + Next.js initialization
+- **Client**: `pages/_app.page.tsx` - Jotai + SWR providers
+- **Wiki Pages**: `pages/[[...path]]/index.page.tsx` - Catch-all route (SSR)
+
+## API Design (RESTful API v3)
+
+Routes in `server/routes/apiv3/` with OpenAPI specs:
+
+```typescript
+/**
+ * @openapi
+ * /api/v3/pages/{id}:
+ *   get:
+ *     summary: Get page by ID
+ */
+router.get('/pages/:id', async (req, res) => {
+  const page = await PageService.findById(req.params.id);
+  res.json(page);
+});
+```
+
+## State Management
+
+- **Jotai**: UI state (modals, forms) in `states/`
+- **SWR**: Server data (pages, users) in `stores-universal/`
+
+For detailed patterns, see `app-specific-patterns` skill.
+
+## Design Principles
+
+1. **Feature Isolation**: New features self-contained in `features/`
+2. **Server-Client Separation**: Prevent server code bundled into client
+3. **API-First**: Define OpenAPI specs before implementation
+4. **Type-Driven**: Define interfaces before implementation
+5. **Progressive Migration**: Gradually move legacy code to `features/`
+
+## Legacy Migration
+
+Legacy directories (`components/`, `server/models/`, `client/`) should be gradually migrated to `features/`:
+
+- New features → `features/`
+- Bug fixes → Can stay in legacy
+- Refactoring → Move to `features/`
+
+## Summary
+
+1. **New features**: `features/{feature-name}/` structure
+2. **Server-client separation**: Keep separate
+3. **API-first**: OpenAPI specs for API v3
+4. **State**: Jotai (UI) + SWR (server data)
+5. **Progressive migration**: No rush for stable legacy code

+ 160 - 0
apps/app/.claude/skills/app-commands/SKILL.md

@@ -0,0 +1,160 @@
+---
+name: app-commands
+description: GROWI main application (apps/app) specific commands and scripts. Auto-invoked when working in apps/app.
+user-invocable: false
+---
+
+# App Commands (apps/app)
+
+Commands specific to the main GROWI application. For global commands (turbo, pnpm), see the global `tech-stack` skill.
+
+## Quick Reference
+
+| Task | Command |
+|------|---------|
+| **Migration** | `pnpm run dev:migrate` |
+| **OpenAPI generate** | `pnpm run openapi:generate-spec:apiv3` |
+| **REPL console** | `pnpm run console` |
+| **Visual regression** | `pnpm run reg:run` |
+| **Version bump** | `pnpm run version:patch` |
+
+## Database Migration
+
+```bash
+# Run pending migrations
+pnpm run dev:migrate
+
+# Check migration status
+pnpm run dev:migrate:status
+
+# Apply migrations
+pnpm run dev:migrate:up
+
+# Rollback last migration
+pnpm run dev:migrate:down
+
+# Production migration
+pnpm run migrate
+```
+
+**Note**: Migrations use `migrate-mongo`. Files are in `config/migrate-mongo/`.
+
+### Creating a New Migration
+
+```bash
+# Create migration file manually in config/migrate-mongo/
+# Format: YYYYMMDDHHMMSS-migration-name.js
+
+# Test migration cycle
+pnpm run dev:migrate:up
+pnpm run dev:migrate:down
+pnpm run dev:migrate:up
+```
+
+## OpenAPI Commands
+
+```bash
+# Generate OpenAPI spec for API v3
+pnpm run openapi:generate-spec:apiv3
+
+# Validate API v3 spec
+pnpm run lint:openapi:apiv3
+
+# Generate operation IDs
+pnpm run openapi:build:generate-operation-ids
+```
+
+Generated specs output to `tmp/openapi-spec-apiv3.json`.
+
+## Style Pre-build (Vite)
+
+```bash
+# Development mode
+pnpm run dev:pre:styles
+
+# Production mode
+pnpm run pre:styles
+```
+
+Pre-builds SCSS styles into CSS bundles using Vite.
+
+## Debug & Utility
+
+### REPL Console
+
+```bash
+pnpm run console
+# or
+pnpm run repl
+```
+
+Interactive Node.js REPL with Mongoose models loaded. Useful for debugging database queries.
+
+### Visual Regression Testing
+
+```bash
+pnpm run reg:run
+```
+
+## Version Commands
+
+```bash
+# Bump patch version (e.g., 7.4.3 → 7.4.4)
+pnpm run version:patch
+
+# Create prerelease (e.g., 7.4.4 → 7.4.5-RC.0)
+pnpm run version:prerelease
+
+# Create preminor (e.g., 7.4.4 → 7.5.0-RC.0)
+pnpm run version:preminor
+```
+
+## Production
+
+```bash
+# Start server (after build)
+pnpm run server
+
+# Start for CI environments
+pnpm run server:ci
+```
+
+**Note**: `preserver` hook automatically runs migrations before starting.
+
+## CI/CD
+
+```bash
+# Launch dev server for CI
+pnpm run launch-dev:ci
+
+# Start production server for CI
+pnpm run server:ci
+```
+
+## Environment Variables
+
+Development uses `dotenv-flow`:
+
+- `.env` - Default values
+- `.env.local` - Local overrides (not committed)
+- `.env.development` - Development-specific
+- `.env.production` - Production-specific
+
+See `.env.example` for available variables.
+
+## Troubleshooting
+
+### Migration Issues
+
+```bash
+pnpm run dev:migrate:status   # Check status
+pnpm run dev:migrate:down     # Rollback
+pnpm run dev:migrate:up       # Re-apply
+```
+
+### Build Issues
+
+```bash
+pnpm run clean                # Clear artifacts
+pnpm run build                # Rebuild
+```

+ 173 - 0
apps/app/.claude/skills/app-specific-patterns/SKILL.md

@@ -0,0 +1,173 @@
+---
+name: app-specific-patterns
+description: GROWI main application (apps/app) specific patterns for Next.js, Jotai, SWR, and testing. Auto-invoked when working in apps/app.
+user-invocable: false
+---
+
+# App Specific Patterns (apps/app)
+
+For general testing patterns, see the global `testing-patterns-with-vitest` skill.
+
+## Next.js Pages Router
+
+### File Naming
+
+Pages must use `.page.tsx` suffix:
+
+```
+pages/
+├── _app.page.tsx           # App wrapper
+├── [[...path]]/index.page.tsx  # Catch-all wiki pages
+└── admin/index.page.tsx    # Admin pages
+```
+
+### getLayout Pattern
+
+```typescript
+// pages/admin/index.page.tsx
+import type { NextPageWithLayout } from '~/interfaces/next-page';
+
+const AdminPage: NextPageWithLayout = () => <AdminDashboard />;
+
+AdminPage.getLayout = (page) => <AdminLayout>{page}</AdminLayout>;
+
+export default AdminPage;
+```
+
+## Jotai State Management
+
+### Directory Structure
+
+```
+src/states/
+├── ui/
+│   ├── sidebar/              # Multi-file feature
+│   ├── device.ts             # Single-file feature
+│   └── modal/                # 1 modal = 1 file
+│       ├── page-create.ts
+│       └── page-delete.ts
+├── page/                     # Page data state
+├── server-configurations/
+└── context.ts
+
+features/{name}/client/states/  # Feature-scoped atoms
+```
+
+### Placement Rules
+
+| Category | Location |
+|----------|----------|
+| UI state | `states/ui/` |
+| Modal state | `states/ui/modal/` (1 file per modal) |
+| Page data | `states/page/` |
+| Feature-specific | `features/{name}/client/states/` |
+
+### Derived Atoms
+
+```typescript
+import { atom } from 'jotai';
+
+export const currentPageAtom = atom<Page | null>(null);
+
+// Derived (read-only)
+export const currentPagePathAtom = atom((get) => {
+  return get(currentPageAtom)?.path ?? null;
+});
+```
+
+## SWR Data Fetching
+
+### Directory
+
+```
+src/stores-universal/
+├── pages.ts       # Page hooks
+├── users.ts       # User hooks
+└── admin/settings.ts
+```
+
+### Patterns
+
+```typescript
+import useSWR from 'swr';
+import useSWRImmutable from 'swr/immutable';
+
+// Auto-revalidation
+export const usePageList = () => useSWR<Page[]>('/api/v3/pages', fetcher);
+
+// No auto-revalidation (static data)
+export const usePageById = (id: string | null) =>
+  useSWRImmutable<Page>(id ? `/api/v3/pages/${id}` : null, fetcher);
+```
+
+## Testing (apps/app Specific)
+
+### Mocking Next.js Router
+
+```typescript
+import { mockDeep } from 'vitest-mock-extended';
+import type { NextRouter } from 'next/router';
+
+const createMockRouter = (overrides = {}) => {
+  const mock = mockDeep<NextRouter>();
+  mock.pathname = '/test';
+  mock.push.mockResolvedValue(true);
+  return Object.assign(mock, overrides);
+};
+
+vi.mock('next/router', () => ({
+  useRouter: () => createMockRouter(),
+}));
+```
+
+### Testing with Jotai
+
+```typescript
+import { Provider } from 'jotai';
+import { useHydrateAtoms } from 'jotai/utils';
+
+const HydrateAtoms = ({ initialValues, children }) => {
+  useHydrateAtoms(initialValues);
+  return children;
+};
+
+const renderWithJotai = (ui, initialValues = []) => render(
+  <Provider>
+    <HydrateAtoms initialValues={initialValues}>{ui}</HydrateAtoms>
+  </Provider>
+);
+
+// Usage
+renderWithJotai(<PageHeader />, [[currentPageAtom, mockPage]]);
+```
+
+### Testing SWR
+
+```typescript
+import { SWRConfig } from 'swr';
+
+const wrapper = ({ children }) => (
+  <SWRConfig value={{ dedupingInterval: 0, provider: () => new Map() }}>
+    {children}
+  </SWRConfig>
+);
+
+const { result } = renderHook(() => usePageById('123'), { wrapper });
+```
+
+## Path Aliases
+
+Always use `~/` for imports:
+
+```typescript
+import { PageService } from '~/server/services/PageService';
+import { currentPageAtom } from '~/states/page/page-atoms';
+```
+
+## Summary
+
+1. **Next.js**: `.page.tsx` suffix, `getLayout` for layouts
+2. **Jotai**: `states/` global, `features/*/client/states/` feature-scoped
+3. **SWR**: `stores-universal/`, null key for conditional fetch
+4. **Testing**: Mock router, hydrate Jotai, wrap SWR config
+5. **Imports**: Always `~/` path alias