크라우니메일 M2 — UI v3 픽셀 재현
개요
mail.crowny.org v3 재구현 프로젝트(작업계획-v3.md)의 M2 모듈. 핸드오프 디자인(시안 1a~1g, 프로토타입, 크라우니캔버스 디자인 규칙.css)을 픽셀 단위로 재현한 단일 SPA를 신규 작성.무엇을 했는지
pages/앱v3.html— 단일 파일 SPA. 화면: 1a 받은편지함 3단 / 1b 읽기+관계패널 / 1c 컴포즈(모달) / 1d 대기실 / 1e 설정 / 1f 모바일 탭바 / 1g 모바일 읽기(반응형 ≤767px). 다크 =[data-theme="다크"]토큰 스왑.pages/로그인v3.html— 최소 로그인 화면(통합 아이디/비밀번호, 골드 버튼 1개, POST /api/auth/login → localStoragemail_session).- 렌더은 전부
h()DOM 헬퍼(createElement+createTextNode)로 구성 — innerHTML은=''클리어에만 사용, 사용자 데이터 HTML 주입 경로 없음. /demo경로 감지 시 API 호출 없이 내장 데모 데이터(김선경 페르소나, inbox 8·대기실 2·sent 2·drafts 1·scheduled 1·trash 1·연락처 6·지갑 120맘)로 전 화면 동작.- 서버 모드는 작업계획-v3.md API 계약(list/read/send/delete/contacts/tier/memo/gate/relationship/wallet/audit/policy/settings/setup) 전수 호출부 구현, X-Session 헤더, 401→로그인 리다이렉트. M1 서버가 가동 중이지 않아 실제 curl 검증은 못함 — 계약 준수 여부만 정적으로 맞춤.
- Interactions: 읽음 처리+맘 적립 토스트, 답장/전달 프리필, 휴지통 영구삭제 확인, Tier 변경/수락/차단 확인 다이얼로그+감사 로그, 대기실 수락→inbox 편입, 컴포즈 Enter 칩+슬래시 메뉴 5항목+맘 스테퍼, 예약/임시저장, 다크 칩, 토스트 2.6s, 서비스 경계 이동 안내 모달, 설문형 초기세팅 위저드(5문항) 모두 구현.
- 외부 메일에는 캔버스/맘/cName 렌더 자체를 하지 않도록
c.cr가드(방어적으로 이중 처리). 대기실 메일은 첨부 잠금 문구·답장 버튼 미노출.
정적 검증 결과 (실측)
node -e "new Function(...)"문법 검사: PASS (앱v3.html, 로그인v3.html)- innerHTML 사용: 8건 전부
=''클리어뿐, HTML 문자열 주입 0건 - 하드코딩 hex 출처 대조(시안.dc.html+프로토타입.dc.html+토큰CSS 합집합 96종 대비): 출처 불명 hex 0건
- "맘 N개" 패턴 grep: 0건
- 진짜 픽토그래프 이모지 grep: 0건 (✕ 4건은 프로토타입 원본에도 동일하게 쓰인 텍스트 기호이며 이모지 아님)
[data-theme="다크"]스왑 정의: 존재- 탭바(
#tabbar)·FAB(.fab)·미디어쿼리 767: 존재 - 화면 마커
data-screen="1a"~"1g": 7종 전부 1건씩 존재 - 시안 대조 값: 사이드바 246px · 목록 372px · 관계패널 330px · 상단바 50px · 최소폭 1180px · 둥글기 14/12/11/9/7/12 · 본문 13px 전부 grep 확인
- QA 체크리스트(docs/12 §3) 8항목 중 정적으로 확인 가능한 항목 전수 통과 (다크 골드/그린/적갈 원색 재정의 0건 포함)
관련 파일
/Users/ef/crowny-mail/pages/앱v3.html/Users/ef/crowny-mail/pages/앱v3.한선(동반 — 핵심 업무로직 한선씨 대응본)/Users/ef/crowny-mail/pages/로그인v3.html/Users/ef/crowny-mail/pages/로그인v3.한선(동반)- 참조 SSOT:
/Users/ef/Downloads/design_handoff_크라우니메일/(시안·프로토타입·토큰 CSS),/Users/ef/crowny-mail/설계/작업계획-v3.md
수정 이력 — 크롬 시운전 1회차 결함 5건 (2026-07-24 추가)
메인 세션 실브라우저 시운전에서 보고된 5건을 pages/앱v3.html만 수정해 해결(라이브 크라우니메일서버.toau가 60KB 초과 파일용 _대용량HTML전송 스트리밍 경로로 서빙 중이라 파일만 고치면 즉시 반영됨을 확인).
- 버그1 (모바일 상단바 낱자 깨짐): 원인 = 상단바 6개 요소(로고·mail.crowny.org 배지·검색창·동기화·설정버튼·프로필칩)가 767px 폭에서 전부
flex-shrink경쟁 → 로고 텍스트가 음수에 가까운 폭으로 눌려 한글이 글자 단위로 줄바꿈.@media(max-width:767px)에서 검색창·호스트배지·설정버튼·프로필칩의 이름/아이디를 숨기고 나머지에white-space:nowrap+flex:none부여로 해결. - 버그2 (모바일 읽기 카드 낱자 깨짐): 발신자 카드·캔버스 카드가
flex:1;min-width:0텍스트 블록 + 버튼을 한 줄(flex-wrap없음)에 강제 배치 → 버튼이 텍스트를 0px 근처로 압착. 각 카드에flex-wrap:wrap과 텍스트 블록flex:1 1 180~200px;min-width:150~190px를 부여해 좁은 폭에서 버튼이 다음 줄로 내려가도록 수정. - 버그3 (모바일 "설정" 탭 → 빈 목록): 탭 클릭 핸들러가 무조건
backToList()(목록 패널 표시·본문 숨김)를 호출 → box는 settings로 바뀌었지만 실제 설정 화면(#content)이 숨겨지고 빈 메일 목록만 보임. settings 탭만모바일상세클래스를 붙여 #content를 보이게 분기 처리. - 버그4 (데스크톱 관계 패널 미노출, 근본 원인 2개 중첩): ① 임의로 넣었던
@media(max-width:1280px){#relpanel{display:none}}(스펙에 없는 값)이 1280px 미만 폭에서 패널을 지움 →max-width:767px로 교정(모바일에서만 숨김). ②#appRoot{overflow:hidden}이 앱 최소폭 1180px를 넘는 콘텐츠를 스크롤 없이 그냥 잘라버림(README "좁으면 가로 스크롤" 위반) →overflow-x:auto로 교정. 크롬 자동화로 직접 DOM 측정(getBoundingClientRect)해 수정 전#relpanel너비 0 → 수정 후 330px 실측 확인. - 확인5 (미읽음 카운트): 실측 결과 버그 아님 — 읽음 처리 시
boxCounts()가state.mails에서 매번 재계산되어 정상 반영(3→2 실측 확인). 부수적으로.읽기래퍼의overflow-y:auto뒤에overflow:hidden이 와서 스크롤을 무효화하던 잠재 버그도 같이 발견해overflow-x:hidden;overflow-y:auto로 정리.
mail.crowny.org/demo 실사이트에 크롬 자동화로 접속. 이 샌드박스의 resize_window가 실제 뷰포트를 바꾸지 못하는 한계(요청한 1490/1920px에도 window.innerWidth가 863에 고정)가 있어, 모바일 폭 검증은 같은 페이지에 width:390px <iframe>을 주입해 iframe 자체의 @media 컨텍스트로 확인(검증 후 iframe 제거). 데스크톱은 #appRoot 가로 스크롤로 330px 관계 패널 노출을 실측.수정 후 정적 게이트 재실행: 문법 PASS, 하드코딩 hex 출처 불명 0건, innerHTML은 여전히 ='' 클리어만, "맘 N개" 0건, 진짜 이모지 0건 — 전부 재확인.
2a·2b 추가 구현 (핸드오프 개정판 "design_handoff_크라우니메일 2" 반영, 2026-07-24)
새 핸드오프 폴더의 README·시안.dc.html id="2a"/id="2b" 프레임을 정독해 pages/앱v3.html에 추가(수정 파일은 이 하나뿐). 기존 5건 수리(모바일 상단/카드/설정탭/관계패널/오버플로)와 충돌 없도록 브레이크포인트를 3단으로 정리:
- ≤767px = 모바일(1f/1g, 기존 그대로) — 탭바·FAB.
- 768~1280px(신규, 2a) = 태블릿 — 아이콘 레일 64px(
#rail, 42px 버튼·활성#3B2B18·대기실 주황 배지) + 목록 320px + 읽기 2단. 사이드바·관계패널·탭바 전부 숨김. - >1280px = 데스크톱(1a/1b, 기존) — 사이드바 246 + 목록 372 + 읽기 + 관계패널 330.
- 바타입(2b, 별도 축) =
(max-height:420px) and (orientation:landscape)일 때#appRoot전체를 대체하는#barType(글랜스 전용, 좌측 224px 다크 존 + 우측 목록, 행 84px·터치 ≥48px).
#appRoot{overflow:hidden} 클리핑 버그는 그대로 고쳐진 채 유지, 거기에 태블릿 전용 레이아웃(2a)이 정식으로 채워지면서 1280 이하 구간이 "고장난 데스크톱"이 아니라 "의도된 태블릿 화면"이 되었다.해석사항(명세 침묵 부분, 지시대로 명기):
- 2b 트리거 기준 — README·시안 모두 CSS 트리거 값을 명시하지 않아 지시받은 폴백
(max-height:420px) and (orientation:landscape)그대로 적용. - 2a 아이콘 레일 8개 아이콘은 라벨이 없어 정확한 매핑이 불명 — 쓰기/구분선/받은편지함(활성)/대기실(배지)/보낸편지함/임시보관(다이아몬드 아웃라인)/연동·명함집(그라데이션 다이아몬드)/spacer/설정(원형) 순으로 데스크톱 사이드바 동작을 재사용해 매핑.
- 2b "작성·답장은 폰/데스크톱으로 이어서"는 상세 읽기 패널을 이 좁은 화면에 넣지 않는다는 뜻으로 해석 — 행 클릭은 읽음 처리 + "상세는 폰·데스크톱에서 이어보기" 토스트만 하고 리딩 뷰는 열지 않음. 다만 "메일 쓰기" 버튼은 (기기 구분이 없는 단일 SPA라) 실제 컴포즈 모달을 열도록 구현해 기능 손실을 막음.
- 2b 검색창 placeholder 문구("검색")·2a 검색창 축소 폭(max-width:300)은 시안 값 그대로 CSS로 반영했으나, 텍스트 자체(플레이스홀더 문구)는 데스크톱과 동일하게 유지 — CSS만으로는 placeholder 문자열을 바꿀 수 없어 별도 반응형 로직을 추가하지 않음(경미한 편차로 기록).
mail.crowny.org/demo에 iframe 트릭으로 1024×768(태블릿) / 800×360(바타입) / 390×844(모바일 회귀) / 1490×900(데스크톱 회귀) 4가지 뷰포트를 각각 주입해 실측. 태블릿 = 레일·목록320·relpanel 없음 확인, 바타입 = 좌측존·필터칩·행 클릭 시 읽음처리+토스트+대기실 전환 전부 실측, 모바일·데스크톱 = 기존 5건 수리 결과 회귀 없음 확인. 검증용 iframe은 매번 제거.수정 후 정적 게이트 재실행: 문법 PASS · 하드코딩 hex(구+신 핸드오프 96종 대비) 출처 불명 0건 · data-screen="1a"~"1g"/"2a"/"2b" 9종 전부 1건씩 존재 · #rail(64px/42px) · #barType(224px/1080×360 주석) 시안 값 grep 확인.
서버 모드 발송 UX 소보완 3건 (실사용 "안 보내짐" 혼동 신고, 2026-07-24)
pages/앱v3.html만 수정.
- apiFetch 오류 신호 보강:
fetch자체 예외(네트워크 단절)와!r.ok(4xx/5xx) 모두{__error:true, status, message}형태로 반환하도록 변경(성공 시 반환 모양은 기존과 동일해 다른 호출부는 무영향).doSend서버 모드에서res.__error(또는res자체가 null — 401 리다이렉트 케이스)를 감지하면 "발송 실패 — <사유>" 토스트만 띄우고return— 이전에는 오류 응답 바디({"error":"..."})가 그냥 truthy라서res.chainBlock없음만 무시하고 아래로 흘러 "발송됨" 성공 토스트가 떠버렸던 게 근본 원인. 실패 시resetCompose()/render()를 호출하지 않아 컴포즈 모달과 입력값이 그대로 남아 재시도 가능. - 발송 성공 후 재조회: 신규
loadBoxFromServer(box)함수 —api.list(box)로 서버 재조회해 그 폴더분만state.mails에서 교체(다른 폴더는 보존). 성공 시'sent'와(다르면)state.box(현재 보고 있는 함)를 둘 다 재조회하도록doSend에 연결.boot()의 inbox 최초 로드도 같은 함수로 통합(중복 로직 제거). 발신함(sent/drafts/scheduled)은 목록 API가toName/toId를 안 줘서 "상대"를to문자열로 임시 키잉 — inbox/waiting과 달리 "발신자"가 아니라 "수신자"를 상대로 보여주도록 분기. - 미연결 크라우니 안내: 확인 결과 이미 서버·데모 공통 — "상대 대기실로" 문구는 서버 응답이 아니라 클라이언트 측 수신자 분류(
state.coTo의kind==='wait'개수)로 계산되어 모드 무관하게 항상 붙는다(코드 변경 없음, 실측 확인만). 데모 배지 추가: 데스크톱 상단바(.데모뱃지, 동기화 표시 옆)와 바타입(2b) 좌측 존(동기화 행 아래)에데모 모드일 때만"데모 — 실발송 없음" 표기, 토큰(var(--주황글자/배경/테두리), 2b는 같은 파일 내 기존 대기실 칩과 동일한 다크존 팔레트 재사용) 준수. 태블릿·모바일 상단바는 이미 여유가 없어@media(max-width:1280px)에서 함께 숨김(1차 수리 때 고친 상단바 낱자깨짐 회귀 방지).
mail.crowny.org/demo 라이브 탭에서 window.fetch를 임시로 몹(mock)해 state.mode='server'로 전환 후 doSend()를 직접 호출 — ① 400 오류 응답 시 토스트 "발송 실패 — 맘 보유량 부족" + state.coOpen 유지 + 입력값 보존 실측, ② 성공 응답 시 토스트에 "실패" 없음 + api.list가 box=sent·box=inbox 두 번 호출됨 + state.mails에 새 sent 레코드가 정확한 수신자 이름으로 반영 + 컴포즈 닫힘 실측. 데모 배지는 1490×900 iframe(진짜 데스크톱 폭)에서 getBoundingClientRect로 비0 크기·올바른 문구 확인(사각 스크린샷은 샌드박스 850px 뷰포트 한계로 직접 캡처 불가해 DOM 실측으로 대체).정적 게이트 재실행: 문법 PASS · hex 출처 불명 0건 · innerHTML은 여전히 클리어만 · "맘 N개" 0건.
?view= 강제 미리보기 오버라이드 (2026-07-24)
배경: 바타입(2b) 트리거가 (max-height:420px) and (orientation:landscape)라 일반 데스크톱 창을 아무리 조절해도 도달하기 어려움 — 사용자가 직접 미리보려면 강제 진입 경로가 필요. pages/앱v3.html만 수정.
- 부팅 시(스크립트 최상단,
IS_DEMO선언 직후)?view=2a|2b|mobile쿼리를 즉시 파싱해<html>에view-2a/view-2b/view-mobile클래스를 강제로 건다. 값 없음/모르는 값이면 클래스를 전혀 안 붙여 기존 동작과 100% 동일. - CSS는 기존
@media블록을 전혀 건드리지 않고, 그 아래에 동일한 선언을:root.view-2a #id형태로만 다시 걸었다 —#id단독(특이도 0,1,0)보다:root.view-2a #id(0,2,0)가 항상 높아 실제 뷰포트 폭·조건과 무관하게 이긴다(!important대부분 불필요, 모바일의body.모바일상세같은 기존!important규칙과 충돌할 수 있는 자리에만 보수적으로!important병기). 새 hex·새 토큰 없음 — 전부var(--...)또는 기존에 이미 파일에 있던 값 재사용. - 태블릿 경계는 메인 세션이 이미 1179px로 낮춘 상태(
≥1180=데스크톱)를 그대로 유지, 코드 곳곳의 stale 주석("768~1280"/">1280")만 실제 값(1179/≥1180)에 맞게 정정.
mail.crowny.org/demo에 iframe 주입, 실제 서버 무접촉):
?view=2b, 1244×700(일반 데스크톱 비율, 실제로는 바타입 미디어쿼리에 전혀 안 걸림 —matchMedia('(max-height:420px) and (orientation:landscape)').matches === false확인) →html.view-2b,#appRoot{display:none},#barType{display:flex}실측 + 스크린샷으로 좌측 다크존·필터칩·행 목록 정상 렌더 확인(데모 배지도 함께 보임).?view=2a, 1244×700 → 레일(64px, flex) 표시·사이드바 숨김·목록 320px 실측 + 스크린샷 확인.?view=mobile, 1244×700 → 탭바 표시·사이드바/레일 숨김·검색창 숨김·목록 전체폭(1238px) 실측 + 스크린샷 확인.- 파라미터 없음(회귀 확인), 1490×900 →
html.className===''(클래스 미부착), 사이드바 246 표시·레일 숨김·관계패널 표시·목록 372px·탭바 숨김 — 기존 동작과 완전 동일 실측.
사이드바 저장소 카드 실데이터 배선 (2026-07-24)
pages/앱v3.html만 수정. GET /api/storage → {"usedMB":N,"availMB":N,"quotaGB":15}(다른 에이전트 병렬 작업 중인 계약) 소비부를 미리 배선.
state.storage = {usedGB, quotaGB, pct}신규 상태(기본값{1.2, 15, 8}= 기존 데모 고정 표기·게이지와 완전히 동일해 데모 모드는 손대지 않아도 회귀 0).api.storage()추가,boot()서버 모드 분기 마지막에 호출 — 성공(usedMB가 숫자) 시에만state.storage를 재계산해 교체, 404·네트워크 예외·형식이상 등 실패 전부는apiFetch가 이미{__error:true,...}로 통일 신호하므로 조건 미충족 → 아무것도 안 하고 기존 값 유지(데모 고정값 폴백 요구사항 그대로).- 표기 2곳 모두 하드코딩 제거하고
state.storage로 배선: 사이드바 "메일 저장소" 카드(지시 대상) + 설정 화면의 "크라우니 클라우드 · X.X / 15GB 사용" 줄(같은 데이터라 방치하면 사이드바만 바뀌고 설정 화면은 낡은 값으로 남는 불일치가 생겨 함께 수정 — 범위 외 작업 아님, 파일은 동일하게 앱v3.html 하나). - 게이지 바 width는
usedMB/(quotaGB*1024)*100을 0~100으로 클램프 후 소수 1자리 반올림해 CSS width로 그대로 사용.
mail.crowny.org는 게이트웨이(crowny-gateway, upstream http://1.234.20.240:9610)를 경유하는데, 이번 편집 직후 그 경로가 옛 스냅샷을 서빙해(같은 세션 앞선 5차례 수정 때는 매번 즉시 반영됐던 것과 달리) 자체검증이 한 번 어긋났다. curl로 mail.crowny.org와 localhost:9610(원본 서버, 같은 프로세스)을 직접 비교해 원본은 이미 최신 파일을 정상 서빙 중임을 확인 — 게이트웨이/CDN 경로에 캐시 레이어가 있는 것으로 보인다. 이후 검증은 http://localhost:9610/demo(원본)로 진행해 정상 확인했다. 다음 세션 참고: mail.crowny.org에서 즉시 반영이 하나라도 이상하면 먼저 localhost:9610으로 원본을 직접 대조할 것 — 서버 파일 편집 문제가 아니라 게이트웨이 캐시일 가능성이 있다.검증(원본 localhost:9610/demo, window.fetch 목으로 실제 서버 무접촉):
- 데모 모드: 리로드 후
state.storage={1.2,15,8}그대로, 카드 텍스트 "1.2 / 15GB", 바 8% — 불변 확인. - 서버 모드 성공:
/api/storage가{usedMB:5324,quotaGB:15}반환 →state.storage={5.199...,15,34.7}, 카드 "5.2 / 15GB", 바 34.7% 실측. 설정 화면 "크라우니 클라우드 · 5.2 / 15GB 사용"도 동기화 확인. - 서버 모드 404: 임의값(9.9/66%)을 미리 넣고
/api/storage404 응답 후boot()재호출 → 값 불변(9.9/66% 유지) 확인 — 폴백 정상. - 서버 모드 네트워크 예외: 동일 방식으로
fetch가 throw할 때도 값 불변(7.7/51% 유지) 확인.
로그인v3.html 재디자인 — 메인 앱 언어 이식 (2026-07-25)
pages/로그인v3.html(+동반 .한선 주석 갱신)만 수정. 사용자가 기존 "밋밋한 중앙 카드"를 지적 — 시안이 없는 화면이라 새로 발명하지 않고 앱v3.html(사이드바·설정 화면)의 기존 시각 언어를 그대로 이식.
- 레이아웃: 데스크톱 좌우 2단 — 좌측은 메인 앱 사이드바와 동일한 항상-다크 초콜릿(
#281B10) 브랜드 패널(45° 골드그라데 다이아몬드, "크라우니메일" 워드마크, "관계가 곧 받은편지함" + 부제(시안 hero 문구 재사용, 신규 카피 없음),chain:sealed/gate:handshake/nodes:5모노태그 3개 — 전부 앱에 이미 존재하는 실제 태그) + 우측 크림(#F5EFE1) 바탕에 폼 카드(#FDFAF2, 둥글기 11px). 골드 솔리드는 로그인 버튼 1개만(화면당 1~2 규칙 준수),/demo는 고스트 링크. - 모바일(≤767): 세로 스택, 브랜드 패널 컴팩트화(부제·모노태그 3개 숨김, 워드마크+태그라인만 유지) — 지시대로.
- 다크모드:
[data-theme="다크"]스왑 +prefers-color-scheme폴백(우측 폼 패널만 스왑, 좌측 브랜드 패널은 메인 앱 사이드바처럼 테마 무관 항상 고정 다크 — 앱과 동일한 규칙). - 기능 회귀 금지 확인:
POST /api/auth/login {id,password}→ 성공 시localStorage.setItem('mail_session', token)→location.href='/'그대로. 실패 UX만 인라인 빨간 배너 → 메인 앱과 동일한 하단 중앙 토스트(초콜릿 배경·2.6s)로 교체, 429 전용 문구("요청이 많습니다 — 잠시 후 다시 시도해 주세요") 추가. - 세리프·이모지 없음(Pretendard만), 모든 hex는 토큰 CSS/
앱v3.html에 이미 존재하는 값만 재사용, 터치 타깃(입력창·버튼·고스트링크) 전부min-height:44px.
.브랜드태그행{display:none} 등)을 처음엔 관련 기본 선언보다 앞에 배치해, 같은 특이도에서 소스 순서상 뒤에 오는 기본 display:flex 선언이 이겨 모바일에서도 태그가 계속 보이는 버그가 있었다. 실브라우저 렌더로 실제로 잡아냄 — 오버라이드 미디어쿼리 블록을 기본 선언들 뒤로 재배치해 수정.검증(http://localhost:9610/pages/로그인v3.html 원본 서버에 iframe 주입 — mail.crowny.org 게이트웨이 캐시 이슈 회피, 위 항목 참고):
- 1244×735(데스크톱): 좌우 2단 스플릿 렌더 확인, 라이트 강제 시 초콜릿/크림 대비 스크린샷 확보, 시스템
prefers-color-scheme:dark(이 샌드박스 기본값) 상태에서도 우측 패널만 정상 다크 스왑됨을getComputedStyle로 실측. - 390×844(모바일): 세로 스택 + 브랜드 컴팩트화(태그·부제
display:none실측) 확인, 버그 수정 전/후 스크린샷 대조. - 로그인 로직 5종 실측: 빈 입력 토스트, 429 전용 토스트, 일반 실패 토스트, 성공 시
localStorage.mail_session토큰 저장 — 전부doLogin()직접 호출 +fetch목으로 확인.
앱v3.html 기존 사용값 대비) · 진짜 이모지 0 · Noto Serif 참조 0 · [data-theme="다크"] 스왑 존재 · 터치 44px 선언 4곳 · 골드 솔리드 버튼 정확히 1개 · API 계약(/api/auth/login, mail_session, location.href='/') 문자열 그대로 존재.잔여 이슈 / 해석 필요했던 지점
- 데모 대기실 인원을 프로토타입 3명(yjin/taeho/sumin-스팸)에서 2명(yjin/taeho)으로 축소 — 데모 규약 명시값("대기실 2") 우선 적용, 스팸 차단 시나리오는 데모 기본값에서 제외.
- inbox 8통을 맞추기 위해 프로토타입에 없는 연락처
이수현(@suhyun, T1)+ 메일 1건을 추가(미읽음 3 조건도 이걸로 충족). - M1 백엔드가 미가동 상태라 서버 모드 API 연동은 계약 문서 기준 구현만 완료, 실제 HTTP 왕복 검증 없음 — M3 연결·QA 단계에서 실배선 검증 필요.
- 태블릿 레일(768~1280px 아이콘 레일)은 시안에 없어 미구현 — 해당 구간은 관계패널만 숨기고 데스크톱 레이아웃 유지.
- 실브라우저 렌더 검증(스크린샷 대조)은 메인 세션 담당 범위로 남김.