diff --git a/.env.sample b/.env.sample index 2add463..9344874 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,14 @@ -# Deployment used by `npx convex dev` -CONVEX_DEPLOYMENT=anonymous:anonymous-convex - -PUBLIC_CONVEX_URL=http://127.0.0.1:3210 +# Application +PORT=3000 +CORS_ORIGIN=http://localhost:5173 +PUBLIC_API_BASE_URL=http://localhost:3000 +# Database +DATABASE_URL=postgres://localhost:5432/prism +# Better Auth +BETTER_AUTH_SECRET=your-secret-key-at-least-32-characters +# Google OAuth +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret +# Feature Flags +DISABLE_AUTH=false +PUBLIC_DISABLE_AUTH=false diff --git a/.envrc b/.envrc index 3550a30..e2be889 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,7 @@ -use flake +#!/usr/bin/env bash + +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +use devenv diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3bb5a70..5b6e789 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,32 +7,23 @@ on: pull_request: jobs: - biome: - name: Biome Lint + check: + name: Lint, Format & Type Check runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile - - run: bun run check:lint - - prettier: - name: Prettier - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - run: bun install --frozen-lockfile - - run: bun run check:format + - run: bun run check - all-checks: - name: All Checks + build: + name: Build runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile - - run: bun run check + - run: bun run --filter=@apps/server build + - run: bun run --filter=@apps/desktop build diff --git a/.gitignore b/.gitignore index 1c5ad35..2ef83f1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,15 +4,14 @@ node_modules # development .env .direnv +.devenv +.devenv.flake.nix # Tests test-results +playwright-report # target target .svelte-kit dist - -# Convex? -**/gen/schemas -.env.local diff --git a/.prettierignore b/.prettierignore index 4f560ec..fbaef81 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,5 +3,3 @@ **/paraglide/runtime.js **/paraglide/server.js **/paraglide/registry.js - -**/convex/_generated/* \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 48f5926..26316f5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,3 @@ { - "recommendations": [ - "inlang.vs-code-extension", - "svelte.svelte-vscode", - "rust-lang.rust-analyzer" - ] + "recommendations": ["svelte.svelte-vscode", "rust-lang.rust-analyzer"] } diff --git a/CLAUDE.md b/CLAUDE.md index 71aa546..b1773b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,155 +2,234 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Project Architecture + -This is a TypeScript monorepo using a Convex backend and SvelteKit frontend with Tauri for desktop apps. +## Stack -### Stack - -- **Frontend**: SvelteKit with Svelte 5, TypeScript, TailwindCSS, DaisyUI - - **CRITICAL**: This project uses Svelte 5 RUNES MODE - NEVER use legacy reactive statements (`$:`) - - **ALWAYS use**: `$state`, `$derived`, `$effect` instead of legacy syntax -- **Backend**: Convex (real-time database and functions) -- **Desktop**: Tauri (optional, conflicts with web dev server) -- **Styling**: TailwindCSS v4 with DaisyUI components +- **Frontend**: SvelteKit + Svelte 5 + TailwindCSS v4 + DaisyUI +- **Backend**: Elysia (Bun) + Drizzle ORM + PostgreSQL +- **Desktop**: Tauri (optional) - **Package Manager**: Bun -- **Monorepo Structure**: Workspaces with `packages/` +- **Monorepo**: Workspaces (`apps/*`) +- **Dev Environment**: devenv (logs: `.devenv/processes.log`) + +## Directory Structure -### Apps Structure +``` +. +├── apps/ +│ ├── desktop/ # SvelteKit frontend +│ │ ├── src/ +│ │ │ ├── components/ # UI components +│ │ │ ├── features/ # Feature modules +│ │ │ ├── lib/ # Utilities +│ │ │ ├── routes/ # SvelteKit routes +│ │ │ └── icons/ # Icon components +│ │ └── src-tauri/ # Tauri config +│ ├── server/ # Elysia API server +│ │ └── src/ +│ │ ├── db/ # Drizzle schema & queries +│ │ ├── domains/ # Business logic +│ │ └── middleware/ # Elysia middleware +│ └── api-client/ # Shared API types +├── docs/ +│ └── skills/ # Agent skill docs +├── tasks/ # Procfile for dev +├── .env.sample # environment variable samples. +└── .env # all environment variables in here. pls don't read +``` -- `packages/client/` - SvelteKit frontend with Tauri integration -- `packages/convex/` - Convex backend with database schema and functions +## Import Aliases -### Frontend (SvelteKit) +| Alias | Path | +| ------------- | ----------------- | +| `@` | `src/` | +| `$components` | `src/components/` | +| `@apps/{pkg}` | `apps/{pkg}/` | -- **Routes**: Standard SvelteKit routing in `packages/client/src/routes/` -- **Components**: Organized in `packages/client/src/components/` with atoms and examples -- **Aliases**: - - `@` → `src` - - `@@` → `../..` (workspace root) - - `$components` → `src/components` - - `@` → `src/` - - `@apps/{package}` → monorepo -- **Convex Integration**: Uses `convex-svelte` for reactive queries -- **State Pattern**: Logic components (e.g., TaskList.svelte) separate from presentation (TaskListSkin.svelte) + -### Backend (Convex) + -- **Schema**: Defined in `packages/convex/src/convex/schema.ts` -- **Functions**: Database operations in `packages/convex/src/convex/[feature].ts` -- **Type Safety**: Auto-generated types from schema shared with frontend via workspace dependency +## Svelte 5 Runes (CRITICAL) -### Data Flow +**NEVER use legacy syntax.** This project uses Svelte 5 runes mode. + +```svelte + +$: reactiveVar = ... +let count = 0 + + +let count = $state(0) +const doubled = $derived(count * 2) +$effect(() => { ... }) +``` -1. Convex schema defines database structure -2. Convex functions provide type-safe CRUD operations -3. Frontend uses `convex-svelte` hooks for reactive data -4. Automatic type generation ensures type safety across stack +## Svelte Tips -## Framework - Convex +- **clsx builtin**: `
` +- **Reactive class**: Define in `.svelte.ts` files for reusability -### Convex の Import について +## Controllers (`.svelte.ts`) + +Controllers must be instantiated **synchronously at script top-level**, so `$effect`/`$derived` work in constructors. + +**Reactive props pattern:** ```ts -import { api, type Id } from "@apps/convex"; +// ✅ Pass getter function, fine-grained reactivity +class MyController { + organizationId: string; + constructor(organizationId: () => string) { + this.organizationId = $derived(organizationId()); + } +} +// Usage: new MyController(() => organizationId) -// use api and type Id ... +// ❌ Don't pass raw values (not reactive) +class MyController { + organizationId: string; + constructor(organizationId: string) { + this.organizationId = organizationId; // Won't update! + } +} +``` + +```svelte + ``` -### 注意点: convex-svelte の `useQuery` について +**Hooks in controllers:** `useWebSocket`, `useQuery`, etc. can be called in constructors since they run at component init time. +for example, -`useQuery` に渡す引数は、関数の形式で渡してください。そうでないと、期待しない動作を引き起こす可能性があります。 +```ts +// foo.controller.svelte.ts +class FooController { + constructor() { + useWebSocket("message:foo", (ev) => { + console.log("event received:", ev); + }); + } +} +``` + +## Vocaburaly + +[Hooks] +we derive the words "hooks" from react. +hooks in svelte can only be called at script initialization time. + +```ts +// for "constant" variable you may use bare variables, but otherwise use getter-style passing, just like in controllers. +function useHook(defaultVal: number, plus: () => number) { + // inside hooks you can call effects + $effect(() => { + console.log("effects"); + }); + + // create reactive variables + let reactive = $state(defaultVal); + let derived = $derived(reactive * 2 + plus()); + + // and return controller-like objects + // ALWAYS use getters and setters for returning reactive variables, otherwise it won't be reactive. + return { + get reactive() { + return reactive; + }, + set reactive(newVal) { + reactive = newVal; + } + get derived() { + return derived; + } + } +} +``` ```svelte + +{foo.reactive} * 2 + {plus} = {foo.derived} ``` -### Mutations with useMutation + -Since `convex-svelte` doesn't export `useMutation`, we have a custom utility at `src/lib/useMutation.svelte.ts`: + -```typescript -import { useMutation } from "@/lib/useMutation.svelte.ts"; +## Eden Treaty (Data Fetching) -const createOrganization = useMutation(api.organizations.create); +```ts +import { treaty } from "@elysiajs/eden"; +import type { App } from "@apps/server"; + +const client = treaty("http://localhost:8080"); -// Use like this -await createOrganization.run({ name: "New Org", description: "..." }); -// which exposes these properties -createOrganization.processing; // boolean, use for button disabled state / loading spinners -createOrganization.error; // string | null, use for error messages +await client.products.get(); // GET +await client.products["123"].get(); // Dynamic param +await client.products.get({ query: { category: "foo" } }); // Query +await client.products.post({ name: "bar", price: 100 }); // POST ``` -## Framework - Svelte + -### Syntax + -Never use logacy svelte syntax. This project uses Svelte 5 runes mode. +## Code Quality -- ❌ FORBIDDEN: `$: reactiveVar = ...` (reactive statements) -- ❌ FORBIDDEN: `let count = 0` for reactive state -- ✅ REQUIRED: `let count = $state(0)` for reactive state -- ✅ REQUIRED: `$effect(() => { ... })` for side effects -- ✅ REQUIRED: `const sum = $derived(a + b);` for derived variables -- ✅ REQUIRED: `const sum = $derived.by(() => { if (a + b < 0) return 0; return a + b; );` for derived variables which needs a block. +- **FILE LENGTH**: 30-50 lines recommended, be warned over 100, 200 AT MAX +- **TIDY**: Run `bun tidy` after writing code (auto-fix + check) +- **DOCUMENTATION**: Document behavior, not implementation -### Svelte Capabilities +## Svelte Rules -- clsx: Svelte has clsx builtin to its class. `
{text}
` +- **NAMING**: Snippets use camelCase (not PascalCase) +- **ALIAS**: Use `@/` for imports +- **STYLING**: TailwindCSS + DaisyUI only. No `