← 목록
기타 2026-07-24 24KB 읽기 22분

크라우니메일 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 → localStorage mail_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).
중요 정정: 이전 수리 때 "관계패널이 1490px에서도 안 보인다"를 고치며 숨김 기준을 767px로 낮췄었는데, 이번 2a 시안을 보니 768~1280(태블릿)에서는 관계패널이 원래 없는 게 맞는 디자인이었다(2a 목업이 명확히 2단만 그림). 그래서 기준을 다시 1280으로 되돌렸다 — 원래 root cause였던 #appRoot{overflow:hidden} 클리핑 버그는 그대로 고쳐진 채 유지, 거기에 태블릿 전용 레이아웃(2a)이 정식으로 채워지면서 1280 이하 구간이 "고장난 데스크톱"이 아니라 "의도된 태블릿 화면"이 되었다.

해석사항(명세 침묵 부분, 지시대로 명기):

  1. 2b 트리거 기준 — README·시안 모두 CSS 트리거 값을 명시하지 않아 지시받은 폴백 (max-height:420px) and (orientation:landscape) 그대로 적용.
  2. 2a 아이콘 레일 8개 아이콘은 라벨이 없어 정확한 매핑이 불명 — 쓰기/구분선/받은편지함(활성)/대기실(배지)/보낸편지함/임시보관(다이아몬드 아웃라인)/연동·명함집(그라데이션 다이아몬드)/spacer/설정(원형) 순으로 데스크톱 사이드바 동작을 재사용해 매핑.
  3. 2b "작성·답장은 폰/데스크톱으로 이어서"는 상세 읽기 패널을 이 좁은 화면에 넣지 않는다는 뜻으로 해석 — 행 클릭은 읽음 처리 + "상세는 폰·데스크톱에서 이어보기" 토스트만 하고 리딩 뷰는 열지 않음. 다만 "메일 쓰기" 버튼은 (기기 구분이 없는 단일 SPA라) 실제 컴포즈 모달을 열도록 구현해 기능 손실을 막음.
  4. 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만 수정.

  1. apiFetch 오류 신호 보강: fetch 자체 예외(네트워크 단절)와 !r.ok(4xx/5xx) 모두 {__error:true, status, message} 형태로 반환하도록 변경(성공 시 반환 모양은 기존과 동일해 다른 호출부는 무영향). doSend 서버 모드에서 res.__error(또는 res 자체가 null — 401 리다이렉트 케이스)를 감지하면 "발송 실패 — <사유>" 토스트만 띄우고 return— 이전에는 오류 응답 바디({"error":"..."})가 그냥 truthy라서 res.chainBlock 없음만 무시하고 아래로 흘러 "발송됨" 성공 토스트가 떠버렸던 게 근본 원인. 실패 시 resetCompose()/render()를 호출하지 않아 컴포즈 모달과 입력값이 그대로 남아 재시도 가능.
  2. 발송 성공 후 재조회: 신규 loadBoxFromServer(box) 함수 — api.list(box)로 서버 재조회해 그 폴더분만 state.mails에서 교체(다른 폴더는 보존). 성공 시 'sent'와(다르면) state.box(현재 보고 있는 함)를 둘 다 재조회하도록 doSend에 연결. boot()의 inbox 최초 로드도 같은 함수로 통합(중복 로직 제거). 발신함(sent/drafts/scheduled)은 목록 API가 toName/toId를 안 줘서 "상대"를 to 문자열로 임시 키잉 — inbox/waiting과 달리 "발신자"가 아니라 "수신자"를 상대로 보여주도록 분기.
  3. 미연결 크라우니 안내: 확인 결과 이미 서버·데모 공통 — "상대 대기실로" 문구는 서버 응답이 아니라 클라이언트 측 수신자 분류(state.coTokind==='wait' 개수)로 계산되어 모드 무관하게 항상 붙는다(코드 변경 없음, 실측 확인만). 데모 배지 추가: 데스크톱 상단바(.데모뱃지, 동기화 표시 옆)와 바타입(2b) 좌측 존(동기화 행 아래)에 데모 모드일 때만 "데모 — 실발송 없음" 표기, 토큰(var(--주황글자/배경/테두리), 2b는 같은 파일 내 기존 대기실 칩과 동일한 다크존 팔레트 재사용) 준수. 태블릿·모바일 상단바는 이미 여유가 없어 @media(max-width:1280px)에서 함께 숨김(1차 수리 때 고친 상단바 낱자깨짐 회귀 방지).
검증: mail.crowny.org/demo 라이브 탭에서 window.fetch를 임시로 몹(mock)해 state.mode='server'로 전환 후 doSend()를 직접 호출 — ① 400 오류 응답 시 토스트 "발송 실패 — 맘 보유량 부족" + state.coOpen 유지 + 입력값 보존 실측, ② 성공 응답 시 토스트에 "실패" 없음 + api.listbox=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·탭바 숨김 — 기존 동작과 완전 동일 실측.
정적 게이트 재실행: 문법 PASS · hex 출처 불명 0건(신규 hex 0 — 전부 var() 재사용) · innerHTML 여전히 클리어만 · "맘 N개" 0건.

사이드바 저장소 카드 실데이터 배선 (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차례 수정 때는 매번 즉시 반영됐던 것과 달리) 자체검증이 한 번 어긋났다. curlmail.crowny.orglocalhost: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/storage 404 응답 후 boot() 재호출 → 값 불변(9.9/66% 유지) 확인 — 폴백 정상.
  • 서버 모드 네트워크 예외: 동일 방식으로 fetch가 throw할 때도 값 불변(7.7/51% 유지) 확인.
정적 게이트 재실행: 문법 PASS · hex 출처 불명 0건 · innerHTML 여전히 클리어만 · "맘 N개" 0건.

로그인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.
작업 중 발견·수정한 CSS 버그: 모바일 컴팩트화 규칙(.브랜드태그행{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 목으로 확인.
정적 게이트 재실행: 문법 PASS · hex 출처 불명 0건(토큰CSS+양쪽 핸드오프+앱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 아이콘 레일)은 시안에 없어 미구현 — 해당 구간은 관계패널만 숨기고 데스크톱 레이아웃 유지.
  • 실브라우저 렌더 검증(스크린샷 대조)은 메인 세션 담당 범위로 남김.