Simplifying transforms unstructured inputs into trustworthy events and actionable tasks—fast, clear, and synced to the tools you already use.
# 1. Install dependencies
pnpm install
# 2. Set up environment
cp .env.example .env.local
# 3. Start development server
pnpm devTip: Use
npx ncu -i --format groupwith npm-check-updates to update dependencies.
This is a pnpm monorepo with a Next.js 15 app and modular feature packages:
simplifying/
├── apps/
│ ├── web/ # Next.js web application
│ └── chrome-extension/ # Chrome extension
├── packages/ # ✨ Modular feature packages
│ ├── tsconfig/ # Shared TypeScript configs
│ ├── ui/ # Shared UI components
│ ├── prisma/ # Database client & encryption
│ ├── errors/ # Error classes + response types
│ ├── serialization/ # ISO date helpers (toISOString, toDate)
│ ├── api-server/ # API response builders
│ ├── api-client/ # Fetch-based HTTP client
│ ├── task/ # Task management (full-stack)
│ └── task-group/ # Task grouping (full-stack)
├── prisma/ # Database schema
└── turbo.json # Turborepo config
All new features go in packages/. The web app (apps/web/) consumes packages via @simplifying/* imports.
| Package | Purpose | Entrypoints |
|---|---|---|
@simplifying/tsconfig |
Shared TypeScript configs | ./base, ./react-library, ./node-library |
@simplifying/ui |
UI components (shadcn/ui) | ., ./client |
@simplifying/prisma |
Encrypted Prisma client | ., ./client, ./server |
@simplifying/errors |
Error classes, response envelope types | . |
@simplifying/serialization |
ISO date helpers (toISOString, toDate) |
., ./client, ./server |
@simplifying/api-server |
API response builders (apiSuccess, apiError) |
., ./server |
@simplifying/api-client |
Fetch-based HTTP client | . |
@simplifying/task |
Task CRUD + TanStack Query | ., ./client, ./server |
@simplifying/task-group |
Task grouping (full-stack) | ., ./client, ./server |
Every feature package exposes three entrypoints:
// Default: Types, schemas, constants (safe for all contexts)
import { MAX_GROUPS, type TaskGroupDTO } from "@simplifying/task-group";
// Client: React components, TanStack hooks
import { GroupSelector, useTaskGroups } from "@simplifying/task-group/client";
// Server: Repositories, services (Prisma-dependent)
import { createTaskGroupService } from "@simplifying/task-group/server";packages/<feature>/
├── package.json # Exports: ., ./client, ./server
├── tsconfig.json # Extends @simplifying/tsconfig
└── src/
├── index.ts # Default exports (lib + types)
├── client.ts # React exports ("use client")
├── server.ts # Server exports (repositories)
├── types.ts # Client-safe DTOs
├── types.server.ts # Server-only types (Prisma aliases)
├── lib/ # Pure business logic + serializers
│ └── <domain>-serializer.ts # Prisma ↔ DTO conversion
├── api/ # HTTP transport (fetch)
├── query/ # TanStack Query (keys, options, hooks)
├── components/ # React components
├── hooks/ # Local React hooks
├── services/ # Use-case flows
└── repositories/ # Prisma data access
| From | Can Import | Cannot Import |
|---|---|---|
packages/** |
@simplifying/*, relative |
@/, apps/ |
| Client code | ./client, default |
./server, repositories/ |
| Server code | All entrypoints | — |
Serializers (serialize*) |
Export from ./server only |
Export from default/client |
Deserializers (deserialize*) |
Export from default (.) |
— |
Packages with HTTP APIs use @simplifying/api-client + TanStack Query:
// Layer 1: API functions (api/<feature>.api.ts)
import { createFetchClient } from "@simplifying/api-client";
const client = createFetchClient({ baseURL: "/api/tasks" });
export async function createTask(data: TaskCreatePayload): Promise<Task> {
return client.post<Task>("/", data); // Returns T directly
}
// Layer 2: Query keys (query/keys.ts)
export const taskKeys = {
all: ["tasks"] as const,
lists: () => [...taskKeys.all, "list"] as const,
detail: (id: string) => [...taskKeys.all, "detail", id] as const,
} as const;
// Layer 3: TanStack hooks (query/hooks.ts)
export function useCreateTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createTask,
onSuccess: () => queryClient.invalidateQueries({ queryKey: taskKeys.all }),
});
}Usage:
import { useCreateTask } from "@simplifying/task/client";
const createTask = useCreateTask();
createTask.mutate({ title: "New Task" });pnpm dev # Start Next.js dev server
pnpm dev:all # Start all packages in dev mode (Turbo)
pnpm build # Build packages + Next.js apppnpm db:push # Push schema changes (dev only)
pnpm exec prisma studio # Database GUI
pnpm exec prisma generate # Regenerate Prisma typespnpm lint # Lint across workspace (Turbo)
pnpm lint:fix # Biome with auto-fix
pnpm type-check # Type-check root app
pnpm type-check:all # Type-check across workspace (Turbo)pnpm test # Run Vitest (watch mode)
pnpm test:run # Run tests once
pnpm test:coverage # Run with coverage
pnpm test:all # Tests across workspace (Turbo)mkdir -p packages/<feature>/src/{lib,api,query,components,hooks,services,repositories}{
"name": "@simplifying/<feature>",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"sideEffects": false,
"exports": {
".": "./src/index.ts",
"./client": "./src/client.ts",
"./server": "./src/server.ts"
},
"scripts": {
"type-check": "tsc -p tsconfig.json --noEmit",
"lint": "biome check ."
},
"devDependencies": {
"@simplifying/tsconfig": "workspace:*",
"typescript": "5.5.3"
}
}{
"extends": "@simplifying/tsconfig/react-library",
"compilerOptions": {
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}// src/index.ts - Safe defaults (types, constants, schemas)
export type { MyFeatureDTO } from "./types";
export { mySchema } from "./lib/schemas";
// src/client.ts - React layer
"use client";
export { MyComponent } from "./components/my-component";
export { useMyHook } from "./hooks/use-my-hook";
// src/server.ts - Server layer
export { createMyRepository } from "./repositories/my-repository";
export { createMyService } from "./services/server-my-service";{
"dependencies": {
"@simplifying/<feature>": "workspace:*"
}
}transpilePackages: [
"@simplifying/<feature>",
],| Category | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Database | PostgreSQL + Prisma ORM |
| Auth | Auth.js + Prisma Adapter |
| UI | React + Tailwind CSS v3.4.6 + shadcn/ui |
| State | TanStack Query (server) + Zustand (client) |
| AI | Vercel AI SDK (OpenAI, Cerebras) |
| Date/Time | date-fns + date-fns-tz |
| Validation | Zod v4.1.12 |
| Build | Turborepo + pnpm workspaces |
| Linting | Biome |
| Testing | Vitest |
| File | Purpose |
|---|---|
pnpm-workspace.yaml |
Workspace packages definition |
turbo.json |
Turborepo task pipeline |
biome.json |
Linting and formatting rules |
tsconfig.json |
Root TypeScript config |
apps/web/next.config.mjs |
Next.js configuration |
apps/web/env.mjs |
Type-safe environment variables |
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secretNote: Some guides use GOOGLE_OAUTH_CLIENT_ID/GOOGLE_OAUTH_CLIENT_SECRET—this project uses the non-OAUTH names above.
- Package Development: See packages/README.md for detailed package conventions
- API Contract: See api-contract-layer-spec/ for response envelope, serialization, and RFC 9457 error conventions
- Database: See prisma/schema.prisma for data models
- AI Instructions: See .claude/CLAUDE.md for coding standards