프롬프트의 품질이 결과의 품질을 좌우합니다
클로드 코드를 공부하다가 토큰이 떨어져서 antigravity 에 가서 클로드 코드에서 쓰던 SPEC.md 파일로 앱 작성을 요청했습니다.
근데… 요구사항이 거의 완전히 구현되었습니다.
한 번의 요청으로 다음과 같은 앱이 완성되었습니다. (배포를 위해 sqlite 를 turso로 바꿔달라고 한 건 별개)
https://personal-notes-app-iota.vercel.app/
로그인을 묻긴 하는데, 아무 이메일과 8자 이상의 아무값이나 패스워드로 넣으면 됩니다.
어떤 프롬프트로 일을 시키는지가 결과물의 품질을 좌우하는 것 같습니다.
각자 자신의 프롬프트 템플릿을 갈고 닦아야 할 것 같네요.
다음은 사용한 SPEC.md 파일의 내용입니다.
# 기술 명세서: Personal Notes App
## 1. 개요 (Overview)
사용자가 리치 텍스트 형식의 노트를 작성, 수정, 관리하고 필요시 외부에 공유할 수 있는 **텍스트 중심**의 웹 애플리케이션입니다. **개발 및 학습 목적**으로, 배포 시의 데이터 지속성(Persistence) 제약은 고려하지 않고 Bun의 고성능 내장 SQLite를 활용합니다.
## 2. 기술 스택 (Tech Stack)
* **Runtime & Package Manager:** Bun (v1.x 이상)
* **Framework:** Next.js 15 (App Router)
* **Language:** TypeScript
* **Styling:** Tailwind CSS + `@tailwindcss/typography`
* **Authentication:** Better-Auth (Email & Password Strategy)
* **Database:** SQLite (`bun:sqlite` built-in module)
* **Editor:** TipTap (Headless wrapper around Prosemirror) - **Text Only**
## 3. 데이터베이스 스키마 (Database Schema)
Bun의 `Database` 클래스를 사용하여 `local.db` 파일에 데이터를 저장합니다. 애플리케이션 시작 시 테이블 존재 여부를 확인하고 생성(Migration)하는 로직을 포함합니다.
### 3.1. Notes Table
```sql
CREATE TABLE IF NOT EXISTS notes (
id TEXT PRIMARY KEY, -- UUID v4
user_id TEXT NOT NULL, -- Foreign Key to user table
title TEXT, -- 노트 목록 표시용 제목 (옵션, 텍스트 파싱하여 추출)
content TEXT NOT NULL, -- JSON String (TipTap Output)
is_public INTEGER DEFAULT 0, -- 0: Private, 1: Public
created_at INTEGER NOT NULL, -- Unix Timestamp (Date.now())
updated_at INTEGER NOT NULL, -- Unix Timestamp
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id);
```
### 3.2. Authentication Tables (Better-Auth Core Schema)
Better-Auth가 요구하는 Core Schema입니다. `better-auth` CLI (`npx @better-auth/cli migrate`)를 통해 자동 생성하거나, 아래 스키마를 참고하여 수동으로 생성합니다.
#### user
| Column | Type | Description |
| --- | --- | --- |
| `id` | TEXT (PK) | 사용자 고유 식별자 |
| `name` | TEXT NOT NULL | 표시 이름 |
| `email` | TEXT NOT NULL | 이메일 주소 |
| `emailVerified` | INTEGER (boolean) | 이메일 인증 여부 |
| `image` | TEXT | 프로필 이미지 URL (optional) |
| `createdAt` | INTEGER (Date) | 계정 생성 시각 |
| `updatedAt` | INTEGER (Date) | 정보 수정 시각 |
#### session
| Column | Type | Description |
| --- | --- | --- |
| `id` | TEXT (PK) | 세션 고유 식별자 |
| `userId` | TEXT (FK → user.id) | 사용자 ID |
| `token` | TEXT (UNIQUE) | 고유 세션 토큰 |
| `expiresAt` | INTEGER (Date) | 세션 만료 시각 |
| `ipAddress` | TEXT | 접속 IP (optional) |
| `userAgent` | TEXT | User-Agent 정보 (optional) |
| `createdAt` | INTEGER (Date) | 세션 생성 시각 |
| `updatedAt` | INTEGER (Date) | 세션 수정 시각 |
#### account
| Column | Type | Description |
| --- | --- | --- |
| `id` | TEXT (PK) | 계정 고유 식별자 |
| `userId` | TEXT (FK → user.id) | 사용자 ID |
| `accountId` | TEXT | SSO 제공자의 계정 ID (credential의 경우 userId와 동일) |
| `providerId` | TEXT | 인증 제공자 ID (e.g. "credential") |
| `accessToken` | TEXT | 액세스 토큰 (optional) |
| `refreshToken` | TEXT | 리프레시 토큰 (optional) |
| `accessTokenExpiresAt` | INTEGER (Date) | 액세스 토큰 만료 시각 (optional) |
| `refreshTokenExpiresAt` | INTEGER (Date) | 리프레시 토큰 만료 시각 (optional) |
| `scope` | TEXT | 인증 스코프 (optional) |
| `idToken` | TEXT | ID 토큰 (optional) |
| `password` | TEXT | 해싱된 비밀번호 - Email/Password 인증용 (optional) |
| `createdAt` | INTEGER (Date) | 계정 생성 시각 |
| `updatedAt` | INTEGER (Date) | 계정 수정 시각 |
#### verification
| Column | Type | Description |
| --- | --- | --- |
| `id` | TEXT (PK) | 인증 요청 고유 식별자 |
| `identifier` | TEXT | 인증 요청 식별자 |
| `value` | TEXT | 인증할 값 |
| `expiresAt` | INTEGER (Date) | 인증 요청 만료 시각 |
| `createdAt` | INTEGER (Date) | 인증 요청 생성 시각 |
| `updatedAt` | INTEGER (Date) | 인증 요청 수정 시각 |
## 4. 상세 기능 명세 (Feature Specifications)
### 4.1. 사용자 인증 (Authentication)
* **제공자:** Email & Password 방식만 사용.
* **구현:** `better-auth` 라이브러리의 표준 클라이언트/서버 API 사용.
* **흐름:**
* 회원가입 (`/sign-up`): 이메일, 이름, 비밀번호 입력.
* 로그인 (`/sign-in`): 이메일, 비밀번호 입력.
* 로그아웃: 세션 쿠키 삭제.
* **보안:** 비밀번호는 Better-Auth 내부 로직에 의해 해싱되어 저장됨.
### 4.2. 노트 관리 (Note Management)
* **저장 포맷:** TipTap JSON (Stringified).
* **생성:**
* "새 노트" 버튼 클릭 시 즉시 DB에 레코드 생성 후 에디터 페이지로 이동 (UUID 미리 생성).
* 초기 제목은 "제목 없음" 또는 빈 값.
* **자동 저장 (Auto-Save):**
* 타이핑 중에는 로컬 상태만 업데이트.
* `useDebounce` 등을 활용하여 입력 중단 1000ms 후 자동으로 `PUT` 요청.
* 저장 상태 표시 (저장 중... -> 저장됨).
* **삭제:**
* 노트 목록 또는 상세 화면에서 삭제 버튼 제공.
* DB에서 영구 삭제 (Hard Delete).
### 4.3. 리치 텍스트 에디터 (Rich Text Editor)
**라이브러리:** `@tiptap/react`
**기능 제한:** 텍스트 포맷팅 전용 (이미지, 비디오 등 미디어 업로드 기능 제외).
**사용 Extension:**
1. `StarterKit`:
* **Text Styling:** Bold, Italic
* **Structure:** Heading (Level 1, 2, 3), Paragraph
* **Lists:** BulletList, OrderedList
* **Divider:** HorizontalRule
2. `CodeBlockLowlight`: 코드 스니펫 (Syntax Highlighting 포함).
3. `Placeholder`: 빈 줄에 '내용을 입력하세요...' 표시.
**UI:**
* Floating Menu 또는 Fixed Toolbar를 사용하여 포맷팅 버튼 제공.
* Tailwind Typography (`prose`, `prose-slate`)를 적용하여 깔끔한 기본 스타일링 제공.
### 4.4. 공유 (Sharing)
* **공유 토글:**
* 사용자가 "공유" 스위치를 켜면 `is_public` 필드를 `1`로 업데이트.
* 고유 URL 복사 버튼 제공.
* **외부 접근:**
* 공유된 URL (`/share/[noteId]`)로 접근 시 인증 불필요.
* **Editor is Read-Only:** `editable={false}` 속성을 적용하여 뷰어 모드로 렌더링.
## 5. API Endpoints (Next.js Route Handlers)
| Method | Endpoint | Description | Payload / Params |
| --- | --- | --- | --- |
| `GET` | `/api/notes` | 내 노트 목록 | - |
| `POST` | `/api/notes` | 새 노트 생성 | `{ title?: string, content: JSON }` |
| `GET` | `/api/notes/[id]` | 노트 상세 (본인 확인) | - |
| `PUT` | `/api/notes/[id]` | 노트 저장 | `{ content: JSON, title?: string }` |
| `DELETE` | `/api/notes/[id]` | 노트 삭제 | - |
| `PATCH` | `/api/notes/[id]/share` | 공유 상태 변경 | `{ isPublic: boolean }` |
| `GET` | `/api/public/[id]` | 공개 노트 조회 | - |
## 6. 개발 로드맵 (Development Steps)
1. **초기 설정:**
* Next.js + Bun 프로젝트 생성.
* Tailwind CSS 설정.
2. **데이터베이스 & 인증 설정:**
* Bun SQLite 인스턴스 생성 (`lib/db.ts`).
* 테이블 생성 스크립트 작성 (Raw SQL).
* Better-Auth 설치 및 Email/Password 프로바이더 설정 (`lib/auth.ts`).
* 로그인/회원가입 페이지 구현.
3. **노트 API 구현:**
* CRUD 라우트 핸들러 작성.
* SQL 쿼리 작성 및 테스트.
4. **에디터 구현:**
* TipTap 컴포넌트 생성.
* 툴바 및 스타일링 적용.
* 데이터 로드 및 자동 저장 로직 연결.
5. **공유 기능 구현:**
* 공유 상태 관리 및 Read-only 뷰어 페이지 작성.
6. **마무리:**
* UI 다듬기 및 버그 수정.이 md 파일은 gemini 에게 작성해 달라고 한 결과입니다.
다음과 같은 요청을 했습니다.
"개인용 노트" 웹 앱을 만들려고 해
이 앱에서 사용자는 노트를 생성하고 관리할 수 있어야 해. "노트"는 리치 텍스트 에디터(TipTap)로 작성한 텍스트야.
사용자 인증(필수!)한 사용자는 다음과 같은 작업을 할 수 있어:
- 노트의 생성, 보기, 편집, 삭제
- 노트를 공개적으로 공유하기 (공유 중지하기 기능도 있어야 함)
이 앱을 타입스크립트와 bun을 사용한 next.js로 만들려고 해. 스타일링을 위해서는 tailwindcss 를 사용할 거야.
사용자 인증은 better-auth 라이브러리로 구현할 것이고, 리치 텍스트 에디터는 TipTap 라이브러리를 사용하여 구현할 거야.
리치 텍스트 포매팅에는 다음과 같은 기능이 있을 거야
- 볼드체, 이탤릭체
- 3 단계의 Heading 과 일반 텍스트
- 인라인 코드와 코드 스니펫
- 불릿 리스트
- 수평 구분선
데이터는 JSON 형태로 SQLite 데이터베이스에 저장하는데, Bun의 내장 sqlite 클라이언트와 raw SQL문을 사용할 거야
이 애플리케이션을 만들기 위한 기반으로 사용하기 위해 technical spec 문서를 작성해 줘. 그 작업을 위해 더 필요한 정보가 있으면 물어 봐 줘.