# 아리(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 문자열**을 포함합니다(``, `

`, `
`). 렌더 시 신뢰된 시드로 취급하되 [§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) + │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ /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) + │ │ ├─ 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": "" } } ``` `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": "...분기 리포트..." }, "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` `` 참조. ### 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`** + ``가 path 문자열을 `"|"`로 split해 다중 `` 렌더(`shell.jsx` 42~49행). - 이식: `frontend/components/Icon.tsx` — **중앙 `paths` 맵** + ``. `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`*