bookquote

화면 설계 — 인용구 목록·상세

그룹 1 · Stage 2. 입력 근거: competitor-screen-analysis §5 (인용 목록/상세 행), QA-1, Readwise 카드 문법 + StoryGraph 무드. 위치(별도 탭 vs 서재 내 뷰)는 §1에서 결정 대기.


1. 목적 / 진입·이탈 / 라우트


2. 와이어프레임

┌─────────────────────────────────────────┐
│ 서재          [ 책 ]  [ 인용구 ]    🔍   │  서재 탭 헤더 — 책↔인용구 세그먼트 + 검색
├─────────────────────────────────────────┤
│ 〔전체〕〔위로 12〕〔먹먹 8〕〔새벽3시 5〕〔통찰 3〕│  무드 필터 칩(개수 표시) + 가로 스크롤
│ ┌─────────────────────────────────────┐ │
│ │ ┌──┐ "가장 깊은 밤에 가장 빛나는      │ │  인용구 카드 (Readwise 문법)
│ │ │표│  별이 보인다."                   │ │  표지 썸네일 + 인용구 2~3줄 + 책/저자/페이지
│ │ │지│  미드나잇 라이브러리 · p.132     │ │  + 무드 칩(색 코딩) + 메모 1줄(있으면)
│ │ └──┘  〔위로〕〔먹먹〕                  │ │
│ │       메모: 힘들 때 다시 읽으려고     │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ ┌──┐ "우리는 우리가 반복하는 것이다." │ │
│ │ │표│  니코마코스 윤리학 · p.55  〔통찰〕│ │
│ │ └──┘                                 │ │
│ └─────────────────────────────────────┘ │
│              … (무한 스크롤)              │
│ ┌── 정렬: 최근순 ▾ ──┐                    │  정렬 = 최근순 / 책별 그룹 / 페이지순
└─────────────────────────────────────────┘

필터는 3개로 충분(Readwise만큼 다차원일 필요 없음): 무드별(칩) / 책별(정렬 “책별 그룹” 또는 책 상세에서 진입) / 최근순(기본 정렬) + 상단 검색. “라이브러리가 커져도 안 무너지게”가 목표.


3. 상태

상태 처리 표시 심각도
로딩: 첫 페이지 스켈레톤 카드 3~4개. RefreshIndicator(pull-to-refresh). <800ms Inline 스켈레톤 낮음
로딩: 페이지네이션 하단 spinner. cursor-after(created_at + id), 페이지 15(DECISIONS 2026-05-10 — offset 금지). 중복 fetch 가드 Inline (하단) 낮음
빈: 인용구 0개 “아직 인용구가 없어요. 좋아하는 책의 한 줄을 저장해보세요.” + [+ 인용구 추가] Empty 중간
빈: 특정 무드 필터 결과 0개 “이 무드의 인용구가 아직 없어요” + [전체 보기] Empty (영역) 낮음
빈: 검색 결과 0개 ”‘$검색어’와 일치하는 인용구가 없어요” Empty (영역) 낮음
에러: 목록 로드 실패 (네트워크/RLS) “인용구를 불러오지 못했어요” + [다시 시도]. RLS 거부(PGRST301)면 세션 만료 Modal Empty 에러 / Modal 중간
에러: 인용구 삭제 실패 낙관적 제거 롤백 + “삭제하지 못했어요” Toast Toast 중간
오프라인 마지막 캐시 목록 표시(stale-while-revalidate) + 상단 “오프라인” 배너. 동기화 대기 중 인용구(아웃박스)는 “동기화 대기” 뱃지 + (책 매칭 실패 시) “책 정보 필요” 액션 배너 + 뱃지 중간
권한 거부 해당 없음

4. 인터랙션


5. 토큰

영역 토큰
화면 secondary200 배경. 세그먼트: 선택 primary900/secondary50, 미선택 primary400/border primary200
무드 칩 quote-input.mdmoodColors 맵 — 미선택: 무드별 연한 배경 + 어두운 텍스트, 선택: primary900/secondary50
인용구 카드 secondary100 배경 + primary100 border + AppRadius.md + AppShadow.card / 표지 BookCover 34×50 / 인용구 AppFonts.quote 13 primary800 (2~3줄 말줄임) / 책·저자·페이지 ui xxs primary400 / 메모 ui xs primary500 italic
빈/에러 Empty 패턴 — primary400 + CTA accent500
동기화 대기 뱃지 semanticWarningLight/semanticWarning xxs

6. 재사용 / 신규

재사용: library_screen.dart(서재 탭 — 세그먼트 추가), RefreshIndicator·_EmptyView·_ErrorView 패턴, BookCover, quote_repository.listMyQuotes({moods}) / myQuotesProvider (quote-input.md 신규), cursor-after 페이지네이션 패턴(book_providers 참고). 신규: lib/features/quote/presentation/quote_list_view.dart(서재 탭 안의 인용구 뷰), lib/features/quote/presentation/widgets/quote_card.dart(목록용 카드 — card_editorquote_card.dart와 이름 충돌 주의, quote_list_card.dart로), quote_repositorylistMyQuotes + count-by-mood, moodColors 맵(tokens.dart).


7. 엣지 / 접근성

교차 관심사: ① 오프라인=1급(stale-while-revalidate + 아웃박스 뱃지) ② 데이터 유실 = 인용구는 DB ③ PII = 인용구 텍스트·검색어 미전송 ④ 막다른 골목 = 빈 상태마다 출구 CTA ⑤ 해당 없음 ⑥ 에러 표시 일관성 ⑦ 인증 필요(내 인용구) ⑧ 해당 없음.

엣지 심각도 처리
매우 긴 목록(인용구 수백 개) 낮음 ListView.builder 가상화. 카드 안 표지는 작게
인용구에 줄바꿈·이모지 낮음 2~3줄 말줄임에서 줄바꿈은 공백 취급(미리보기), 확장 시 원본. 이모지 컬러 글리프
무드 필터 + 검색 동시 낮음 AND 조합
책 없는 인용구(BOOK_UNRESOLVED) 중간 표지 자리에 placeholder(“책 미연결”) + “책 연결하기” 인라인 액션
알 수 없는 무드 값(앱 업데이트로 셋 변경) 낮음 “기타”로 그룹 또는 그대로 표시하고 필터에서 무시 — 데이터 보존
동기화 대기 인용구가 목록 상단에 임시 표시 중간 “동기화 대기” 뱃지 → 완료 시 실데이터 교체(깜빡임 최소)

접근성: 인용구 카드 ≥48dp 탭 영역, '$book의 인용구: $text, $page페이지, 무드: $moods' semantics. 무드 칩 = 색 + 텍스트 + 개수. 세그먼트 toggle semantics. 빈 상태 CTA에 명확한 라벨. 검색 필드에 label: '인용구 검색'.


변경 이력