From 99285d1663858602f071701c95414fee0c7b00ac Mon Sep 17 00:00:00 2001 From: Asep Haryana Saputra <90584806+MythEclipse@users.noreply.github.com> Date: Fri, 22 May 2026 13:05:59 +0000 Subject: [PATCH] Finalize scaffold lockfile and implementation plan --- bun.lock | 18 +- .../2026-05-22-fullstack-monorepo-scaffold.md | 1325 +++++++++++++++++ package.json | 8 +- 3 files changed, 1337 insertions(+), 14 deletions(-) mode change 100644 => 100755 bun.lock create mode 100644 docs/superpowers/plans/2026-05-22-fullstack-monorepo-scaffold.md diff --git a/bun.lock b/bun.lock old mode 100644 new mode 100755 index 827ea9f..bf7dbd9 --- a/bun.lock +++ b/bun.lock @@ -1,9 +1,7 @@ { - "lockfileVersion": 1, - "configVersion": 1, + "lockfileVersion": 0, "workspaces": { "": { - "name": "zeavis-edu", "devDependencies": { "@moonrepo/cli": "^1.32.4", "typescript": "^5.6.3", @@ -13,7 +11,7 @@ "name": "@zeavis/api", "version": "0.1.0", "dependencies": { - "@zeavis/shared": "workspace:*", + "@zeavis/shared": "packages/shared", "drizzle-orm": "^0.36.4", "elysia": "^1.1.25", "postgres": "^3.4.5", @@ -31,7 +29,7 @@ "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-query": "^5.59.16", "@vitejs/plugin-react": "^4.3.3", - "@zeavis/shared": "workspace:*", + "@zeavis/shared": "packages/shared", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.468.0", @@ -282,11 +280,11 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "@zeavis/api": ["@zeavis/api@workspace:apps/api"], + "@zeavis/api": ["@zeavis/api@workspace:apps/api", { "dependencies": { "@zeavis/shared": "packages/shared", "drizzle-orm": "^0.36.4", "elysia": "^1.1.25", "postgres": "^3.4.5" }, "devDependencies": { "@types/bun": "latest", "drizzle-kit": "^0.27.1", "typescript": "^5.6.3" } }], - "@zeavis/shared": ["@zeavis/shared@workspace:packages/shared"], + "@zeavis/shared": ["@zeavis/shared@workspace:packages/shared", { "devDependencies": { "typescript": "^5.6.3" } }], - "@zeavis/web": ["@zeavis/web@workspace:apps/web"], + "@zeavis/web": ["@zeavis/web@workspace:apps/web", { "dependencies": { "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-query": "^5.59.16", "@vitejs/plugin-react": "^4.3.3", "@zeavis/shared": "packages/shared", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.468.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0", "tailwind-merge": "^2.5.4", "zustand": "^5.0.1" }, "devDependencies": { "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "typescript": "^5.6.3", "vite": "^6.0.1" } }], "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], @@ -328,7 +326,7 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -338,7 +336,7 @@ "drizzle-kit": ["drizzle-kit@0.27.2", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-F6cFZ1wxa9XzFyeeQsp/0/lIzUbDuQjS8/njpYBDWa+wdWmXuY+Z/X2hHFK/9PGHZkv3c9mER+mVWfKlp/B6Vw=="], - "drizzle-orm": ["drizzle-orm@0.36.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "prisma": "*", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "react", "sql.js", "sqlite3"] }, "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA=="], + "drizzle-orm": ["drizzle-orm@0.36.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA=="], "electron-to-chromium": ["electron-to-chromium@1.5.361", "", {}, "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA=="], diff --git a/docs/superpowers/plans/2026-05-22-fullstack-monorepo-scaffold.md b/docs/superpowers/plans/2026-05-22-fullstack-monorepo-scaffold.md new file mode 100644 index 0000000..d86df5f --- /dev/null +++ b/docs/superpowers/plans/2026-05-22-fullstack-monorepo-scaffold.md @@ -0,0 +1,1325 @@ +# Fullstack Monorepo Scaffold Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the Bun + Moon fullstack TypeScript scaffold for ZeaVis Edu beside the existing `Machine_Learning/` pipeline. + +**Architecture:** Add a root Bun workspace managed by Moon with three projects: `apps/web`, `apps/api`, and `packages/shared`. The frontend is a React/Vite app with landing and dashboard routes; the backend is an Elysia API with health/status endpoints and Drizzle/PostgreSQL configuration that does not require a live database for startup. + +**Tech Stack:** Bun, Moon, TypeScript, React, Vite, React Router, TanStack Query, Zustand, Tailwind CSS, shadcn/ui-style components, Elysia, Drizzle ORM, PostgreSQL. + +--- + +## File Structure + +Create these root files: + +- `package.json`: Bun workspaces, package manager marker, root scripts delegating to Moon. +- `bunfig.toml`: Bun install/runtime defaults. +- `tsconfig.base.json`: shared strict TypeScript compiler options. +- `.env.example`: app ports and `DATABASE_URL` documentation. +- `.gitignore`: ignore JS/TS dependency, build, env, and Moon cache outputs while preserving existing ML files. +- `.moon/workspace.yml`: Moon workspace configuration. +- `.moon/toolchain.yml`: Bun/Node toolchain configuration. + +Create `packages/shared`: + +- `packages/shared/package.json`: package metadata and export paths. +- `packages/shared/tsconfig.json`: TypeScript config extending root base. +- `packages/shared/src/index.ts`: shared exports. +- `packages/shared/src/app-status.ts`: shared app status response type and factory. + +Create `apps/api`: + +- `apps/api/package.json`: API dependencies and scripts. +- `apps/api/tsconfig.json`: API TypeScript config. +- `apps/api/drizzle.config.ts`: Drizzle Kit config using `DATABASE_URL`. +- `apps/api/src/index.ts`: Elysia server entry. +- `apps/api/src/config/env.ts`: env helpers for port and database URL. +- `apps/api/src/routes/health.ts`: `/health` route. +- `apps/api/src/routes/status.ts`: `/api/v1/status` route. +- `apps/api/src/db/schema.ts`: minimal schema placeholder. +- `apps/api/src/db/client.ts`: Drizzle client factory that is not used at server boot. + +Create `apps/web`: + +- `apps/web/package.json`: frontend dependencies and scripts. +- `apps/web/tsconfig.json`: app TypeScript config. +- `apps/web/tsconfig.node.json`: Vite config TypeScript config. +- `apps/web/vite.config.ts`: Vite React config. +- `apps/web/index.html`: Vite HTML entry. +- `apps/web/postcss.config.js`: PostCSS config for Tailwind. +- `apps/web/tailwind.config.ts`: Tailwind content/theme config. +- `apps/web/src/main.tsx`: React root render. +- `apps/web/src/app.tsx`: providers and router wiring. +- `apps/web/src/index.css`: Tailwind layers and theme variables. +- `apps/web/src/lib/utils.ts`: `cn` utility. +- `apps/web/src/store/ui-store.ts`: small Zustand UI store. +- `apps/web/src/components/ui/button.tsx`: shadcn-style button. +- `apps/web/src/components/ui/card.tsx`: shadcn-style card. +- `apps/web/src/pages/landing-page.tsx`: landing route. +- `apps/web/src/pages/dashboard-page.tsx`: placeholder dashboard route. + +Create Moon project files: + +- `apps/web/moon.yml`: web tasks. +- `apps/api/moon.yml`: api tasks. +- `packages/shared/moon.yml`: shared tasks. + +--- + +### Task 1: Root Workspace and Moon Configuration + +**Files:** +- Create: `package.json` +- Create: `bunfig.toml` +- Create: `tsconfig.base.json` +- Create: `.env.example` +- Create: `.gitignore` +- Create: `.moon/workspace.yml` +- Create: `.moon/toolchain.yml` + +- [ ] **Step 1: Create root package configuration** + +Create `package.json`: + +```json +{ + "name": "zeavis-edu", + "private": true, + "packageManager": "bun@1.1.42", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "dev": "moon run :dev", + "build": "moon run :build", + "typecheck": "moon run :typecheck" + }, + "devDependencies": { + "@moonrepo/cli": "^1.32.4", + "typescript": "^5.6.3" + } +} +``` + +- [ ] **Step 2: Create Bun config** + +Create `bunfig.toml`: + +```toml +[install] +exact = true +``` + +- [ ] **Step 3: Create shared TypeScript base config** + +Create `tsconfig.base.json`: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@zeavis/shared": ["packages/shared/src/index.ts"], + "@zeavis/shared/*": ["packages/shared/src/*"] + } + } +} +``` + +- [ ] **Step 4: Create environment example** + +Create `.env.example`: + +```dotenv +WEB_PORT=5173 +API_PORT=3000 +DATABASE_URL=postgres://postgres:postgres@localhost:5432/zeavis_edu +``` + +- [ ] **Step 5: Create root gitignore** + +Create `.gitignore`: + +```gitignore +node_modules/ +.bun/ +.moon/cache/ +.env +.env.* +!.env.example + +dist/ +build/ +coverage/ +*.tsbuildinfo + +.DS_Store +``` + +- [ ] **Step 6: Create Moon workspace config** + +Create `.moon/workspace.yml`: + +```yaml +projects: + web: apps/web + api: apps/api + shared: packages/shared +``` + +- [ ] **Step 7: Create Moon toolchain config** + +Create `.moon/toolchain.yml`: + +```yaml +node: + packageManager: bun +``` + +- [ ] **Step 8: Verify root config files are present** + +Run: `test -f package.json && test -f .moon/workspace.yml && test -f tsconfig.base.json` + +Expected: command exits with status 0 and no output. + +- [ ] **Step 9: Commit root workspace config** + +```bash +git add package.json bunfig.toml tsconfig.base.json .env.example .gitignore .moon/workspace.yml .moon/toolchain.yml +git commit -m "Add Bun and Moon workspace configuration" +``` + +--- + +### Task 2: Shared Package + +**Files:** +- Create: `packages/shared/package.json` +- Create: `packages/shared/tsconfig.json` +- Create: `packages/shared/src/index.ts` +- Create: `packages/shared/src/app-status.ts` +- Create: `packages/shared/moon.yml` + +- [ ] **Step 1: Create shared package metadata** + +Create `packages/shared/package.json`: + +```json +{ + "name": "@zeavis/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./app-status": "./src/app-status.ts" + }, + "scripts": { + "typecheck": "tsc --project tsconfig.json" + }, + "devDependencies": { + "typescript": "^5.6.3" + } +} +``` + +- [ ] **Step 2: Create shared TypeScript config** + +Create `packages/shared/tsconfig.json`: + +```json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022"], + "types": [] + }, + "include": ["src"] +} +``` + +- [ ] **Step 3: Create shared app status type** + +Create `packages/shared/src/app-status.ts`: + +```ts +export type AppStatus = { + name: 'ZeaVis Edu'; + status: 'ok'; + version: string; +}; + +export function createAppStatus(version = '0.1.0'): AppStatus { + return { + name: 'ZeaVis Edu', + status: 'ok', + version, + }; +} +``` + +- [ ] **Step 4: Create shared package export** + +Create `packages/shared/src/index.ts`: + +```ts +export type { AppStatus } from './app-status'; +export { createAppStatus } from './app-status'; +``` + +- [ ] **Step 5: Create shared Moon tasks** + +Create `packages/shared/moon.yml`: + +```yaml +type: library +language: typescript + +tasks: + typecheck: + command: bun run typecheck + inputs: + - src/**/* + - tsconfig.json + - package.json +``` + +- [ ] **Step 6: Run shared package typecheck after dependencies are installed** + +Run after Task 5 Step 1 has installed dependencies: `bun --cwd packages/shared run typecheck` + +Expected: TypeScript exits with status 0. + +- [ ] **Step 7: Commit shared package** + +```bash +git add packages/shared +git commit -m "Add shared TypeScript package" +``` + +--- + +### Task 3: Elysia API Scaffold + +**Files:** +- Create: `apps/api/package.json` +- Create: `apps/api/tsconfig.json` +- Create: `apps/api/drizzle.config.ts` +- Create: `apps/api/src/index.ts` +- Create: `apps/api/src/config/env.ts` +- Create: `apps/api/src/routes/health.ts` +- Create: `apps/api/src/routes/status.ts` +- Create: `apps/api/src/db/schema.ts` +- Create: `apps/api/src/db/client.ts` +- Create: `apps/api/moon.yml` + +- [ ] **Step 1: Create API package metadata** + +Create `apps/api/package.json`: + +```json +{ + "name": "@zeavis/api", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --watch src/index.ts", + "start": "bun src/index.ts", + "typecheck": "tsc --project tsconfig.json", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate" + }, + "dependencies": { + "@zeavis/shared": "workspace:*", + "drizzle-orm": "^0.36.4", + "elysia": "^1.1.25", + "postgres": "^3.4.5" + }, + "devDependencies": { + "drizzle-kit": "^0.27.1", + "typescript": "^5.6.3" + } +} +``` + +- [ ] **Step 2: Create API TypeScript config** + +Create `apps/api/tsconfig.json`: + +```json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022"], + "types": ["bun"], + "noEmit": true + }, + "include": ["src", "drizzle.config.ts"] +} +``` + +- [ ] **Step 3: Create API environment helper** + +Create `apps/api/src/config/env.ts`: + +```ts +export const env = { + port: Number(Bun.env.API_PORT ?? 3000), + databaseUrl: Bun.env.DATABASE_URL, +}; +``` + +- [ ] **Step 4: Create health route** + +Create `apps/api/src/routes/health.ts`: + +```ts +import { Elysia } from 'elysia'; + +export const healthRoutes = new Elysia().get('/health', () => ({ + status: 'ok' as const, +})); +``` + +- [ ] **Step 5: Create status route using shared package** + +Create `apps/api/src/routes/status.ts`: + +```ts +import { createAppStatus } from '@zeavis/shared'; +import { Elysia } from 'elysia'; + +export const statusRoutes = new Elysia({ prefix: '/api/v1' }).get('/status', () => + createAppStatus(), +); +``` + +- [ ] **Step 6: Create Drizzle schema placeholder** + +Create `apps/api/src/db/schema.ts`: + +```ts +import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core'; + +export const appEvents = pgTable('app_events', { + id: uuid('id').primaryKey().defaultRandom(), + name: varchar('name', { length: 120 }).notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), +}); +``` + +- [ ] **Step 7: Create Drizzle client factory** + +Create `apps/api/src/db/client.ts`: + +```ts +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import { env } from '../config/env'; +import * as schema from './schema'; + +export function createDbClient() { + if (!env.databaseUrl) { + throw new Error('DATABASE_URL is required to create a database client'); + } + + const client = postgres(env.databaseUrl); + return drizzle(client, { schema }); +} +``` + +- [ ] **Step 8: Create Drizzle config** + +Create `apps/api/drizzle.config.ts`: + +```ts +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/db/schema.ts', + out: './drizzle', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL ?? 'postgres://postgres:postgres@localhost:5432/zeavis_edu', + }, +}); +``` + +- [ ] **Step 9: Create Elysia server entry** + +Create `apps/api/src/index.ts`: + +```ts +import { Elysia } from 'elysia'; +import { env } from './config/env'; +import { healthRoutes } from './routes/health'; +import { statusRoutes } from './routes/status'; + +const app = new Elysia().use(healthRoutes).use(statusRoutes).listen(env.port); + +console.log(`ZeaVis Edu API running at http://${app.server?.hostname}:${app.server?.port}`); + +export type App = typeof app; +``` + +- [ ] **Step 10: Create API Moon tasks** + +Create `apps/api/moon.yml`: + +```yaml +type: application +language: typescript + +tasks: + dev: + command: bun run dev + local: true + build: + command: bun build src/index.ts --outdir dist --target bun + inputs: + - src/**/* + - package.json + - tsconfig.json + - drizzle.config.ts + outputs: + - dist + typecheck: + command: bun run typecheck + inputs: + - src/**/* + - package.json + - tsconfig.json + - drizzle.config.ts +``` + +- [ ] **Step 11: Commit API scaffold** + +```bash +git add apps/api +git commit -m "Add Elysia API scaffold" +``` + +--- + +### Task 4: React Web Scaffold + +**Files:** +- Create: `apps/web/package.json` +- Create: `apps/web/tsconfig.json` +- Create: `apps/web/tsconfig.node.json` +- Create: `apps/web/vite.config.ts` +- Create: `apps/web/index.html` +- Create: `apps/web/postcss.config.js` +- Create: `apps/web/tailwind.config.ts` +- Create: `apps/web/src/main.tsx` +- Create: `apps/web/src/app.tsx` +- Create: `apps/web/src/index.css` +- Create: `apps/web/src/lib/utils.ts` +- Create: `apps/web/src/store/ui-store.ts` +- Create: `apps/web/src/components/ui/button.tsx` +- Create: `apps/web/src/components/ui/card.tsx` +- Create: `apps/web/src/pages/landing-page.tsx` +- Create: `apps/web/src/pages/dashboard-page.tsx` +- Create: `apps/web/moon.yml` + +- [ ] **Step 1: Create web package metadata** + +Create `apps/web/package.json`: + +```json +{ + "name": "@zeavis/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc --project tsconfig.json && vite build", + "preview": "vite preview --host 0.0.0.0", + "typecheck": "tsc --project tsconfig.json" + }, + "dependencies": { + "@tanstack/react-query": "^5.59.16", + "@vitejs/plugin-react": "^4.3.3", + "@zeavis/shared": "workspace:*", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "tailwind-merge": "^2.5.4", + "zustand": "^5.0.1" + }, + "devDependencies": { + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^6.0.1" + } +} +``` + +- [ ] **Step 2: Create web TypeScript config** + +Create `apps/web/tsconfig.json`: + +```json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@zeavis/shared": ["../../packages/shared/src/index.ts"], + "@zeavis/shared/*": ["../../packages/shared/src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +- [ ] **Step 3: Create Vite TypeScript config** + +Create `apps/web/tsconfig.node.json`: + +```json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + }, + "include": ["vite.config.ts", "tailwind.config.ts"] +} +``` + +- [ ] **Step 4: Create Vite config** + +Create `apps/web/vite.config.ts`: + +```ts +import react from '@vitejs/plugin-react'; +import path from 'node:path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}); +``` + +- [ ] **Step 5: Create Vite HTML entry** + +Create `apps/web/index.html`: + +```html + + + + + + ZeaVis Edu + + +
+ + + +``` + +- [ ] **Step 6: Create PostCSS config** + +Create `apps/web/postcss.config.js`: + +```js +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; +``` + +- [ ] **Step 7: Create Tailwind config** + +Create `apps/web/tailwind.config.ts`: + +```ts +import type { Config } from 'tailwindcss'; + +export default { + darkMode: ['class'], + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + }, + }, + plugins: [], +} satisfies Config; +``` + +- [ ] **Step 8: Create frontend CSS theme** + +Create `apps/web/src/index.css`: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 48 60% 97%; + --foreground: 156 26% 12%; + --card: 0 0% 100%; + --card-foreground: 156 26% 12%; + --primary: 142 60% 32%; + --primary-foreground: 0 0% 100%; + --muted: 84 35% 90%; + --muted-foreground: 156 12% 35%; + --border: 84 24% 82%; + --radius: 1rem; + } + + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground antialiased; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + } +} +``` + +- [ ] **Step 9: Create utility helper** + +Create `apps/web/src/lib/utils.ts`: + +```ts +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +``` + +- [ ] **Step 10: Create UI store** + +Create `apps/web/src/store/ui-store.ts`: + +```ts +import { create } from 'zustand'; + +type UiState = { + dashboardCompact: boolean; + toggleDashboardCompact: () => void; +}; + +export const useUiStore = create((set) => ({ + dashboardCompact: false, + toggleDashboardCompact: () => + set((state) => ({ dashboardCompact: !state.dashboardCompact })), +})); +``` + +- [ ] **Step 11: Create Button component** + +Create `apps/web/src/components/ui/button.tsx`: + +```tsx +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import type { ButtonHTMLAttributes } from 'react'; +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + outline: 'border border-border bg-transparent hover:bg-muted', + ghost: 'hover:bg-muted', + }, + size: { + default: 'h-10 px-4 py-2', + lg: 'h-12 rounded-lg px-6', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +type ButtonProps = ButtonHTMLAttributes & + VariantProps & { + asChild?: boolean; + }; + +export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { + const Comp = asChild ? Slot : 'button'; + + return ; +} +``` + +- [ ] **Step 12: Add missing Radix Slot dependency** + +Modify `apps/web/package.json` dependencies to include `@radix-ui/react-slot`: + +```json +"dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.59.16", + "@vitejs/plugin-react": "^4.3.3", + "@zeavis/shared": "workspace:*", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "tailwind-merge": "^2.5.4", + "zustand": "^5.0.1" +} +``` + +- [ ] **Step 13: Create Card component** + +Create `apps/web/src/components/ui/card.tsx`: + +```tsx +import type { HTMLAttributes } from 'react'; +import { cn } from '@/lib/utils'; + +export function Card({ className, ...props }: HTMLAttributes) { + return
; +} + +export function CardHeader({ className, ...props }: HTMLAttributes) { + return
; +} + +export function CardTitle({ className, ...props }: HTMLAttributes) { + return

; +} + +export function CardDescription({ className, ...props }: HTMLAttributes) { + return

; +} + +export function CardContent({ className, ...props }: HTMLAttributes) { + return

; +} +``` + +- [ ] **Step 14: Create landing page** + +Create `apps/web/src/pages/landing-page.tsx`: + +```tsx +import { ArrowRight, Leaf, ShieldCheck, Sprout } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; + +const features = [ + { + icon: Leaf, + title: 'Deteksi penyakit daun jagung', + description: 'Fondasi aplikasi siap untuk integrasi model klasifikasi ZeaVis Edu.', + }, + { + icon: ShieldCheck, + title: 'Edukasi berbasis data', + description: 'Materi dan hasil analisis dapat dikembangkan di atas dashboard awal.', + }, + { + icon: Sprout, + title: 'Siap tumbuh bersama produk', + description: 'Monorepo memisahkan frontend, backend, dan shared types dengan jelas.', + }, +]; + +export function LandingPage() { + return ( +
+
+ + +
+
+
+ Platform edukasi kesehatan tanaman jagung +
+
+

+ Belajar mengenali penyakit daun jagung dengan alur digital yang rapi. +

+

+ Scaffold ini menyiapkan fondasi aplikasi ZeaVis Edu untuk antarmuka edukasi, + API, dan integrasi model machine learning berikutnya. +

+
+
+ + +
+
+ +
+ {features.map((feature) => ( + + +
+ +
+
+ {feature.title} + {feature.description} +
+
+
+ ))} +
+
+
+
+ ); +} +``` + +- [ ] **Step 15: Create dashboard page** + +Create `apps/web/src/pages/dashboard-page.tsx`: + +```tsx +import { BookOpen, History, Leaf, LayoutDashboard } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useUiStore } from '@/store/ui-store'; + +const dashboardCards = [ + { + icon: Leaf, + title: 'Deteksi Penyakit', + description: 'Area ini akan menjadi pintu masuk analisis gambar daun jagung.', + }, + { + icon: History, + title: 'Riwayat Analisis', + description: 'Hasil deteksi sebelumnya akan ditampilkan saat fitur data tersedia.', + }, + { + icon: BookOpen, + title: 'Materi Edukasi', + description: 'Konten edukasi penyakit jagung akan terhubung ke modul pembelajaran.', + }, +]; + +export function DashboardPage() { + const { dashboardCompact, toggleDashboardCompact } = useUiStore(); + + return ( +
+
+
+
+
+ Dashboard +
+

ZeaVis Edu Workspace

+

+ Placeholder awal untuk fitur deteksi, riwayat analisis, dan edukasi. +

+
+
+ + +
+
+ +
+ {dashboardCards.map((item) => ( + + +
+ +
+ {item.title} + {item.description} +
+ +
+ Belum ada data. Fitur akan dihubungkan pada iterasi berikutnya. +
+
+
+ ))} +
+
+
+ ); +} +``` + +- [ ] **Step 16: Create app router and providers** + +Create `apps/web/src/app.tsx`: + +```tsx +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { DashboardPage } from '@/pages/dashboard-page'; +import { LandingPage } from '@/pages/landing-page'; + +const queryClient = new QueryClient(); + +const router = createBrowserRouter([ + { + path: '/', + element: , + }, + { + path: '/dashboard', + element: , + }, +]); + +export function App() { + return ( + + + + ); +} +``` + +- [ ] **Step 17: Create React root entry** + +Create `apps/web/src/main.tsx`: + +```tsx +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './app'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); +``` + +- [ ] **Step 18: Create web Moon tasks** + +Create `apps/web/moon.yml`: + +```yaml +type: application +language: typescript + +tasks: + dev: + command: bun run dev + local: true + build: + command: bun run build + inputs: + - src/**/* + - index.html + - package.json + - tsconfig.json + - tsconfig.node.json + - vite.config.ts + - tailwind.config.ts + - postcss.config.js + outputs: + - dist + typecheck: + command: bun run typecheck + inputs: + - src/**/* + - package.json + - tsconfig.json + - tsconfig.node.json + - vite.config.ts + - tailwind.config.ts +``` + +- [ ] **Step 19: Commit web scaffold** + +```bash +git add apps/web +git commit -m "Add React web scaffold" +``` + +--- + +### Task 5: Install Dependencies and Verify Builds + +**Files:** +- Create: `bun.lockb` or `bun.lock` +- Modify: dependency lockfile generated by Bun + +- [ ] **Step 1: Install dependencies** + +Run: `bun install` + +Expected: Bun installs dependencies and creates a lockfile. + +- [ ] **Step 2: Run shared typecheck** + +Run: `bun --cwd packages/shared run typecheck` + +Expected: command exits with status 0. + +- [ ] **Step 3: Run API typecheck** + +Run: `bun --cwd apps/api run typecheck` + +Expected: command exits with status 0. + +- [ ] **Step 4: Run API build** + +Run: `bun --cwd apps/api build src/index.ts --outdir dist --target bun` + +Expected: Bun writes compiled output to `apps/api/dist` and exits with status 0. + +- [ ] **Step 5: Run web typecheck** + +Run: `bun --cwd apps/web run typecheck` + +Expected: command exits with status 0. + +- [ ] **Step 6: Run web build** + +Run: `bun --cwd apps/web run build` + +Expected: Vite writes production output to `apps/web/dist` and exits with status 0. + +- [ ] **Step 7: Verify API health endpoint manually** + +Run server in one terminal: + +```bash +API_PORT=3000 bun --cwd apps/api run start +``` + +In another terminal, run: + +```bash +curl -s http://localhost:3000/health +``` + +Expected response: + +```json +{"status":"ok"} +``` + +Stop the API server after verifying. + +- [ ] **Step 8: Verify API status endpoint manually** + +Run while the API server is still running: + +```bash +curl -s http://localhost:3000/api/v1/status +``` + +Expected response: + +```json +{"name":"ZeaVis Edu","status":"ok","version":"0.1.0"} +``` + +- [ ] **Step 9: Commit dependency lockfile and build fixes** + +```bash +git add bun.lockb bun.lock package.json apps packages .moon .env.example .gitignore tsconfig.base.json +git commit -m "Verify fullstack scaffold builds" +``` + +If the repository uses `bun.lock` instead of `bun.lockb`, only add the generated lockfile that exists. + +--- + +### Task 6: Final Project Documentation Update + +**Files:** +- Modify: `CLAUDE.md` + +- [ ] **Step 1: Read existing `CLAUDE.md`** + +Run: `sed -n '1,220p' CLAUDE.md` + +Expected: output shows the existing ML-focused project guidance. + +- [ ] **Step 2: Add fullstack command section to `CLAUDE.md`** + +Append this section after the existing common commands section: + +```markdown +## Fullstack app commands + +The TypeScript application scaffold lives at the repository root and uses Bun workspaces with Moon tasks. + +Install dependencies: + +```bash +bun install +``` + +Run all development tasks through Moon: + +```bash +bun run dev +``` + +Run type checks: + +```bash +bun run typecheck +``` + +Run production builds: + +```bash +bun run build +``` + +Run the API directly: + +```bash +API_PORT=3000 bun --cwd apps/api run start +``` + +Run the web app directly: + +```bash +bun --cwd apps/web run dev +``` +``` + +- [ ] **Step 3: Add fullstack architecture note to `CLAUDE.md`** + +Append this section after the high-level architecture section: + +```markdown +## Fullstack application architecture + +The root TypeScript workspace is a Bun + Moon monorepo: + +- `apps/web/` contains the React + Vite + TypeScript frontend with React Router, TanStack Query, Zustand, Tailwind, and shadcn/ui-style components. +- `apps/api/` contains the Elysia backend with health/status routes and Drizzle/PostgreSQL configuration. +- `packages/shared/` contains shared TypeScript types and utilities consumed by both apps. + +The backend reads `DATABASE_URL` for Drizzle/PostgreSQL, but the initial health/status endpoints do not require a live database connection. +``` + +- [ ] **Step 4: Run final verification commands** + +Run: + +```bash +bun run typecheck +bun run build +``` + +Expected: both commands exit with status 0. + +- [ ] **Step 5: Commit documentation update** + +```bash +git add CLAUDE.md +git commit -m "Document fullstack app commands" +``` + +--- + +## Self-Review + +- Spec coverage: the plan covers Bun workspaces, Moon config, `apps/web`, `apps/api`, `packages/shared`, `.env.example`, Drizzle config without mandatory DB startup, landing/dashboard UI, health/status endpoints, and verification. +- Placeholder scan: no TBD/TODO placeholders are present; deferred features are explicitly listed in the design and not implemented. +- Type consistency: `AppStatus`, `createAppStatus`, route paths, package names, and project paths are consistent across tasks. +- Known execution note: Task 4 requires adding `@radix-ui/react-slot` because the shadcn-style `Button` uses `Slot` for `asChild` support. diff --git a/package.json b/package.json index 99f05bd..21291c3 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,10 @@ "name": "zeavis-edu", "private": true, "packageManager": "bun@1.1.42", + "devDependencies": { + "@moonrepo/cli": "^1.32.4", + "typescript": "^5.6.3" + }, "workspaces": [ "apps/*", "packages/*" @@ -10,9 +14,5 @@ "dev": "moon run :dev", "build": "moon run :build", "typecheck": "moon run :typecheck" - }, - "devDependencies": { - "@moonrepo/cli": "^1.32.4", - "typescript": "^5.6.3" } }