Commit Graph

20 Commits

Author SHA1 Message Date
ttipo 907287021f feat(frontend): 주소록 가져오기 · 내보내기 · 여러 서명과 서명 바꾸기 · 부재중 서식 · 휴대폰 바꾸기
메일 16단계(개인 설정)의 화면이다(보드 Contacts16 · Signature16 · Settings16).
- 주소록: 브라우저가 vCard · CSV · 엑셀(.xlsx)을 읽어 미리 보기(파일은 서버로 가지 않는다 — 새 의존성 없이
  DecompressionStream · DOMParser), 네이버 · Google · Outlook 머리를 알아보고 칸 없는 정보는 메모 끝에,
  묶음(100명 · 값 8,000 · 700KB)으로 넣고 ✕까지 남는 결과 알림의 [되돌리기], 내보내기 창.
- 서명: 왼쪽 목록 · 오른쪽 편집 · 메일에 넣을 서명 둘, 쓰기 화면 [서명 ▾]로 서명 자리만 바꾸기
  (앱과 같은 경계 — 앞 빈 줄을 지켜 임시 저장 · 이어 쓰기에서도 찾는다).
- 부재중: 내용 편집기(서식이 있을 때만 서식 본문) · 회사 밖에서 온 메일 응답 셋 · 미리 보기 전환.
- 2단계 인증: [휴대폰 바꾸기] 두 걸음 창(비밀은 창 안에만).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:44:42 +09:00
ttipo d02a983d57 feat(frontend): 읽기 화면 이동 · 끌어다 놓기 · 메일함 옮기기 · 차단할 때 받은편지함 정리 · 예약 메일 지금 보내기
- 읽기 화면 [이동 ▾](옮긴 뒤 다음 메일 · [되돌리기]), 도구 줄은 읽기 칸 폭(컨테이너 쿼리)으로 아이콘만
- [이동 ▾] 메뉴 들여쓰기 · 그 메일이 든 메일함에 하위가 있으면 흐린 줄 · 넘치면 좌우 바꾸기(EditorPopup 메뉴 모두),
  같은 이름이 다른 곳에도 있을 때만 알림에 "상위 › 이름"
- 목록 → 메일함 패널 끌어다 놓기(고른 것 · 대화 줄, 휴지통은 삭제), 메일 화면에 놓은 파일은 무시
- 스팸메일함으로 옮기는 세 길 같게 — 메뉴 · 끌기도 스팸 표시를 붙이고, 꺼내면 스팸 아님
- 메일함 ⋯ 옮기기 창(같은 이름이 있는 곳은 처음부터 막음) · 하위까지 지우기 확인 창
- 차단 창에 받은편지함 정리 칸(같은 탭의 필터 적용 · 스팸메일함 비우기와 함께 돌지 않음), 진행 알림 하나
- 예약 안내 줄 [지금 보내기] — 결과를 모르는 실패는 그 메일을 다시 받아 가르고 확인하지 못한 일은 단정하지 않는다
- 상단 바 좁은 폭에서 [로그아웃]이 꺾이지 않게, 같은 대화 [모두 펼치기] · 펼친 줄 머리 [답장]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 18:37:55 +09:00
ttipo 433ef12cc3 feat(frontend): 설정 › 휴대폰 앱과 휴대폰 받기 화면 /app 을 더한다
확정 보드 SettingsMobileApp 대로 PC 에서는 QR 로 휴대폰에 넘기고, 셸 밖 /app(로그인 필요)에서 휴대폰으로 받는다.
내려받기 전에 세션을 확인하고, 올린 판이 없으면(404 RES_001) 알림 없이 안내와 문의 줄을 보인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:04 +09:00
ttipo 4ca2d41bad docs: 앱 검색 1차의 구현하며 정한 것과 짝을 적는다
앱 디자인 README 「검색」 절, flutter 스킬 표 · 함정 두 줄 · CI 경로, 계약 2절
"검색 — 14단계"의 앱 클라이언트 줄과 목록 항목의 searchHighlight 칸이 늘 있다는
줄, 두 표본 표 머리말(앱 시험이 백엔드 표를 읽는다)을 적는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:19:45 +09:00
ttipo ab16379836 feat(frontend): 검색에 조건 · 강조 · 준비 중 안내를 더한다 (14단계 화면 1차)
- [조건] 패널(보낸 사람 · 받는 사람 · 제목 · 내용 · 기간 · 첨부)과 결과 머리의 조건 줄 —
  조건도 화면 주소에 싣는다(since · until 은 브라우저 시간대 날짜 · 양 끝 포함)
- 결과 줄 세 줄(분할 보기)과 맞은 말 노랑 강조 — HighlightedText 가 글 조각으로 그린다(v-html 없음),
  읽기 화면은 서버가 넣은 <mark> · 제목 · 첨부 이름 강조. 강조가 든 응답은 보이기만 한다
  (답장 · 전달 · 인쇄 · 예약은 강조 없이 받는다)
- "검색 준비 중" 줄 · 한 글자 안내 · 빈 쪽이면 이어 찾기(한 번에 5번까지) + [더 찾기],
  total 이 null 이면 통 수를 숨긴다
- search-match.ts — backend search-query · search-text · subject-base 와 짝(같은 표본 표)

리뷰 반영: 이어 받는 동안 [다음] · [메일 더 보기] 기다림, 새로 고치는 동안 이어 받지 않음,
손으로 고친 주소도 패널과 같은 검사(8낱말 · 날짜 범위 · 거꾸로 된 기간), 패널 컴포넌트 나눔,
시험 정리(unmount · 보이지 않는 글자 이스케이프).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 07:53:56 +09:00
ttipo 349c312989 test(frontend): 대용량 파일을 다 올린 뒤 보낸다 — 조각 SHA-256 이 스레드 풀이라 부하에서 늦다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Failing after 10m31s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m18s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 14m21s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m30s
쓰기 화면의 두 테스트는 대용량 파일을 고른 뒤 양보(flushPromises) 두 번 만에 다 올렸다고
보았다. 조각의 SHA-256 은 jsdom 이 아니라 Node 의 Web Crypto(libuv 스레드 풀)라, CI 부하에서
그 안에 끝나지 않았다. 다 올리기 전에 누른 [보내기]는 막혀 보내기 요청이 끝내 나가지 않아
payload 가 undefined 였다(2026-11-03 CI — ComposePage.spec.ts 첨부 합계 테스트).

- 두 테스트(첨부 합계 · 링크로 보내기)는 대용량 줄이 ready 가 될 때까지 vi.waitFor 로 본다.
  첨부 합계 테스트는 보내기 전에 막는 까닭이 없음도 단언한다.
- test-utils/timeouts.ts 에 WAIT_FOR_TIMEOUT_MS(15초 — 테스트 한도 20초보다 짧게)를 둔다.
- digest 를 늦춘 실험으로 고치기 전에는 깨지고 고친 뒤에는 통과함을 확인했다(실험은 되돌림).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 23:26:28 +09:00
ttipo 1881ec7a42 feat(frontend): 13단계 전환 준비 화면 — 새 비밀번호 · 2단계 켜기 단계 · 유예 줄 · 메일 프로그램 연결 · 30일 안내 · 문의 줄
- 제한된 세션: restriction 이 있으면 보호 화면 대신 로그인 틀의 그 단계(새 비밀번호 → 2단계 켜기)로, AUTH_010 은 알림 없이 me 를 다시 받아 옮긴다.
  제한된 동안은 나가기 확인을 묻지 않고(확인에서 이동을 취소하면 메일 화면에 갇혔다 — 리뷰) 실시간 알림을 열지 않는다.
- 로그인 때 넣은 비밀번호는 반응형이 아닌 클로저에 10분 · 세션이 끝나면 지운다. 2단계를 켠 직원의 새 비밀번호 단계는 다음 코드를 받는다.
- 유예 노란 줄(시작일 전날까지), 설정 › 2단계 인증의 필수 안내, 설정 › 메일 프로그램 연결(펼침 줄 · 새 Outlook 은 권하지 않음), 휴지통 · 스팸메일함 30일 줄, 로그인 · 코드 단계의 문의 줄.
검증: lint · type-check · 단위 2,354 · 실사용 시험 89/89.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:37:14 +09:00
ttipo c31442e639 feat(frontend): 12단계 계정 보안 화면 — 로그인 코드 단계 · 2단계 인증 · 앱 비밀번호 · 로그인 기록
로그인은 비밀번호가 맞고 2단계를 켠 직원만 코드 단계로 간다. 설정 › 계정에 2단계 인증 · 앱 비밀번호 · 로그인 기록을 더하고,
비밀번호 화면에 2단계를 켠 직원의 코드 칸을 둔다.
비밀(코드 단계의 비밀번호 · 2단계 키 · 앱 비밀번호 값)은 화면 메모리에만 두고, 세션이 바뀌면 그 창을 모두 닫는다 —
12단계부터 다른 기기의 로그아웃이 이 세션을 바로 끊는다. 비밀이 드는 요청은 Pinia 액션 밖에서 보낸다(devtools 기록).
QR 은 uqr 0.1.3(MIT · 의존성 없음 — 2단계 인증 화면 청크에만)으로 SVG 를 그린다(v-html 없음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:25:53 +09:00
ttipo 3461c361f8 feat(frontend): 대용량 첨부 — 쓰기 · 읽기 · 설정 · 공개 내려받기 화면
- 쓰기: 20MB 넘는 파일 · 합계 25MB 를 넘기는 파일 · 메일에 못 담는 형식은 저절로 링크로(묻는 창 없음). 고르자마자 서버에 만들고
  16MiB 조각(Content-Digest)을 파일 3개까지 함께, 끊기면 2 · 5 · 15 · 30초로 다시 · 1분 넘으면 멈춤 + [다시 시도],
  새로고침 뒤 같은 파일을 다시 고르면 잇는다. 다 올릴 때까지 [보내기]는 aria-disabled + 까닭, 위험 판정은 빨간 줄 + 실시간 알림.
  대용량 파일이 있으면 첫 원문 인용 앞을 data-ara-quote-start · bodyQuoteStart 로 알린다(서버가 그 자리에 덩이를 넣는다).
- 읽기: 첨부 칸의 대용량 항목(서버의 largeFiles), 본문 덩이 접기, 보낸 사람의 받은 수 · 링크 끊기(끊은 날 — revokedAt).
- 설정 › 메일 › 대용량 첨부: 보관 사용량 · 보내지 않은 파일 · 보낸 링크(끊기).
- 공개 화면 /s/:token: 세션 복원 없이(meta.public) · credentials omit · no-referrer, 라우트는 대소문자를 가린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:37:55 +09:00
ttipo b99ccfbf6c feat(frontend): 보내기 취소 · 예약 발송 · 외부 이미지 · 인쇄 · 보낸 사람 차단 · 보내기 · 읽기 설정 화면
쓰기 화면의 [보내기 ▾](예약 발송 창), 보내기 취소 알림(남은 시간 막대), 메일함 패널 「예약됨」 · 예약 목록, 읽기 화면의 안내 줄([이미지 보기] · [고치기] · [예약 취소] · [차단 풀기]),
인쇄 화면(새 탭 — A4 폭으로 다시 재서 빈 쪽 없이), 보낸 사람 차단 창, 설정 › 보내기 · 읽기 · 차단한 주소. 디자인 README "9단계", 계약 2절.

리뷰로 고친 것:
- 붙잡힌 메일을 여러 통 처리로 지워도 예약이 그대로 나갔다 — 고르지 못하게 했다(고르기 칸 aria-disabled + 까닭, 하나 · 범위 · 모두 고르기가 건너뛴다).
  읽기 화면 [삭제]는 확인(처음 초점 「취소」) 뒤 발송을 취소하고 휴지통으로 — 서버도 MAIL_015 로 막는다.
- 엔진이 발송을 풀어도 상태가 바뀌지 않아 "보내는 중" · 「예약됨」이 남았다 — 보낼 시각 + 2초에 목록 · 열린 메일 · 예약 수를 다시 맞춘다(MAIL_014 때도).
- 설정을 연달아 고르면 늦게 온 앞 응답이 화면과 서버를 갈랐다 — 칸마다 저장은 한 번에 하나, 끝나면 마지막으로 고른 값으로 맞춘다.
- 응답을 기다리는 사이 다른 목록으로 간 사용자를 옛 목록의 다음 메일로 끌고 갔다 — 같은 목록 · 같은 메일을 보고 있을 때만 옮긴다(3 · 4단계 단추도).
- 이미지를 보일 때 본문 틀의 CSP 를 우리 출처의 중계 경로로만 연다 — 'self' 면 정화를 빠져나간 CSS url() 이 다른 API 를 부를 수 있었다.
- 알림 단추는 한 번만 돈다, 보내기 취소 알림의 마감은 설정한 초로 묶는다, [보내기 ▾] 메뉴는 화면 안에서 연다(쓰기 화면이 움직이지 않게),
  차단 목록 · 설정 저장의 상태가 멈추던 것, 초점을 잃던 것과 알리지 않던 라이브 영역.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:08:06 +09:00
ttipo e2c41d38bb feat(frontend): 주소록 화면 · 메일 연동
레일의 주소록 모듈 — 내 연락처 · 그룹 · 사내 목록(직원 · 공용 주소), 초성 찾기, 연락처 추가 · 고치기 창, 그룹 · 구성원 넣기 —
과 메일 연동(받는 사람 추천의 주소록 · 그룹, 연락처 · 그룹 · 사내의 [메일 쓰기], 읽기 화면 ⋯ 「보낸 사람을 주소록에 추가」)을 더한다.
디자인 README "8단계", 계약 3절.

리뷰로 고친 것:
- 쓰기 화면을 채우는 라우터 상태에 연 직원을 싣는다 — 같은 탭에서 다른 직원이 로그인한 뒤 뒤로 가면 앞 직원의 받는 사람이 채워졌다.
- [고치기]는 창을 열 때 새로 받고 바꾼 칸만 보낸다 — 받아 둔 값으로 다른 탭 · 휴대폰의 변경을 덮었다. 검사도 바꾼 칸만.
- [되돌리기]는 서버의 되돌리기 표로 — 다시 만들기로는 다른 프로그램의 칸 · uid 를 잃었다.
- 읽기 화면에서 연 연락처 추가 창을 메일 레이아웃으로 옮겼다 — 메일을 바꾸면 쓴 것이 확인 없이 사라졌다.
- 그룹을 이어 고르면 앞 그룹이 빠지던 것, 저장 중 창을 닫으면 거절이 묻히던 것, 구성원 넣기의 첫 쪽이 모두 구성원이면 막히던 것(excludeGroupId).
- 받는 사람 추천은 초성 · 조합 중 글자도 서버에 묻는다(서버가 같은 맞추기 — 같은 표본 표를 두 쪽 테스트에 둔다).
- 바꿀 때마다 불러온 쪽을 모두 다시 받지 않고 응답을 서버 차례로 그 자리에 넣는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:13:20 +09:00
ttipo 894550732e feat(frontend): 설정 › 메일 › 필터 · 읽기 화면에서 필터 만들기
확정 보드(SettingsFilter · MailFilterStates)대로 필터 목록 · 켜기 · 순서 · 지우기(되돌리기) ·
고치기 창을 만들고, 읽기 화면 ⋯ 에 「이런 메일로 필터 만들기」를 둔다.

- 저장은 한 번에 하나다. 되돌리기는 누른 차례대로, 앞 저장이 끝난 뒤에 보낸다 —
  연달아 누르면 하나가 사라지던 경합을 리뷰에서 찾았다
- 다른 곳에서 먼저 바꿨으면 창 안에서도 다시 불러올 수 있고, 쓴 내용은 지킨다
- 메뉴의 꺼진 항목은 aria-disabled 로 둔다(편집기 메뉴 공용) — 키보드로 가서 까닭을 들을 수 있다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:59:53 +09:00
ttipo 50165bd3b7 feat(frontend): 실시간 새 메일 · 바탕화면 알림 · 설정 › 알림
- 로그인한 동안 앱 전체에서 실시간 연결 — 브라우저마다 하나(Web Locks 잠금 + BroadcastChannel 로 다른 탭에 나눔), fetch 로 읽기,
  401 은 세션 갱신 한 번, 자리 잡은 뒤에만 다시 잇기 간격을 처음부터(열리자마자 끝나는 연결에서 갱신 폭주 막기), 탭을 닫으면 이어받기
- 메일 화면: 연결이 살아 있는 동안 1분 확인을 멈추고, 바뀌면 1초 모아 맞추기 · 숨은 탭은 탭 제목 (N) 만
- 바탕화면 알림: 웹메일을 보고 있지 않을 때만(보이고 초점이 있는 탭 없음), 한 통 · 여러 통 · 제목 숨김 문구, 누르면 그 메일로, 돌아오면 닫기
- 설정 › 메일 › 알림(허락 상태 · 제목 숨김 · 시험 알림 · 연결 상태)과 목록 위 한 번 권하기 줄, 알림 아이콘

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:32:09 +09:00
ttipo af1e581be1 feat(frontend): 설정(비밀번호 · 부재중 응답) · 메일함 관리 · 스팸 신고 · 원문 내려받기 화면
- 설정 메뉴를 계정 · 메일 묶음으로, 비밀번호 바꾸기(칸 아래 오류 · 30초 한도)와 부재중 자동 응답(기간 · 미리 보기 · 나가기 확인),
  메일함 패널의 부재중 안내(1분마다 다시 계산)
- 메일함 패널의 내 메일함 묶음 · ⋯ 메뉴(보이는 영역 안에 열기) · 대화상자(만들기 · 이름 바꾸기 · 지우기), 목록 머리 비우기
  — 확인한 그 메일함만, 휴지통 비우기와 메일함 지우기는 함께 돌리지 않는다
- 오래 걸리는 처리의 진행 알림("…하는 중 · 600 / 1,234통" → 결과), 세션이 바뀌면 멈추고 늦게 온 응답은 쓰지 않는다
- 읽기 화면 · 여러 통 도구 줄의 스팸 신고 / 스팸 아님 + 되돌리기, ⋯ › 원문 내려받기 (.eml)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:47:12 +09:00
ttipo 192807f943 feat(frontend): 새 메일 확인·메일 검색·여러 통 선택과 처리 화면
- 화면이 보이는 동안 1분마다 바뀐 것을 묻고 목록·메일함 숫자·탭 제목 "(N)" 을 맞춘다 — 늦게 온 응답은 목록 변경 세대로 버린다
- 상단 검색 칸 → /mail/search (범위 전환, 결과 없음 안내에 엔진 한계)
- 표·분할 보기에서 여러 통 고르기와 도구 줄, 이동 메뉴, 휴지통 이동 + 되돌리기, 휴지통에서만 확인 뒤 영구 삭제
- 읽기 화면의 삭제도 DELETE 대신 휴지통 이동 · 확인 뒤 영구 삭제 — 다른 창이 휴지통에 넣은 메일을 확인 없이 지우지 않게
- 되돌리기 알림은 띄운 세션에서만 움직인다 — 메일 id 는 계정마다 겹칠 수 있다
- 메일 스토어를 기능별 setup 모듈로 나눴다 (공개 API 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:56:09 +09:00
ttipo 5710151c0b feat(frontend): 임시 저장·서명 설정·받는 사람 자동 완성 화면
- 쓰기 화면 자동 저장(3초 멈춤 · 30초 · 10초 간격 · 한 번에 하나)과 상태 글, 저장할 때마다 주소를 ?draft= 로 맞춰
  새로고침해도 이어 쓴다. MAIL_008 · PUT 404 는 막지 않고 새 저장본으로 남긴다
- 찾지 못한 첨부가 남았거나 본문이 한도를 넘는 동안은 저장하지 않고, 실을 수 없는 첨부가 든 남의 저장본은 원본을 지우지 않는다
- 휴지통은 휴지통으로 옮기기(PATCH)와 되돌리기 — 이미 휴지통에 있는 메일을 DELETE 로 영구 삭제하지 않게
- 설정 › 메일 › 서명: 서식 · 그림(크기 줄) · 미리 보기 · 넣을 메일, 저장하지 않고 떠나면 묻는다
- 받는 사람 자동 완성: 사내 · 최근 묶음, 초성 검색(조합 중 글자 포함), 완전한 주소는 미리 고르지 않는다
- 본문에 그림으로 보인 첨부(isInline)는 읽기 화면의 첨부 목록에서 빼고 전달에는 붙인다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:23:22 +09:00
ttipo bbd88e707a feat(frontend): 쓰기 화면 나가기 확인·읽기 화면 본문 안내·내 메일 답장 받는 사람
- 쓰기 화면을 연 뒤 바꾼 것이 있으면 떠나기 전에 묻는다 — 앱 안 이동은 확인 대화상자, 탭 닫기·새로고침은 브라우저 확인창.
  보낸 뒤·보내는 중·세션이 끝났을 때는 묻지 않고, 보내는 중에 떠나면 결과는 알림으로만 알린다(떠난 양식은 건드리지 않는다).
  대화상자는 공용 ModalDialog 로 모았다 — 세션 만료 대화상자도 같은 것을 쓴다
- 본문 길이 초과는 MAIL_007 문구로 본문 아래에 알린다 (서버가 같은 코드를 돌려줘도 같다, 발송 키는 그대로)
- 읽기 화면에 본문 안내(서식 없이 표시·표시할 수 없음·앞부분만 표시). 잘린 원문을 인용하면 생략 표시를 둔다.
  인용할 원문은 읽기 화면과 같은 규칙으로 고른다 — 공백뿐인 HTML·그림만 남는 HTML 은 글자 본문으로
- 붙여 넣은 글·원문 인용에서 8px 미만 글자 크기와 알파 0.3 미만 글자 색을 버린다 (서버와 같은 규칙, 같은 표본 표 105행)
- 내가 보낸 메일에 답장하면 받는 사람이 원래 받는 사람이다 (전에는 받는 사람 칸이 비었다)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:00:09 +09:00
ttipo 2701d04fc2 test: 부하에 흔들리던 시간 검사를 계산량·조건 대기로 고정
다른 작업과 함께 돌면 혼자 0.8초인 정화가 9초로 재지는 등, 벽시계 한도와 고정 대기를 쓰던 테스트가 가끔 실패했다.

- 백엔드 성능 단언은 벽시계 대신 이 프로세스의 CPU 시간으로 잰다 (backend/test/cpu-time.ts 의 startCpuTimer).
  부하 중에도 1.4배쯤만 늘고, 예전 제곱 시간 경로(15~172초)는 그대로 한도를 넘는다
- 임시 업로드 삭제 테스트는 고정 20ms 대신 파일이 지워질 때까지 기다린다 (최대 2초, 지워지지 않으면 실패)
- 프론트 테스트 시간 한도를 20초로 (jsdom·편집기 테스트가 부하에서 5~13초), 긴 초안을 맞추는 테스트는 30초
  (src/test-utils/timeouts.ts)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:00:08 +09:00
ttipo f77179a508 feat(frontend): 메일 쓰기 서식 편집기와 입력칸 포커스 통일
메일 쓰기 화면은 입력칸마다 포커스 모양이 달랐고, 본문은 서식 없는 글만 쓸 수 있었다.

- 글자 입력 행(받는 사람·참조·숨은참조·제목·본문)의 포커스를 아래 선 하나(.compose-field)로 통일했다
- 본문을 TipTap 서식 편집기로 바꿨다 — 글꼴·크기·굵게·색·목록·링크·정렬·들여쓰기·인용·표·가로줄, 단축키,
  좁은 폭에서 묶음째 접는 툴바. 편집기는 쓰기 화면에서 따로 불러온다(지연 로딩)
- 보낼 때는 기본 글꼴(맑은 고딕) div 와 인라인 style 의 HTML 에 텍스트 대체 본문을 함께 보낸다 (여러 칸 공백은 nbsp, 탭은 8칸 자리까지)
- 붙여 넣은 글·원문 인용은 서버 정화와 같은 값 형식만 읽는다 — 편집기에서 숨은 글이 받는 사람에게 보이지 않게
- 답장·전달 인용은 "원본 메일" 머리와 함께 서식째 옮긴다. 초안은 내 글 몫 5%를 남기고, 보내기 직전에 다시 재서 인용을 줄인다
- 본문에 붙여 넣거나 끌어다 놓은 이미지·파일은 첨부로 올린다 — 화면 어디에 놓아도 (브라우저가 파일을 열어 쓰던 메일을 잃지 않게)

의존성: @tiptap/* 3.31.4 (MIT 무료 패키지만, 모두 같은 판으로 고정)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:49:40 +09:00
ttipo 468a4377ee feat(frontend): 전역 셸·로그인·세션 처리와 웹메일 화면
확정된 디자인(docs/design/webmail)을 기준으로 만든 웹메일 화면이다.

- 앞으로 붙일 사내 모듈(캘린더·파일·ERP 등)을 위한 전역 앱 레일과 셸,
  Pretendard 자체 호스팅, 바다 청록 브랜드 토큰과 콘텐츠 열 토큰
- 로그인과 세션 처리: 401 이면 한 번 갱신한 뒤 다시 보내고, 실패하면 세션 만료 대화상자를 띄운다.
  로그아웃은 다른 탭에도 알리고, 로그아웃 뒤에 늦게 온 갱신 결과는 버린다
- 메일함(분할·목록 보기), 읽기(외부 HTML 은 sandbox iframe 으로만 그린다),
  쓰기(발송 키 규칙, 첨부 형식·크기·합계·개수 사전 검사, 답장·전달 초안 길이 맞춤)
- 오류는 코드로만 분기하고, 문구는 생성된 에러 사전에서 가져온다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:31:59 +09:00