bookquote

화면 설계 — 서재 /library (이미 구현 — 역정리 + 보강 권고)

그룹 3. 입력 근거: lib/features/library/library_screen.dart, lib/features/book/data/book_repository.dart(코드 기준), competitor-screen-analysis §3·§5.8, DECISIONS 2026-05-12(서재 = 책↔인용구 세그먼트). 빈/로딩/에러 뷰 + RefreshIndicator + invalidate 패턴은 다른 화면(홈·인용목록·책상세)이 따라야 할 레퍼런스.

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

2. 와이어프레임

┌─────────────────────────────────────────┐
│ 내 서재   [ 책 ] [ 인용구 ] [ 캘린더 ]   │  ← (PR17) 3 세그먼트. 풀스펙 = library-calendar.md
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────────┐ │  _BookRow (ListView.separated)
│ │▎┌──┐ 미드나잇 라이브러리      [7구절]│ │  (보강) 좌측 4px 표지색 띠 + trailing "N구절" 배지
│ │▎│표│ 매트 헤이그                      │ │  현행: 표지 + 제목 2줄말줄임 + 저자 + publisher·pubDate
│ │▎└──┘ 인플루엔셜 · 2021               │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │▎┌──┐ 니코마코스 윤리학         [3구절]│ │
│ │▎│표│ 아리스토텔레스 · 길              │ │
│ │▎└──┘                                 │ │
│ └─────────────────────────────────────┘ │
│              … (RefreshIndicator)         │  현행 limit 50 — 페이지네이션 없음
│                              ╭───────╮  │  FAB.extended "책 추가" → showBookSearchSheet
│                              │ + 책  │  │
│                              ╰───────╯  │
└─────────────────────────────────────────┘

[ 빈 상태 — 책 0권 ]  현행 _EmptyView: 아이콘 + "아직 책이 없어요" + 안내 (ListView라 pull-to-refresh 동작)
                       (보강) 본문에도 [+ 책 추가] 버튼 — 홈 빈 상태와 일관

3. 상태 (코드 기준)

| 상태 | 처리 | 심각도 | |—|—|—| | 로딩 | myLibraryProvider(FutureProvider.autoDispose<List<Book>>book_repository.listMyLibrary(), limit 50, added_at desc, user_books.select('book:books(*)') 조인). asyncLibrary.when(loading: CircularProgressIndicator(accent500)). (보강: 스켈레톤 권고) | 낮음 | | 빈 | data == []_EmptyView(“아직 책이 없어요” + 안내). ListView라 빈 상태에서도 pull-to-refresh 동작. (보강: 본문에 [+ 책 추가] 버튼) | 낮음 | | 에러 | _ErrorView(“서재를 불러오지 못했어요… ($error)” — raw error 노출, 재시도 버튼 없음) | 중간 (개선) | | 데이터 | _BookList(ListView.separated, _BookRow: BookCover + 제목 2줄말줄임 + 저자 + publisher·pubDate). _BookRow 탭 → context.push('/book/${book.id}') | — | | 책 추가 (FAB) | _onAddBook: showBookSearchSheet(context)Book? → null/unmounted면 return → bookRepository.addToLibrary(book.id)ref.invalidate(myLibraryProvider) → SnackBar(“"$title" 서재에 추가됐어요” + “열기” → /book/${book.id}). on BookRepositoryException → SnackBar(“서재 추가 실패: ${e.message}” — raw message 노출) | 중간 (개선) | | (보강) 오프라인 | connectivity_plus 미연동 — 오프라인이면 myLibraryProvider가 그냥 에러로 떨어짐. stale-while-revalidate 미적용 | 중간 (개선) | | 권한 거부 | 해당 없음 | — |

4. 인터랙션

5. 토큰 매핑

6. 재사용 / 신규

7. 엣지 / 접근성 + 수정·보강 항목

교차 관심사: ① 오프라인=1급(stale-while-revalidate — 보강) · ⑥ 에러 일관성(raw 노출 금지) · ⑦ 인증 가드 · ⑤ 책 검색 시트 왕복 시 (서재 화면 자체 state 없으니 무관). 양호(유지·레퍼런스): RefreshIndicator + asyncX.when(data/loading/error) 패턴 — 홈·인용목록·책상세가 이걸 따름. _BookRow null-guard. FAB → 시트 → 담기 → SnackBar(action) 흐름. 수정·보강 권고 (현행 ≠ 권고):

변경 이력