You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

756 lines
56 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 아리(Ari) — AI Life OS · 개발 개요
> 일·삶을 한곳에서 관리하는 한국어 AI 개인비서 **아리**의 MVP 개발 진입 문서.
> 핵심 철학: **"적을 때는 분류하지 않는다 — 분류·배치·자동화는 아리가."** 사용자는 *읽고 탭 한 번*, 나머지는 아리가 합니다.
**이 문서는 `dev/` 문서 세트의 일부입니다 — 가장 먼저 이 `overview.md`를 읽으세요.** 이후 phase 문서(`phase-0-foundation.md` → `phase-1-design-system.md``phase-2-backend.md``phase-3-tasks.md``phase-4-inbox.md``phase-5-dashboard.md``phase-6-integration.md`)를 빌드 순서대로 진행합니다. 각 phase 문서로의 정확한 연결은 [§11 개발 Phase 로드맵](#11-개발-phase-로드맵)과 [§14 이 문서 세트 사용법](#14-이-문서-세트-사용법)에 있습니다.
> **MVP 이후(포스트-MVP)**: 나머지 10개 페이지와 연합·실연동·능동 에이전트·프로덕션은 별도 세트로 문서화되어 있습니다 — `post-mvp-overview.md`(포스트-MVP 진입 문서)부터 시작해 `phase-7-approvals-automation.md` → `phase-8-calendar-meetings.md` → `phase-9-mail-notifications.md` → `phase-10-research-travel.md` → `phase-11-life-care.md` → `phase-12-daily-narrative.md` → `phase-13-integrations.md` → `phase-14-proactive-agent.md` → `phase-15-production.md` 순으로 진행합니다. 이 세트는 위 MVP 규약(스택·토큰·데이터모델·API·테스트)을 그대로 상속합니다.
원본 디자인(픽셀 충실 재현 기준)은 다음 경로에 보관되어 있습니다. 이 문서의 모든 값(색 HEX, px, 클래스명, 한국어 문구, 데이터 필드)은 이 파일들에서 그대로 인용했습니다.
```
REF = /Users/kim1634/mycloud/personal/workspace/ari_assistant/workspace/design-reference
REF/대시보드.html · REF/인박스.html · REF/작업.html (MVP 3개 페이지 진입 HTML)
REF/assets/dash.css (디자인 토큰의 기준 :root)
REF/assets/shell.jsx (Topbar/SubRail/Icon, MAIN 13항목, P 아이콘 맵)
REF/assets/data.js (대시보드 시드: user/schedule/goals/...)
REF/assets/tasks-data.js (작업 트리·people·columns·scaffold)
REF/assets/tasks-risk.jsx (리스크 레이더 계산 규칙, TODAY=8)
REF/assets/sinbox-data.js (인박스 캡처·분류 시드)
REF/assets/approve-data.js (결재함 읽기전용 시드)
REF/PROJECT-README.md · REF/HANDOFF-README.md (제품 설명·핸드오프)
```
---
## 0. 목차
1. [개요 & 목표](#1-개요--목표)
2. [선행 조건 / 산출물](#2-선행-조건--산출물)
3. [제품 비전 & 철학](#3-제품-비전--철학)
4. [데모 페르소나(지우)와 한 주의 시드 데이터](#4-데모-페르소나지우와-한-주의-시드-데이터)
5. [MVP 범위 & 13페이지 비전 경계](#5-mvp-범위--13페이지-비전-경계)
6. [시스템 아키텍처](#6-시스템-아키텍처)
7. [기술 스택 결정과 근거](#7-기술-스택-결정과-근거)
8. [리포지토리 구조](#8-리포지토리-구조)
9. [데이터 모델 개요](#9-데이터-모델-개요)
10. [REST API 개요](#10-rest-api-개요)
11. [디자인 시스템 요약](#11-디자인-시스템-요약)
12. [페이지 연합(federation) 흐름](#12-페이지-연합federation-흐름)
13. [개발 Phase 로드맵](#13-개발-phase-로드맵)
14. [LLM(Ollama) 추상화 & 분류 계약](#14-llmollama-추상화--분류-계약)
15. [상태 처리 & 엣지 케이스 (전역 원칙)](#15-상태-처리--엣지-케이스-전역-원칙)
16. [테스팅 & 검증](#16-테스팅--검증)
17. [완료 기준 (Definition of Done)](#17-완료-기준-definition-of-done)
18. [용어집](#18-용어집)
19. [이 문서 세트 사용법](#19-이-문서-세트-사용법)
> (위 §번호는 본 진입 문서 내부의 절 번호입니다. CONTRACT 아웃라인 항목과 1:1 대응하며 일부 절은 통합되어 있습니다.)
---
## 1. 개요 & 목표
이 문서는 **아리 MVP**의 진입 문서이자 단일 출처(single source of truth)입니다. 제품 비전, 데모 데이터, 아키텍처, 스택, 데이터 모델, REST API, 디자인 시스템, 페이지 연합 흐름, 개발 로드맵을 총괄합니다. 다른 phase 문서는 이 문서의 정의를 **그대로 참조**하며, 임의로 변경하지 않습니다.
**이 phase(문서)가 끝나면 무엇이 동작하는가**: 직접적으로 코드가 동작하지는 않습니다 — 이 문서는 *지도*입니다. 그러나 이 문서를 읽고 나면 개발자는 (1) 무엇을 만드는지, (2) 어떤 기술로, (3) 어떤 순서로, (4) 어떤 데이터/계약으로 만들어야 하는지를 완전히 이해하고 `phase-0-foundation.md`로 바로 진입할 수 있습니다.
**MVP의 한 줄 정의**: 한국어 사용자 *지우*가, 인박스에 자유롭게 적은 한 줄을 아리가 작업/일정/아이디어로 자동 분류하고, 작업 페이지에서 칸반으로 관리하며 리스크 레이더가 마감·쏠림·의존성을 경고하고, 대시보드에서 하루를 한눈에 집계해 보는 — **로컬 완결형 웹앱(빌드/배포 가능)**.
---
## 2. 선행 조건 / 산출물
### 선행 조건 (의존 phase)
- 없음. 이 문서는 phase 0의 선행 문서이자 전 phase의 인덱스입니다.
### 산출물 (Deliverables)
| 산출물 | 내용 |
|---|---|
| 제품 정의 | 비전·철학·페르소나·MVP 경계 (§3~§5) |
| 아키텍처 그림 | 브라우저↔FastAPI↔SQLite↔Ollama 데이터 흐름 (§6) |
| 스택 결정표 | 선택 기술과 근거 (§7) |
| 리포 트리 | `frontend/` + `backend/` 모노레포 전체 구조 (§8) |
| 데이터 모델 표 | 엔티티·필드·관계 (§9) |
| API 계약 표 | 메서드/경로/용도/요청·응답 예시 (§10) |
| 디자인 토큰 요약 | 색/폰트/글래스/아이콘/라이트·다크 (§11) |
| 연합 흐름도 | 캡처→분류→작업→리스크→대시보드 (§12) |
| 로드맵 표 | phase 0~6 목표·산출물·의존성·작업량 (§13) |
| LLM 계약 요약 | Provider 추상화 + 분류 스키마 + 골든 케이스 (§14) |
| 용어집 | 인박스/결재함/리스크 레이더/sphere/scaffold 등 (§18) |
---
## 3. 제품 비전 & 철학
아리는 *할 일 앱*이 아니라 **"Life OS"** 를 지향합니다. 핵심 가치는 세 개의 문장으로 압축됩니다.
### 3.1 "적을 때는 분류하지 않는다 — 분류·배치·자동화는 아리가."
사용자의 인지 부담을 0에 가깝게 만드는 것이 1순위입니다. 사용자는 떠오른 것을 **인박스에 한 줄 던지기만** 하면 됩니다. 어느 프로젝트인지, 작업인지 일정인지, 언제 할지는 **아리가** 정합니다. (원본 `sinbox-data.js` 주석: *"적을 때는 분류하지 않는다 — 분류·배치·자동화는 아리가."*)
예) `"다음 주에 한국 놀러가는 비행기 티켓 사기"` 한 줄 → 아리가 **작업 / 개인 영역 / `개인 여행 — 한국` 프로젝트 / 가격 추적 알림**까지 자동 처리. 사용자는 결과를 *읽고 탭 한 번*으로 확정.
### 3.2 "할까요?"가 아니라 "이미 해뒀어요"
아리는 묻고 기다리지 않습니다. **미리 처리한 뒤 승인/되돌리기**를 제공합니다(원본 `approve-data.js` 주석: *"아리는 '할까요?'라고 묻지 않고 미리 해둔다."*).
- **위험도 낮음(low)**: 이미 처리. *되돌리기만* 제공. 예) `"치과 예약을 16:00로 옮겼어요"`(`undoLabel: "원래 시간으로"`).
- **위험도 높음(high)**: 보내기/결제/타인 전달 등은 *사용자 확인 후 실행*. 예) `"현우님께 회신 초안이 준비됐어요"`(`cta: "보내기", alt: "수정"`).
이 원칙은 인박스에서 **분류를 먼저 실행하고 결과를 보여준 뒤 확인받는** 방식(`POST /api/inbox/capture`가 동기 분류까지 수행)으로 MVP에 구현됩니다.
### 3.3 "개인 일은 숨기지 않는다 — 개인 = 프로젝트"
가장 중요한 모델 원칙입니다. **"개인"은 별도 비밀 섹션이 아니라 그냥 하나의 폴더(영역)** 이며, 업무/개인은 **같은 트리에서 '필터'로만** 구분됩니다(원본 `tasks-data.js` 트리: 최상위 `work`(업무)·`life`(개인) 두 폴더가 동급). 아리는 **배치(언제 하면 좋을지)만 추천**하지 강제로 숨기지 않습니다.
원본 분류 `reason` 문구가 이 철학을 그대로 말합니다(`sinbox-data.js`):
> *"작업 트리의 '개인' 아래에 '여행 — 한국' 프로젝트를 만들어 넣었어요 — 따로 섹션이 생기는 게 아니라 다른 작업과 똑같이 보여요."*
배치 추천 예: `엄마 생신 선물 알아보기``"주말 오전 블록"`, `엄마 생신 선물``"6/20 전"`. 강제 숨김이 아니라 *추천*임에 유의.
---
## 4. 데모 페르소나(지우)와 한 주의 시드 데이터
### 4.1 사용자: 지우(PM)
| 필드 | 값 | 출처 |
|---|---|---|
| 이름 | 지우 | `data.js: user.name` / `tasks-data.js: people.jiwoo.name` |
| 이니셜 | 지 | `data.js: user.initial` (상단바 아바타 `.t-ava`에 표시) |
| 역할 | PM | PROJECT-README |
| 식별 | `is_me = true`, 색 `--blue` | `tasks-data.js: people.jiwoo = { ..., c: "var(--blue)", me: true }` |
### 4.2 팀원 5인 (`person`)
`tasks-data.js: people` 그대로 이식. `color` 값까지 고정.
| id | name | initial | color | is_me |
|---|---|---|---|---|
| `jiwoo` | 지우 | 지 | `var(--blue)` (#4f72e0) | **true** |
| `hyunwoo` | 현우 | 현 | `var(--violet)` (#8b6fd4) | false |
| `minseo` | 민서 | 민 | `var(--green)` (#4e9b66) | false |
| `jaeho` | 재호 | 재 | `var(--coral)` (#df7256) | false |
| `sua` | 수아 | 수 | `oklch(0.66 0.13 200)` | false |
> 주의: 수아의 색은 토큰이 아닌 **리터럴 `oklch(0.66 0.13 200)`** 입니다. 시드 시 `person.color`에 문자열 그대로 저장.
### 4.3 배경: 6월 7~12일 가상의 한 주, 기준일 6월 8일
- 대시보드 표시 날짜: **`today = "6월 7일 일요일"`**(`data.js`). 단, **리스크 계산 기준 `TODAY = 8`**(6월 8일, `tasks-risk.jsx` 7번째 줄 `const TODAY = 8;`). 이 둘은 의도적으로 다릅니다 — 대시보드 날짜 라벨과 리스크 마감 비교 기준일을 혼동하지 마세요. 백엔드 `services/risk.py``TODAY=8`을 기준 상수로 이식합니다.
- 작업 마감(`due`)은 `MM-DD` 문자열(예 `"06-08"`, `"06-14"`, `"06-20"`). 리스크 계산은 `due.split("-")[1]`(일)만 정수로 비교(`dueD`). 표시는 `6/{일}`(`dueTxt`).
### 4.4 시드 데이터 한눈에 (출처별)
| 영역 | 시드 출처 | 핵심 시드 항목(예) |
|---|---|---|
| 작업 트리 | `tasks-data.js: tree` | 업무(`work`)▸경영 전략/온보딩 리디자인/팀 운영, 개인(`life`)▸일상/여행 — 한국/가족 |
| 작업 | `tasks-data.js: tasks` | `k1` 분기 리포트 초안 마무리(doing, due 06-08, 높음), `k20` 한국행 비행기 티켓 구매(life-trip), `k14` 데이터 전처리 파이프라인(waiting, delegated) 등 |
| 인박스 | `sinbox-data.js: items` | `s1`~`s4` (골든 분류 케이스가 곧 시드) |
| 대시보드 일정 | `data.js: schedule` | 09:30 스탠드업, 11:00 디자인 리뷰, 14:00 분기 전략 미팅(`soon:true`), 16:30 1:1—민서님 |
| 목표 | `data.js: goals` | 분기 OKR—리텐션 68%, 주 4회 운동 75%, '딥 워크' 책 완독 40% |
| 결재함(읽기전용) | `approve-data.js: items` | a1~a6, `savedToday: "47분"`, `autoCountNight: 7` |
| 아침 브리핑 | `data.js: weather/commute/sleep/briefingNote` | 24°·맑음, 출근 23분, 수면 7시간 12분, 브리핑 노트(HTML 포함) |
> 날씨 값 주석: `weather_temp = 24`(현재 기온), `weather_cond`의 "한낮 28°" = 일 최고 기온 — **의도적으로 다른 값**입니다(원본 `data.js` 근거). 두 값을 혼동하지 마세요.
> `briefingNote`, 작업 `notes`는 **HTML 문자열**을 포함합니다(`<b>`, `<h3>`, `<blockquote>`). 렌더 시 신뢰된 시드로 취급하되 [§15](#15-상태-처리--엣지-케이스-전역-원칙) 새니타이즈 원칙을 참조.
---
## 5. MVP 범위 & 13페이지 비전 경계
### 5.1 전체 비전 — 13페이지
원본은 13개 화면(상단 내비 `MAIN` 배열, `shell.jsx`)을 가집니다.
`대시보드 · 인박스 · 결재함(badge 3) · 자동화 · 여정 · 일정 · 작업(badge 4) · 메일 · 알림(badge 6) · 리서치 · 여행 · 라이프 · 하루 마감`
### 5.2 MVP — 페이지 3개만 구현
| # | 라우트 | 라벨 | MVP | 담당 phase 문서 |
|---|---|---|---|---|
| 1 | `/tasks` | 작업 | ✅ 구현 | `phase-3-tasks.md` |
| 2 | `/inbox` | 인박스 | ✅ 구현 | `phase-4-inbox.md` |
| 3 | `/dashboard` | 대시보드 | ✅ 구현 | `phase-5-dashboard.md` |
| 4~13 | 결재함·자동화·여정·일정·메일·알림·리서치·여행·라이프·하루 마감 | — | ❌ **"준비 중" 플레이스홀더** | `phase-1-design-system.md` (placeholder 라우팅) |
### 5.3 경계 규칙 (반드시 지킬 것)
1. **상단 내비는 13항목 전부 노출**합니다(내비 일관성). `shell.jsx``MAIN` 배열(13항목·badge·icon·라벨)을 그대로 `frontend/components/Topbar.tsx`로 이식합니다.
2. MVP 외 10개 항목은 클릭 시 **`/(placeholder)` 라우트의 "준비 중" 화면**으로 이동(앱 셸·테마는 동일 유지).
3. **대시보드의 결재함/일정 요약 카드는 시드 데이터 기반 읽기 전용**으로 표시합니다(결재함 페이지 자체는 MVP 외이지만, 요약 카드는 `approve-data.js`/`data.js` 시드로 채움 → `GET /api/dashboard``approvals_summary`/`schedule` 제공).
4. 배지 숫자도 고정 노출: 결재함 `3`, 작업 `4`, 알림 `6`(원본 `MAIN``badge` 값). 단 작업/결재함 배지는 향후 동적화 가능하도록 `GET /api/dashboard``badges:{appr,task,noti}`로 공급.
---
## 6. 시스템 아키텍처
### 6.1 텍스트 아키텍처 다이어그램
```
┌──────────────────────────────────────────────────────────────────────────┐
│ 브라우저 │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Next.js (App Router, React, TypeScript) │ │
│ │ app/layout.tsx = ThemeProvider(next-themes) + <Topbar/> │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ /tasks │ │ /inbox │ │ /dashboard │ /(placeholder) │ │
│ │ │ 칸반/리스트 │ │ 캡처→분류 │ │ 브리핑·요약 │ "준비 중" │ │
│ │ │ ·리스크 │ │ ·확인·실체화 │ │ ·집계 │ (MVP 외 10) │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ lib/api.ts (fetch wrapper) · lib/types.ts (1:1 schemas) │ │
│ └─────────┼──────────────────────────────────────────────────────────┘ │
└────────────┼───────────────────────────────────────────────────────────────┘
│ HTTP/JSON (prefix /api) · dev: Next rewrites → :8000
┌──────────────────────────────────────────────────────────────────────────┐
│ FastAPI (Python) │
│ app/main.py (CORS, /api router include) │
│ ┌─────────────────────────── routers/ ──────────────────────────────┐ │
│ │ tree.py tasks.py inbox.py dashboard.py people.py llm.py │ │
│ └─────────┬──────────────────────────────────────────┬───────────────┘ │
│ │ services/ │ llm/ │
│ │ classification.py risk.py scaffold.py │ provider.py(IF) │
│ │ │ ollama.py │
│ │ schemas.py (Pydantic I/O) │ heuristic.py │
│ │ models.py (SQLModel) db.py (engine) │ prompts.py │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ SQLite (ari.db) │ │ LLMProvider 추상화 │ │
│ │ SQLModel + Alembic │ │ health()/classify_ │ │
│ │ seed.py (시드 적재) │ │ capture()/generate_json │ │
│ └──────────────────────┘ └────────────┬─────────────┘ │
└────────────────────────────────────────────────────────┼───────────────────┘
│ HTTP (format=json)
┌──────────────────────────────┐
│ Ollama (로컬 LLM 런타임) │
│ OLLAMA_HOST=:11434 │
│ OLLAMA_MODEL=<주입, 모델 비종속>│
│ 미가용 시 → HeuristicProvider │
└──────────────────────────────┘
```
### 6.2 데이터 흐름 (대표 시나리오: 인박스 캡처)
```
[사용자 입력 "비행기 티켓 사기"]
│ 1. POST /api/inbox/capture {kind:"text", raw:"..."}
[FastAPI inbox.py]
│ 2. inbox_item 생성 (status=new) → DB
│ 3. services/classification.py 호출
│ → LLMProvider.classify_capture(raw, context=트리/사람)
│ ├─ Ollama 가용: ollama.py (format=json 구조화 출력)
│ └─ 미가용/테스트: heuristic.py (정규식 규칙 폴백)
│ 4. inbox_classification 저장 (type/sphere/project_suggestion/reason/confidence)
│ 5. item.status = classified
[응답 {item, classification}] → 프론트가 칩/이유/행선지 카드 렌더
│ 6. (사용자가 확인) POST /api/inbox/{id}/confirm
[type=task 이면 task 생성 → materialized_task_id 연결, status=confirmed]
│ 7. 작업 페이지 GET /api/tasks 에 즉시 등장 (federation)
│ 8. GET /api/risks 가 새 task 포함해 마감/쏠림/의존성 재계산
│ 9. GET /api/dashboard 가 task_summary/inbox_recent 집계 갱신
```
---
## 7. 기술 스택 결정과 근거
> **확정 스택 — 절대 변경 금지.** (CONTRACT) 아래 표는 *근거*만 보강합니다.
| 레이어 | 선택 | 근거 |
|---|---|---|
| 프론트엔드 | **Next.js (App Router) + React + TypeScript** | 파일 기반 라우팅이 MVP 3페이지 + placeholder 그룹과 자연스럽게 매핑(`app/dashboard`, `app/inbox`, `app/tasks`, `app/(placeholder)`). TS로 `lib/types.ts` ↔ 백엔드 `schemas.py` 1:1 타입 계약. dev rewrites로 `/api` 프록시. |
| 디자인 토큰 | **CSS Variables (`styles/tokens.css`)** | 원본이 이미 `:root` CSS 변수 + `[data-theme="dark"]` 오버라이드 구조 → 무손실 이식. 프레임워크 종속 없음. |
| 테마 토글 | **next-themes** | 원본은 `document.documentElement``data-theme` + localStorage. next-themes가 동일 메커니즘(SSR 깜빡임 방지 포함)을 제공. |
| 백엔드 | **Python + FastAPI** | 타입 힌트 기반 자동 OpenAPI, Pydantic 검증으로 API 계약을 코드로 강제. LLM/분류 로직(파이썬 생태계)과 자연스럽게 결합. |
| DB | **SQLite** | 단일 파일·무설정, 로컬 완결형 MVP에 적합. 트리/중첩(`parent_id`) 인접 리스트 모델로 충분. |
| ORM | **SQLModel(SQLAlchemy) + Alembic** | SQLModel 모델 = Pydantic 스키마 기반이라 `models.py`/`schemas.py` 정합 용이. Alembic으로 스키마 진화 관리. |
| LLM | **로컬 Ollama + Provider 추상화** | 프라이버시(로컬 처리)·비용 0. **모델 비종속**: `OLLAMA_MODEL` 환경변수 주입, `LLMProvider` 인터페이스로 감싸 특정 모델 강제 금지. 미가용 시 `HeuristicProvider` 폴백으로 *오프라인/테스트에서도 분류 동작*. |
| 백엔드 테스트 | **pytest + httpx TestClient** | 라우터/서비스/분류를 결정적으로 검증. `HeuristicProvider`로 LLM 없이 골든 케이스 테스트. |
| 프론트 테스트 | **Vitest + React Testing Library**(컴포넌트), **Playwright**(E2E), **axe**(접근성) | 단위→통합→E2E→a11y 4단. 연합 흐름(캡처→확인→작업 등장)은 E2E로 검증. |
| 패키지 매니저 | 프론트 **pnpm**(또는 npm), 백엔드 **uv**(또는 pip+venv) | 빠른 설치·재현성. |
---
## 8. 리포지토리 구조
repo root: `/Users/kim1634/mycloud/personal/workspace/ari_assistant/workspace`
```
workspace/
├─ frontend/ Next.js (App Router, TypeScript)
│ ├─ app/
│ │ ├─ layout.tsx 앱 셸: ThemeProvider(next-themes) + <Topbar/>
│ │ ├─ page.tsx / → /dashboard 리다이렉트
│ │ ├─ dashboard/page.tsx ⑤ 대시보드
│ │ ├─ inbox/page.tsx ④ 인박스
│ │ ├─ tasks/page.tsx ③ 작업
│ │ └─ (placeholder)/ MVP 외 10페이지 "준비 중"
│ │ └─ [slug]/page.tsx
│ ├─ components/ 디자인 시스템
│ │ ├─ Icon.tsx 중앙 paths 맵 기반 SVG (shell.jsx P 맵 이식)
│ │ ├─ Topbar.tsx 13항목 mainnav + 검색/알림/테마/아바타
│ │ ├─ SubRail.tsx 좌측 아이콘 레일(선택; 작업 페이지)
│ │ ├─ GlassCard.tsx Badge.tsx Chip.tsx Button.tsx
│ │ ├─ SegmentToggle.tsx 칸반/리스트 전환 (캘린더는 post-MVP placeholder)
│ │ └─ Avatar.tsx
│ ├─ lib/
│ │ ├─ api.ts fetch 래퍼 (/api 호출)
│ │ ├─ types.ts schemas.py 와 1:1 대응 타입
│ │ └─ hooks/ useTree, useTasks, useInbox, useDashboard ...
│ ├─ styles/
│ │ ├─ tokens.css 디자인 토큰 (dash.css :root 이식)
│ │ └─ globals.css body 그라데이션·폰트·전역
│ ├─ tests/ Vitest + RTL
│ ├─ playwright/ E2E + axe
│ ├─ next.config.js /api rewrites
│ ├─ tsconfig.json
│ └─ package.json
├─ backend/ FastAPI
│ ├─ app/
│ │ ├─ main.py 앱 생성, CORS, /api 라우터 등록
│ │ ├─ db.py engine/session
│ │ ├─ models.py SQLModel 테이블 (§9)
│ │ ├─ schemas.py Pydantic I/O 스키마 (types.ts 와 1:1)
│ │ ├─ routers/
│ │ │ ├─ tree.py 폴더/프로젝트 트리
│ │ │ ├─ tasks.py 작업 CRUD·이동·코멘트·scaffold
│ │ │ ├─ inbox.py 캡처·분류·확인·재분류·dismiss
│ │ │ ├─ dashboard.py 집계
│ │ │ ├─ people.py 사람
│ │ │ └─ llm.py LLM health
│ │ ├─ services/
│ │ │ ├─ classification.py 분류 오케스트레이션(프롬프트+폴백)
│ │ │ ├─ risk.py 리스크 레이더(TODAY=8, tasks-risk.jsx 이식)
│ │ │ └─ scaffold.py 하위작업 제안(pickScaffold 정규식 이식)
│ │ ├─ llm/
│ │ │ ├─ provider.py LLMProvider 추상 인터페이스
│ │ │ ├─ ollama.py OllamaProvider (format=json)
│ │ │ ├─ heuristic.py HeuristicProvider (정규식 폴백)
│ │ │ └─ prompts.py 분류 프롬프트
│ │ └─ seed.py REF/assets 시드 적재
│ ├─ migrations/ alembic
│ ├─ tests/ pytest
│ └─ pyproject.toml
├─ dev/ (이 개발 문서들 — overview.md 외 7개 phase 문서)
├─ design-reference/ (원본 프로토타입 — 픽셀 충실 재현 기준)
└─ README.md
```
---
## 9. 데이터 모델 개요
SQLite. 필드명은 **CONTRACT 고정값** 그대로. `frontend/lib/types.ts``backend/app/schemas.py` 필드 1:1.
### 9.1 엔티티 관계 (텍스트 ER)
```
person ──┐ (assignee)
folder 1──* project (self ▸ parent_id 무한 중첩) 1──* task (self ▸ parent_id 무한 중첩) 1──* task_comment
inbox_item 1──1 inbox_classification (최신) │
inbox_item.materialized_task_id ─────────────────────────┘ (confirm 시 연결)
대시보드 읽기전용 시드: event · approval · goal · briefing
```
### 9.2 핵심 엔티티·필드
| 엔티티 | PK | 핵심 필드 | 비고 |
|---|---|---|---|
| `person` | `id`(text, 예 `jiwoo`) | `name, initial, color, is_me(bool)` | 색은 토큰 또는 oklch 리터럴 |
| `folder` | `id`(text, 예 `work`/`life`) | `name(업무/개인), tone, icon, sort_order, is_system(bool)` | 최상위 영역(업무/개인) |
| `project` | `id`(text) | `folder_id→folder, parent_id→project(nullable), name, tone, sort_order, pinned(bool)` | 무한 중첩, `pinned`=즐겨찾기 |
| `task` | `id`(text, 예 `k1`/`kx3`) | `project_id, parent_id(nullable), title, status, assignee_id, due(date,null), prio, notes(HTML), est, delegated(bool), sort_order, created_at, updated_at` | 무한 중첩 하위작업 |
| `task_comment` | `id`(text, 예 `c1`) | `task_id, person_id, text, created_at` | |
| `inbox_item` | `id`(text, 예 `s1`) | `kind, raw, status, created_at, materialized_task_id(null)` | |
| `inbox_classification` | `id`(text, 예 `cls1`) | `inbox_item_id(1:1 최신), type, sphere, project_id(null), proj_label, tone, due_text, when_text, extra, reason(한국어), confidence(float), model, created_at` | |
| `event`(읽기전용) | `id`(text) | `time, title, tag, dur, tone, soon(bool)` | `data.js: schedule` |
| `approval`(읽기전용) | `id`(text) | `icon, tone, risk(low\|high), time, title, detail, cta(null), alt(null), undo_label(null)` | `approve-data.js: items` |
| `goal`(읽기전용) | `id`(text) | `title, pct, sub, tone` | `data.js: goals` (tone=키 값 `blue` 등, `var(--blue)`로 저장 안 함) |
| `briefing`(읽기전용) | 단일 row/settings | `today, weather, commute, sleep, note` | `data.js` (`today="6월 7일 일요일"`=히어로 날짜 라벨; 리스크 계산 `TODAY=8`(6/8)과 혼동 금지) |
### 9.3 열거형(enum) — 고정값
| enum | 값 |
|---|---|
| `task.status` (칸반 컬럼) | `todo`(할 일) · `doing`(진행 중) · `waiting`(대기 중) · `review`(검토) · `done`(완료) — *라벨·순서 고정* (`tasks-data.js: columns`) |
| `task.prio` | `높음` · `보통` · `낮음` |
| `inbox_item.kind` | `text` · `voice` · `image` |
| `inbox_item.status` | `new` · `classified` · `confirmed` · `dismissed` |
| `inbox_classification.type` | `task` · `event` · `idea` |
| `inbox_classification.sphere` | `work` · `life` |
| `tone` (전역) | `blue` · `violet` · `coral` · `green` · `amber` · `ink` · `faint` |
> 칸반 컬럼 accent(`columns`): todo=`--faint`, doing=`--blue`, waiting=`--violet`, review=`--coral`, done=`--green`.
---
## 10. REST API 개요
FastAPI, prefix `/api`. (엔드포인트·동작 고정.) 상세 요청/응답 스키마는 `phase-2-backend.md`에서 전개.
| 메서드 | 경로 | 용도 |
|---|---|---|
| GET | `/api/health` | 헬스체크 `{status:"ok"}` |
| GET | `/api/people` | `person[]` |
| GET | `/api/tree` | `folder[]` (각 folder.projects 중첩, project.task_count 포함) |
| POST | `/api/folders` | 폴더 생성 `{name,tone?,icon?}` |
| PATCH | `/api/folders/{id}` | 폴더 수정 |
| DELETE | `/api/folders/{id}` | 폴더 삭제 |
| POST | `/api/projects` | 프로젝트 생성 `{folder_id,parent_id?,name,tone?}` |
| PATCH | `/api/projects/{id}` | 프로젝트 수정 |
| DELETE | `/api/projects/{id}` | 프로젝트 삭제 |
| POST | `/api/projects/{id}/pin` | `pinned` 토글(즐겨찾기) |
| GET | `/api/tasks?area=work\|life&project_id=&status=&assignee=` | 중첩 task 트리 |
| GET | `/api/tasks/{id}` | 단건 |
| POST | `/api/tasks` | 생성 `{title,project_id,parent_id?,status?,assignee_id?,due?,prio?,notes?,est?}` |
| PATCH | `/api/tasks/{id}` | 상태 이동·필드 수정 |
| DELETE | `/api/tasks/{id}` | 삭제 |
| POST | `/api/tasks/{id}/comments` | 코멘트 `{person_id,text}` |
| POST | `/api/tasks/{id}/scaffold` | 제목 기반 하위작업 제안. body `{create?:bool=false, use_llm?:bool=false}``{kind, icon, items:[{title,est}], created, created_task_ids[]}` |
| GET | `/api/risks?area=work` | RiskRadar 결과(지연/쏠림/의존성), 최대 3건 |
| GET | `/api/inbox` | `inbox_item[]` (+ 최신 classification) |
| POST | `/api/inbox/capture` | `{kind, raw}` → item 생성 + 동기 분류 → `{item, classification}` |
| POST | `/api/inbox/{id}/reclassify` | 재분류 `{type?}` (타입 강제 가능) |
| POST | `/api/inbox/{id}/confirm` | 분류 결과를 작업/일정/아이디어로 *실체화*(federation) → 생성 엔티티 |
| POST | `/api/inbox/{id}/dismiss` | 무시 |
| GET | `/api/dashboard` | `{user:{name,initial}, briefing, saved_today, today_routed, schedule[], task_summary:{open_count,items[]}, goals[], approvals_summary[], inbox_recent[], badges:{appr,task,noti}}` |
| GET | `/api/llm/health` | `{reachable, model, ...}` |
### 10.1 대표 요청/응답 예시
`POST /api/inbox/capture`
```json
// 요청
{ "kind": "text", "raw": "다음 주에 한국 놀러가는 비행기 티켓 사기" }
// 응답
{
"item": { "id": "s1", "kind": "text", "raw": "다음 주에 한국 놀러가는 비행기 티켓 사기",
"status": "classified", "materialized_task_id": null },
"classification": {
"type": "task", "sphere": "life",
"project_suggestion": { "id": null, "label": "개인 여행 — 한국" },
"tone": "coral", "due_text": "출발 전 · ~6/14", "when_text": "오늘 21:00 빈 시간 추천",
"extra": "가격 추적 알림 켜둠",
"reason": "구매라는 행동이 있으니 '작업' 맞아요. 작업 트리의 '개인' 아래에 '여행 — 한국' 프로젝트를 만들어 넣었어요 — 따로 섹션이 생기는 게 아니라 다른 작업과 똑같이 보여요.",
"confidence": 0.92, "model": "<OLLAMA_MODEL 또는 heuristic>"
}
}
```
`GET /api/risks?area=work` (TODAY=8 기준, 최대 3건. 강조어는 `**굵게**` 마크다운, 식별자 필드는 `task_id`)
```json
[
{ "kind": "지연 위험", "icon": "clock", "tone": "coral", "task_id": "k1",
"text": "**분기 리포트 초안 마무리** — 오늘(6/8) 마감인데 아직 진행 중이에요", "cta": "작업 열기" },
{ "kind": "업무 쏠림", "icon": "scale", "tone": "amber", "task_id": null,
"text": "미완료 작업 N건이 내게 몰려 있어요 — 팀 평균의 X배. 벅찬 작업은 위임 & 추적으로 넘겨보세요", "cta": null },
{ "kind": "의존성", "icon": "link", "tone": "violet", "task_id": "...",
"text": "**예산 섹션 작성**이(가) 늦어지면 **경영진 검토 요청 메일**까지 함께 밀려요", "cta": "후속 작업 보기" }
]
```
`GET /api/dashboard` (집계 응답 정본 — `phase-2` 소유 형태를 그대로 소비)
```json
{
"user": { "name": "지우", "initial": "지" },
"briefing": {
"today": "6월 7일 일요일",
"weather": { "temp": 24, "cond": "맑음 · 한낮 28°", "icon": "sun" },
"commute": "출근 23분",
"sleep": "수면 7시간 12분",
"note": "...<b>분기 리포트</b>..."
},
"saved_today": "47분",
"today_routed": 7,
"schedule": [
{ "time": "09:30", "title": "팀 데일리 스탠드업", "tag": "프로덕트", "dur": "15분", "tone": "blue", "soon": false }
],
"task_summary": {
"open_count": 4,
"items": [ { "id": "k1", "title": "분기 리포트 초안 마무리", "prio": "높음", "project": "분기 리포트" } ]
},
"goals": [ { "id": "g1", "title": "분기 OKR — 사용자 리텐션", "pct": 68, "sub": "12개 중 8개 달성", "tone": "blue" } ],
"approvals_summary": [ { "id": "a4", "icon": "mail", "tone": "violet", "title": "현우님께 회신 초안이 준비됐어요", "time": "보내기 대기" } ],
"inbox_recent": [ { "id": "s1", "kind": "text", "raw": "...", "type": "task", "proj_label": "개인 여행 — 한국", "tone": "coral" } ],
"badges": { "appr": 3, "task": 4, "noti": 6 }
}
```
> `approvals_summary`는 `risk=="high"`만 최대 3건, 필드는 `{id,icon,tone,title,time}`(detail/cta/alt/undo_label 미포함). `task_summary`는 `{open_count, items[]}`(total/done/by_status 아님). `goals[].tone`·`schedule[].tone` 등 `tone`은 백엔드에서 **키**(`blue` 등)로 저장하고 프론트가 `var(--tone)`로 변환. `weather.icon`은 시드에 `cloudSun`으로 저장되며 응답·표시 시 `sun`으로 매핑(R11 참조). 사용자 인사("좋은 아침이에요, {name}님")는 `response.user.name` 사용(하드코딩 금지).
---
## 11. 디자인 시스템 요약
기준 파일: `REF/assets/dash.css` `:root`. → `frontend/styles/tokens.css`로 무손실 이식. body 전역은 `globals.css`.
### 11.1 토큰 카테고리 (HEX 그대로)
| 카테고리 | 토큰 = 값 |
|---|---|
| 배경 | `--bg-top #f6efe7` · `--bg-mid #eef0f1` · `--bg-bot #e9ebed` (body: `linear-gradient(178deg, top 0%, mid 20%, bot 100%)`, `background-attachment: fixed`) |
| 카드 | `--card #ffffff` · `--card-2 #f4f5f6` · `--card-3 #eef0f1` |
| 잉크 | `--ink #211f1c` · `--ink-2 #514d47` · `--muted #8d8a85` · `--faint #b6b3ad` |
| 라인 | `--line rgba(33,31,28,.07)` · `--line-2 rgba(33,31,28,.13)` |
| 채움(선택) | `--fill #29241f` · `--fill-soft #36302a` · `--on-fill #f4efe6` |
| CTA 라임 | `--lime #c2f24a` · `--lime-hi #cdf85e` · `--lime-ink #233006` |
| 액센트 | `--blue #4f72e0` · `--coral #df7256` · `--green #4e9b66` · `--violet #8b6fd4` · `--amber #e0a23c` |
| 라운드 | `--radius 22px` · `--radius-sm 14px` |
| 그림자 | `--shadow 0 10px 30px -14px rgba(30,26,22,.22), 0 2px 6px -2px rgba(30,26,22,.06)` · `--shadow-sm 0 4px 14px -8px rgba(30,26,22,.18)` |
| 글래스 | `--glass rgba(255,255,255,.4)` · `--glass-2 rgba(255,255,255,.26)` · `--glass-brd rgba(255,255,255,.7)` · `--glass-ink rgba(26,22,18,.46)` · `--blur blur(26px) saturate(190%)` · `--glass-hi inset 0 1px 0 rgba(255,255,255,.75)` |
### 11.2 폰트
- `--font-disp: "Onest", "Pretendard", system-ui` (디스플레이/제목)
- `--font-ui: "Pretendard", "Onest", system-ui` (본문 기본)
- `--font-mono: "DM Mono", ui-monospace` (숫자/영문, `.mono { font-variant-numeric: tabular-nums }`)
- 한국어 가독성: `body { word-break: keep-all; letter-spacing: -0.011em; }`
- 로드: Onest+DM Mono(Google Fonts), Pretendard(jsDelivr CDN) — `작업.html` `<head>` 참조.
### 11.3 라이트/다크
- 메커니즘: `documentElement``data-theme="light|dark"` 속성 + localStorage 영속 → **next-themes** 사용.
- 다크 오버라이드(`[data-theme="dark"]`): `--bg-top #201d1a` · `--card #2c2925` · `--ink #f3eee6` · `--fill #f4efe6`(반전) 등. 전체 값은 `dash.css` 54~77행 그대로 이식.
- 토글 UI: 상단바 `달/해` 아이콘 버튼(`Icon name="moon"|"sun"`, `shell.jsx` Topbar).
### 11.4 글래스
반투명 카드 = `background: var(--glass)` + `backdrop-filter: var(--blur)` + `border: 1px solid var(--glass-brd)` + `box-shadow: var(--glass-hi)`. 상단바는 `backdrop-filter: blur(14px) saturate(150%)`, sticky, height 70px.
### 11.5 아이콘 패턴
- 원본: 인라인 SVG **path 딕셔너리 `P`** + `<Icon d="name"/>`가 path 문자열을 `"|"`로 split해 다중 `<path>` 렌더(`shell.jsx` 42~49행).
- 이식: `frontend/components/Icon.tsx`**중앙 `paths` 맵** + `<Icon name="..."/>`. `shell.jsx``P`**전체**(spark/grid/route/cal/check/tick/mail/target/heart/wallet/brain/zap/more/search/bell/back/send/sun/moon/swap/up/arrow/inbox/mic/image/pen/users/clock/play/plus/shield/pin)를 옮긴다. 리스크 전용 아이콘(`tasks-risk.jsx` `RP`: radar/clock/scale/link/chev)도 통합.
- 공통 SVG 속성: `viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap/Linejoin="round"`, `.ic { width:1em; height:1em }`.
- **누락 시 렌더 깨짐** 주의(원본 개발 메모) — paths 맵은 단일 출처로 통합하고 누락 키 가드.
### 11.6 공유 컴포넌트
| 컴포넌트 | 역할 | 원본 |
|---|---|---|
| `Icon(name)` | 중앙 paths 맵 SVG | `shell.jsx` `Icon` |
| `Topbar(current)` | 좌 브랜드(아리/AI LIFE OS) · 중앙 13항목 mainnav · 우 검색/알림(dot)/테마/아바타. active=글래스 pill | `shell.jsx` `Topbar`+`MAIN`, `dash.css` `.topbar/.mainnav/.top-actions/.t-btn/.t-ava` |
| `SubRail(items,active,onPick)` | 좌측 아이콘 레일(아이콘+툴팁, dot 배지, sep). MVP 작업 페이지 선택적 사용 | `shell.jsx` `SubRail` |
| `GlassCard, Badge, Chip, Button(라임 CTA), SegmentToggle, Avatar` | 프리미티브. SegmentToggle=칸반/리스트(캘린더는 post-MVP placeholder) | — |
---
## 12. 페이지 연합(federation) 흐름
아리의 핵심 가치는 화면 간 **데이터를 주고받는 하나의 흐름**입니다(PROJECT-README §5). MVP에서 구현하는 연합:
### 12.1 캡처 → 분류 → 작업 (메인 루프)
```
인박스: 한 줄 입력
→ POST /api/inbox/capture (동기 분류, classification.py)
→ 결과 칩(type/sphere)·이유(reason)·행선지 카드(proj_label) 표시
→ POST /api/inbox/{id}/confirm
└ type=task → task 생성 + materialized_task_id 연결
→ 작업 페이지 GET /api/tasks 에 "다른 작업과 똑같이" 등장 (개인=프로젝트 원칙)
```
시드 예: `s1`(비행기 티켓) → 작업 `k20` 한국행 비행기 티켓 구매(`life-trip`)로 실체화된 모습이 이미 트리에 존재(`k20.notes`: *"스마트 인박스에서 자동 생성된 작업이에요"*).
### 12.2 리스크 레이더 = 작업 데이터로 계산
`GET /api/risks`**현재 작업 트리를 직접 계산**(별도 저장 없음, `risk.py`, `TODAY=8`):
- **지연 위험**: `status≠done && due 일자 ≤ 8` → 1건. 예) `k1` 분기 리포트(due 06-08, doing).
- **업무 쏠림**: 담당자별 미완료 수 집계 → 최다 담당자가 `평균×1.5 이상 && 4건 이상`이면 경고. `me`*위임 & 추적* CTA, 타인이면 *재배분* 권유.
- **의존성**: 제목 기반 DEPS 매칭(`예산 섹션 작성→경영진 검토 요청 메일`, `데이터 전처리 파이프라인→사용자 인터뷰 5건 정리`). 둘 다 미완료면 1건. 최대 3건 반환.
### 12.3 대시보드 = 집계 화면
`GET /api/dashboard`가 다른 데이터를 읽어 집계(그래서 **마지막에 빌드**):
- `task_summary` ← tasks, `inbox_recent` ← inbox_item, `schedule`/`approvals_summary` ← 읽기전용 시드, `briefing`/`goals` ← 시드, `badges` ← 카운트.
- 따라서 페이지 빌드 순서는 **작업 → 인박스 → 대시보드**(§13 빌드 순서와 일치).
---
## 13. 개발 Phase 로드맵
### 13.1 로드맵 표
| Phase | 문서 | 목표(끝나면 동작) | 주요 산출물 | 의존성 | 예상 작업량 |
|---|---|---|---|---|---|
| 0 | `phase-0-foundation.md` | 모노레포 스캐폴딩·개발환경. Next dev + FastAPI `/api/health` + SQLite + Ollama 연결 확인 | `frontend/`·`backend/` 골격, `next.config` rewrites, `db.py`, `/api/health`, `/api/llm/health` | overview | S |
| 1 | `phase-1-design-system.md` | 토큰·앱 셸. Topbar 13항목·테마 토글·Icon·라우팅·placeholder 화면 | `tokens.css`, `globals.css`, `Icon/Topbar/SubRail/GlassCard/...`, `(placeholder)` 라우트 | 0 | M |
| 2 | `phase-2-backend.md` | 데이터모델·마이그레이션·시드·전 REST API·Ollama 추상화·분류/리스크/scaffold 서비스 | `models.py`, alembic, `seed.py`, routers 6, services 3, llm 4, `schemas.py` | 0 | L |
| 3 | `phase-3-tasks.md` | 작업 페이지: 폴더 트리 사이드바, 칸반/리스트(캘린더는 placeholder), 하위작업, 상세 드로어, 리스크 레이더 | `app/tasks/page.tsx`, 트리/칸반/드로어 컴포넌트, `useTasks/useTree/useRisks` | 1,2 | L |
| 4 | `phase-4-inbox.md` | 인박스: 캡처 컴포저, 실시간 분류, 결과 칩/이유, 확인·재분류, 행선지 카드, 작업 실체화 | `app/inbox/page.tsx`, 컴포저/분류결과/행선지 컴포넌트, `useInbox` | 1,2,3 | M |
| 5 | `phase-5-dashboard.md` | 대시보드: 아침 브리핑, 결재함/인박스 요약, 일정/작업/목표 요약, 자연어 명령 입력 | `app/dashboard/page.tsx`, 브리핑/요약 카드, `useDashboard` | 1,2,3,4 | M |
| 6 | `phase-6-integration.md` | 연합 흐름 통합·E2E·접근성·성능·실행법·MVP 수용 기준 | Playwright E2E, axe, 실행 스크립트, 수용 체크리스트 | 0~5 | M |
> 작업량 표기: S(작음)/M(보통)/L(큼) — 상대 추정.
### 13.2 빌드 순서
```
0 → 1 → 2 → ( 3 → 4 → 5 ) → 6
작업 인박스 대시보드
```
페이지는 **작업 → 인박스 → 대시보드** 순(대시보드가 작업·인박스 데이터를 집계하므로 마지막, §12.3).
---
## 14. LLM(Ollama) 추상화 & 분류 계약
### 14.1 Provider 추상화 (`backend/app/llm/`)
```python
# provider.py — 추상 인터페이스 (특정 모델 비종속)
class LLMProvider(ABC):
def health(self) -> dict: ... # {reachable, model, ...}
def generate_json(self, prompt: str, schema: dict) -> dict: ...
def classify_capture(self, raw: str, context: dict) -> Classification: ...
```
| 구현 | 파일 | 설명 |
|---|---|---|
| `OllamaProvider` | `ollama.py` | Ollama HTTP API(`/api/chat` 또는 `/api/generate`)에 `format=json` 구조화 출력. 설정 `OLLAMA_HOST`(기본 `http://localhost:11434`), `OLLAMA_MODEL`(주입). **모델 비종속** — 문서·코드에서 특정 모델 강제 금지. |
| `HeuristicProvider` | `heuristic.py` | LLM 미가용/오프라인/테스트용 **규칙 기반 폴백**. 시간표현 정규식→`event`, 행동동사+기한→`task`, 막연→`idea`. `tasks-data.js` `pickScaffold` 정규식 패턴을 분류 보조에 참고. |
선택 로직: `OllamaProvider.health().reachable`이 false면 자동으로 `HeuristicProvider`로 폴백 → **오프라인/CI에서도 분류가 동작**.
### 14.2 분류 결과(Classification) 스키마 = `inbox_classification` 필드
```ts
type Classification = {
type: "task" | "event" | "idea";
sphere: "work" | "life";
project_suggestion: { id?: string; label: string }; // 예 "개인 여행 — 한국"
due_text?: string; when_text?: string; extra?: string;
reason: string; // 사람이 읽는 한국어 한두 문장
confidence: number; // 0~1
};
```
### 14.3 분류 규칙 (프롬프트 + 폴백 공통)
1. **시간 명시 → `event`**(일정). **행동(동사)+기한 → `task`**(작업). **막연/미정 → `idea`**(아이디어).
2. **sphere**: 업무 키워드(미팅/리포트/온보딩/OKR/리뷰…) → `work`, 개인 키워드(가족/여행/선물/구독/병원…) → `life`.
3. **project_suggestion**: 기존 프로젝트 트리 매칭, 없으면 적절한 folder 아래 새 프로젝트 제안(예: 비행기 티켓 → `개인 여행 — 한국`).
4. **reason**: 사람이 읽는 한국어 1~2문장. 개인=프로젝트 철학을 말로 설명(예: *"따로 섹션이 생기는 게 아니라 다른 작업과 똑같이 보여요"*).
### 14.4 골든 분류 테스트 케이스 (반드시 테스트에 포함, = 인박스 시드 `s1~s4`)
| 입력(raw) | type | sphere | project_suggestion | 비고 |
|---|---|---|---|---|
| 다음 주에 한국 놀러가는 비행기 티켓 사기 | task | life | 개인 여행 — 한국 | `extra` 가격추적 |
| 수요일 11시 자전거 수리 맡기기 | event | life | 개인 캘린더 | when 수 11:00 |
| 엄마 생신 선물 미리 알아보기 | task | life | 가족 | 주말 블록 |
| 온보딩 환영 화면에 짧은 애니메이션 넣으면 어떨까 | idea | work | 온보딩 리디자인 · 아이디어 보드 | 보드 보관 |
---
## 15. 상태 처리 & 엣지 케이스 (전역 원칙)
각 페이지 phase 문서가 세부를 다루되, 전역 원칙은 다음과 같습니다.
| 상태 | 전역 처리 |
|---|---|
| **로딩** | 글래스 스켈레톤(카드 형태 유지). 데이터 hook(`lib/hooks/`)의 `isLoading`. |
| **빈(empty)** | 인박스 빈: *"적을 때는 분류하지 않아요 — 떠오른 걸 그냥 적어보세요"* 류 안내. 작업 빈 컬럼: 컬럼 라벨 유지 + placeholder. |
| **에러** | `lib/api.ts`가 비2xx를 throw → 카드 단위 에러 + 재시도 버튼. 전체 앱 셸(Topbar/테마)은 유지. |
| **오프라인/LLM 미가용** | `GET /api/llm/health.reachable=false` → 분류는 `HeuristicProvider`로 계속 동작, `classification.model="heuristic"` 표기. UI는 "아리가 규칙 기반으로 분류했어요" 류 미세 힌트(선택). |
| **HTML 시드(notes/briefingNote)** | 신뢰된 시드지만 사용자 입력 경로(`task.notes`)는 새니타이즈(예: DOMPurify) 후 렌더. |
| **무한 중첩** | project/task `parent_id` 깊이 무제한 — 렌더는 들여쓰기(depth), API는 중첩 트리 반환. 깊이 폭주 가드 권장. |
| **삭제 연쇄** | folder/project 삭제 시 하위 처리 정책은 `phase-2-backend.md`에서 확정(이 문서는 엔드포인트만 정의). |
---
## 16. 테스팅 & 검증
> 본 문서 자체는 *지도*이므로 "검증" = **문서 정확성 + 각 phase로의 정합성**을 점검하는 것입니다. 동시에, 전 phase가 공통으로 쓸 **테스트 도구·실행 명령·통과 기준**을 여기서 한 번에 정의합니다(각 phase 문서는 이를 재사용).
### 16.1 실행 명령 (전 phase 공통 레퍼런스)
```bash
# 백엔드 (backend/)
uv run pytest # 또는: pytest
uv run pytest -k "classify" # 골든 분류 케이스만
uv run uvicorn app.main:app --reload # 개발 서버 (:8000)
uv run alembic upgrade head # 마이그레이션
uv run python -m app.seed # 시드 적재
# 프론트엔드 (frontend/)
pnpm dev # Next dev (:3000, /api → :8000 rewrite)
pnpm test # Vitest + React Testing Library (컴포넌트)
pnpm playwright test # Playwright E2E
pnpm playwright test --grep @a11y # axe 접근성 (태그 기반, 예시)
pnpm build && pnpm start # 프로덕션 빌드 검증
```
### 16.2 이 문서(overview)의 검증 케이스
| # | 검증 항목 | 통과 기준 |
|---|---|---|
| O-1 | 토큰 값 일치 | §11 표의 모든 HEX/px가 `REF/assets/dash.css` `:root`와 100% 일치 |
| O-2 | people 일치 | §4.2 5인의 id/name/initial/color/is_me가 `tasks-data.js: people`와 일치(수아 oklch 포함) |
| O-3 | enum 일치 | §9.3 모든 enum 값·라벨·순서가 CONTRACT/`columns`와 일치 |
| O-4 | API 표 완전성 | §10 표가 CONTRACT의 24개 엔드포인트를 빠짐없이 포함 |
| O-5 | 골든 케이스 = 시드 | §14.4 4건이 `sinbox-data.js: items` s1~s4와 type/sphere/proj 일치 |
| O-6 | TODAY 구분 | §4.3에 `today="6월 7일 일요일"` vs 리스크 `TODAY=8` 차이가 명시 |
| O-7 | 빌드 순서 | §13.2가 `0→1→2→(3→4→5)→6`, 페이지 작업→인박스→대시보드 |
| O-8 | 상호 참조 | 모든 phase 문서가 정확한 파일명(`phase-N-*.md`)으로 링크됨 |
| O-9 | 13항목 내비 | §5.1/§11.6의 MAIN 항목·badge(결재함3/작업4/알림6)가 `shell.jsx: MAIN`과 일치 |
### 16.3 전 phase 공통 통과 기준(요약, 상세는 각 문서)
- 백엔드: `pytest` 전건 통과, 골든 분류 4케이스 통과(LLM 없이 `HeuristicProvider`로도), `GET /api/risks`가 TODAY=8로 지연/쏠림/의존성 최대 3건 정확.
- 프론트: `pnpm test` 컴포넌트 통과, `pnpm playwright test` 연합 E2E(캡처→확인→작업 등장) 통과, axe 위반 0(심각도 serious 이상).
- 디자인 충실도: 라이트/다크 토큰 적용, 상단바 13항목·테마 토글 동작, 글래스/라운드/그림자가 토큰과 일치.
### 16.4 수동 QA 체크리스트 (MVP 전체)
- [ ] `/` 진입 시 `/dashboard`로 리다이렉트.
- [ ] 상단 내비 13항목 모두 표시, MVP 외 10항목 클릭 시 "준비 중" 화면(셸 유지).
- [ ] 테마 토글(달/해) 동작 + 새로고침 후 유지(localStorage).
- [ ] 인박스에 4개 골든 문장 입력 → 각각 task/event/idea·work/life·올바른 행선지·한국어 이유 표시.
- [ ] 인박스 작업형 항목 확인 → 작업 페이지 트리에 등장.
- [ ] 작업 페이지 칸반 5컬럼(할 일/진행 중/대기 중/검토/완료) 라벨·순서.
- [ ] 리스크 레이더에 지연(분기 리포트)·쏠림·의존성 경고 표시.
- [ ] 대시보드 아침 브리핑·일정 4건·목표 3개·결재함/인박스 요약 표시.
- [ ] 한국어 줄바꿈 `word-break: keep-all` 적용(어절 단위 줄바꿈).
---
## 17. 완료 기준 (Definition of Done)
이 진입 문서의 DoD:
- [x] 제품 비전·3대 철학(적을 때 분류 안 함 / 이미 해뒀어요 / 개인=프로젝트) 서술.
- [x] 페르소나(지우)·팀원 5인·6/7~12 한 주·TODAY=8 명시.
- [x] MVP 범위(작업/인박스/대시보드)와 13페이지 경계·placeholder 규칙.
- [x] 텍스트 아키텍처 다이어그램(브라우저↔FastAPI↔SQLite↔Ollama)·데이터 흐름.
- [x] 스택 결정·근거표.
- [x] 모노레포 전체 트리.
- [x] 데이터 모델(ER·필드·enum) 표.
- [x] REST API 24엔드포인트 표 + 요청/응답 예시.
- [x] 디자인 토큰(HEX/px)·폰트·라이트/다크·글래스·아이콘 패턴.
- [x] 연합 흐름(캡처→분류→작업, 리스크=작업계산, 대시보드 집계).
- [x] phase 0~6 로드맵 표 + 빌드 순서.
- [x] LLM Provider 추상화 + 분류 스키마/규칙/골든 케이스.
- [x] 용어집·문서 세트 사용법.
- [x] 모든 phase 문서를 정확한 파일명으로 상호 참조.
---
## 18. 용어집
| 용어 | 정의 |
|---|---|
| **인박스(Inbox)** | "일단 적으세요"의 진입점. 텍스트/음성/이미지 캡처(`inbox_item`)를 받아 아리가 분류. MVP 핵심 페이지. |
| **캡처(Capture)** | 인박스에 던진 한 줄/메모. `POST /api/inbox/capture`로 생성 + 동기 분류. |
| **분류(Classification)** | 캡처를 `task/event/idea` + `work/life` + 행선지 프로젝트 + 한국어 `reason`으로 판정한 결과(`inbox_classification`). |
| **실체화(Materialize / federation)** | 분류 결과를 실제 엔티티로 확정. `confirm` 시 task 생성 + `materialized_task_id` 연결 → 작업 페이지에 등장. |
| **결재함(Approvals)** | 아리가 "이미 해둔" 자동 처리를 모아 승인/되돌리기(`approval`). MVP는 대시보드 요약 카드로만 읽기전용 노출. risk=low(되돌리기)/high(확인 후 실행). |
| **리스크 레이더(Risk Radar)** | 작업 트리를 직접 계산해 **지연 위험·업무 쏠림·의존성**을 최대 3건 경고(`GET /api/risks`, TODAY=8). |
| **sphere** | 영역 구분 `work`(업무) / `life`(개인). "개인=프로젝트" 원칙상 별도 섹션이 아니라 *필터*. |
| **folder(영역)** | 트리 최상위(업무/개인). 사용자 생성 가능, `is_system`으로 시스템 폴더 구분. |
| **project** | folder 아래 무한 중첩 가능한 프로젝트. `pinned`=즐겨찾기. |
| **task / 하위작업** | 작업. `parent_id`로 무한 중첩(하위작업도 그 자체로 완전한 작업). status 5단(칸반). |
| **scaffold(자동 쪼개기)** | 작업 제목 기반 하위작업 제안(`POST /api/tasks/{id}/scaffold`). `pickScaffold` 정규식으로 research/dev/doc/generic 템플릿 선택. |
| **tone** | 색 토큰 집합 `blue/violet/coral/green/amber/ink/faint`. 카드 점·바·라벨 액센트. |
| **prio(우선순위)** | `높음/보통/낮음`. |
| **브리핑(Briefing)** | 대시보드 아침 요약(날씨/출근/수면/노트). `data.js`. |
| **글래스(Glass)** | 반투명 카드 스타일(`--glass*` + `--blur`). 클린 화이트 글래스 테마의 정체성. |
| **Provider 추상화** | LLM 교체 가능 인터페이스(`LLMProvider`). `OllamaProvider`(로컬) ↔ `HeuristicProvider`(폴백). 모델 비종속(`OLLAMA_MODEL`). |
| **SubRail** | 좌측 아이콘 레일(아이콘+툴팁+dot+sep). MVP는 작업 페이지에서 선택적 사용. |
---
## 19. 이 문서 세트 사용법
### 19.1 읽는 순서
1. **`overview.md`(이 문서)** — 전체 그림·계약·로드맵. (현재)
2. **`phase-0-foundation.md`** — 모노레포 스캐폴딩 & 개발환경(Next + FastAPI + SQLite + Ollama 연결 확인).
3. **`phase-1-design-system.md`** — 디자인 토큰 이식 + 앱 셸(Topbar/테마/Icon/레이아웃/placeholder 라우팅).
4. **`phase-2-backend.md`** — 데이터모델·마이그레이션·시드·REST API·Ollama 추상화·분류/리스크/scaffold 서비스.
5. **`phase-3-tasks.md`** — 작업 페이지(트리 사이드바·칸반/리스트[캘린더는 placeholder]·하위작업·상세 드로어·리스크 레이더).
6. **`phase-4-inbox.md`** — 인박스(캡처 컴포저·실시간 분류·칩/이유·확인/재분류·행선지·작업 실체화).
7. **`phase-5-dashboard.md`** — 대시보드(아침 브리핑·결재함/인박스 요약·일정/작업/목표 요약·자연어 명령 입력).
8. **`phase-6-integration.md`** — 연합 통합·E2E·접근성·성능·실행법·MVP 수용 기준.
### 19.2 작업 방식
- 각 phase 문서는 **개요/선행·산출물/상세구현/데이터·타입·API/디자인 충실도/상태·엣지/테스팅·검증/DoD/다음 단계** 9개 섹션을 가집니다.
- 항상 **빌드 순서(§13.2)** 를 지키세요: `0→1→2→(3→4→5)→6`.
- 값(색·필드·문구)은 항상 **`REF/assets`의 원본을 단일 출처**로 삼고, 의심되면 이 문서의 표와 대조하세요.
- 계약(데이터 모델·API·토큰·스택·명명)은 **변경 금지** — 변경이 필요하면 먼저 이 `overview.md`를 갱신하고 전 phase에 전파합니다.
---
*끝. 다음 문서: `phase-0-foundation.md`*