feat(flutter): 앱 첫 판 화면 — 로그인 · 메일 목록 · 읽기 · 쓰기 · 검색 · 새 메일 알림 #21

Merged
ttipo merged 34 commits from feat/app-push into main 2026-11-08 16:34:38 +09:00

34 Commits

Author SHA1 Message Date
ttipo 117ec6c767 chore: 앱 서버 브랜치(14단계 · 문구 다듬기 · main 을 합친 판)를 앱 첫 판 브랜치에 합친다
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m26s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m39s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m20s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m2s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m12s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 22m15s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m53s
CI / Frontend (lint · type-check · test · build) (push) Successful in 13m13s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m56s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m48s
이 PR 이 앱 서버 PR 뒤에 빨리 감기로 합쳐지고 CI 가 합친 모습 그대로를 시험하게 한다.
계약 1절의 앱 절은 앱 서버 쪽이 들고 있고 이 브랜치는 2절 줄만 더했다 — 충돌 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:39:17 +09:00
ttipo 1448835aff docs: 앱 README 의 실기기 확인에 바꿔 쓴 알림의 소리를 더한다
2026-11-08 갤럭시 S23 에서 사용자가 같은 tag 로 바꿔 쓴 알림도 다시 소리를 내는 것을 확인했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:08:36 +09:00
ttipo dacc100d3e docs: 앱 README 의 실기기 확인에 홈 화면 배지를 더한다
2026-11-08 갤럭시 S23 에서 사용자가 notification_count 의 홈 화면 배지를 확인했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:59:15 +09:00
ttipo 5670017d2a fix(flutter): 앱을 보는 중 받은 알림 뒤의 봤음을 1.5초 늦춰 N 이 한 통 많지 않게 한다
서버는 N 을 seen_at 의 초부터 같은 초를 넣어 센다(받은 시각이 초 단위). 앱을 보는 중 새 메일 알림을 받자마자
봤음을 보내면 seen_at 이 그 메일과 같은 초가 되어, 목록으로 보고 열지 않은 그 메일이 다음 알림에 다시 세어졌다
(2026-11-08 실기기 — 한 통이 더 왔는데 "새 메일 2통"). 그 봤음만 받은 때 + 1.5초와 30초 간격 가운데 늦은 때에 보낸다.
앱으로 돌아올 때 · 기다리는 동안 뒤로 갈 때는 그대로다. 고친 뒤 실기기에서 봤음이 2초 뒤에 갔고 다음 알림은 "새 메일 1통"이었다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:56:51 +09:00
ttipo 3a25cc52f7 fix(flutter): 새 메일 알림 켜기가 FID 등록 표시 없이 곧바로 실패하던 것을 고친다
firebase-messaging 26.0.1(firebase_core 5.1.0 의 BoM 35.1.0)의 register() · unregister() 는
매니페스트에 firebase_messaging_installation_id_enabled = true 가 없으면 곧바로 "API disabled" 로 거절한다
(2026-11-08 실기기에서 [알림 켜기]가 누르자마자 SYS_001 — SDK 바이트코드로 확인, 플러그인 문서에는 없다).
표시를 켜고, FID 길은 Google Play 서비스 26.12 이상에서만 탄다는 한계를 앱 README 에 적었다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:56:41 +09:00
ttipo 08b8144087 docs: 앱 새 메일 알림 · 업데이트 · 앱 아이콘을 앱 README · flutter README · 스킬 · 계약에 적는다
앱 디자인 README 에 "새 메일 알림 · 업데이트 · 앱 아이콘" 절(허락 묻기 · 설정 줄 상태 · 받기 · 누르기 · 로그아웃 차례 ·
"알림이 멈췄습니다"를 보내는 때)을, flutter README 에 Firebase 설정 파일 · 아이콘 만들기 · Play 데이터 보안 신고를,
flutter 스킬에 새 행을, 계약 2절 "새 메일 확인"에 앱이 알림을 받았을 때 한 번 묻는 줄을 더했다.
보드 App1Push 의 ⑧ 칸 설명은 "알림이 멈췄습니다"를 넓힌 결정대로 고쳤다(캔버스 v66).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:46:30 +09:00
ttipo e03eda1fd3 feat(flutter): 새 메일 알림(FCM) · 최소 판 확인 · 설정 탭
앱 첫 판의 마지막 기능이다. 알림 글은 "새 메일 N통"뿐이고 화면은 확정 보드 App1Push · App1Shell 대로다.

- firebase_core 5.1.0 · firebase_messaging 17.0.1 — FCM 토큰 API 가 deprecated 라 FID 로 등록한다.
  google-services.json 은 저장소에 두지 않고, 파일이 있을 때만 Gradle 플러그인을 건다(없으면 배포 판 빌드를 멈춘다).
- 받은편지함 위 권하기 줄과 설정 › 새 메일 알림 — 휴대폰의 허락 창은 [알림 켜기]를 누를 때만, 켬 · 끔은 이 휴대폰에 둔다.
- 등록 PUT · 끄기 DELETE · 봤음 unseen(앞에 있을 때만 · 30초 간격), 로그아웃과 세션 끝에는 FCM 등록을 푼다
  (같은 휴대폰에 다른 직원이 로그인해도 앞 직원 알림이 남지 않게 — 풀기가 실패해도 다음 로그인은 다시 등록한다).
- 앱이 앞에 있으면 알림 대신 목록을 바로 맞추고, 앞으로 오면 알림창의 알림을 지운다. 알림을 누르면 받은편지함으로 가고 쓰던 메일은 저장한다.
- 앱 설정 GET /api/app-config 를 인증 줄 밖에서 먼저 묻고, 최소 판보다 낮으면 어느 화면에서든 업데이트 화면으로 간다(묻지 못하면 막지 않는다).
- 설정 탭을 한 화면으로(내 정보 · 새 메일 알림 · 앱 정보 · 문의 · 로그아웃 확인 창), 알림 채널 "새 메일"을 Kotlin 에서 만든다.
- 시험 932 → 1,056.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:46:11 +09:00
ttipo bf725ae96d feat(flutter): 앱 아이콘과 알림 작은 아이콘을 ARA 마크로 바꾼다
확정 보드 App1Push(캔버스 v65)의 ⑪ "나. 남색 바탕"과 ⑨ 작은 아이콘이다.
앱 아이콘은 #0B3640 바탕에 흰 ARA 마크(적응형 · 안드로이드 13 테마 아이콘 monochrome · 7.x 용 PNG),
알림 작은 아이콘 ic_stat_ara 는 흰 실루엣이다. 그림은 브랜드 원본에서 저장소 밖 스크립트로 만들었다.
Flutter 기본 아이콘을 이것으로 바꾼다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:45:36 +09:00
ttipo 5b96ec2e85 docs: 앱 새 메일 알림 보드(App1Push)를 디자인 원본에 더한다
2026-11-07 사용자 확정(캔버스 v65). 허락 묻기 · 설정의 새 메일 알림 줄 상태 ·
휴대폰 알림 모습 · 로그인이 끝났을 때 알림 · 업데이트 화면 · 앱 아이콘 후보를 그렸다.
앱 아이콘은 남색 바탕에 흰 ARA 마크, 작은 아이콘은 ARA 마크(실기기에서 다시 본다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:40:17 +09:00
ttipo 9c5db05ada docs: 앱 검색 2차(조건으로 찾기)를 앱 README · 계약 · flutter 스킬에 적는다
조건 시트 · 날짜 · 조건 줄 · 낱말 넘김 · 시트의 세 모양 · 가림막처럼 구현하며
정한 것과 웹과 다른 점(검색어도 조건도 비면 찾기 전 화면 · 시트 안 낱말 알림 ·
날짜는 오늘까지)을 적고, 1차 문서의 "조건으로 찾기는 2차"와 미룬 것을 고친다.
조건 칸은 14단계 서버부터 받는다는 것과 알려진 한계 둘(라틴 글자의 "나" 견주기 ·
한 스크롤 모양에서 끌어 내려 닫기)도 남긴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:00:33 +09:00
ttipo facf9cd936 feat(flutter): 메일 검색 2차 — 조건으로 찾기 시트와 결과의 조건 줄
2026-11-07 확정한 보드 App1SearchConditions 대로 웹 14단계의 [조건] 패널을
바닥 시트로 옮겼다. 보낸 사람 · 받는 사람 · 제목 · 내용 · 기간 · 첨부로 찾고,
결과에는 "조건: …" 줄과 [조건 지우기]를 둔다. 조건 다듬기 · 합친 낱말 수 ·
기간 · 날짜의 UTC 시각 · 강조 쿼리 · "나"는 웹 mail-search.ts 와 짝이다.

[조건]은 위 막대 맨 오른쪽에 고정해 글을 써도 옆으로 밀리지 않게 했고,
시트에서 낱말 8개를 넘기면 시트를 연 채 알린다. 날짜는 오늘까지만 고른다.
바닥 시트 가림막을 앱 대화상자 색으로 맞춰 메일함 옮기기 시트도 같은 색이 된다.

리뷰에서 가로 화면에 자판이 오르면 시트 모양이 바뀌며 칸이 새로 만들어져
초점을 잃는 것을 찾았다. 시트를 한 트리로 두고 같은 레이아웃 안에서 머리 ·
단추 줄의 실제 높이로 모양 셋(위를 남긴 고정 · 화면 위까지 고정 · 한 스크롤)을
고르게 고쳤고, 모양이 바뀌거나 알림이 뜨면 초점 칸 · 알림을 보이게 한다.
시험 872 → 932.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:00:15 +09:00
ttipo bec78bce42 docs: 앱 검색 2차 보드(App1SearchConditions)를 디자인 원본에 더한다
2026-11-07 사용자가 확정한 조건으로 찾기 화면 넷이다(캔버스 v63) — 조건 시트,
날짜 고르기, 조건이 걸린 결과, 조건에 맞는 메일이 없을 때.
계획과 다르게 정한 것: [조건]은 위 막대 맨 오른쪽에 고정, 시트에서 낱말 8개를
넘기면 시트 안에 알림, 날짜는 오늘까지만 고르고 칸마다 지우기 단추, 가림막은
앱 대화상자 색. 화면의 글은 화면 상수가 기준이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:00:36 +09:00
ttipo 3278e1abc1 feat(flutter): 조사 도우미가 호환 자모 · 숫자의 받침도 고른다
웹 조사 도우미(frontend shared/utils/korean.ts)와 짝으로 맞춘다. 초성 검색어(‘ㄱㅁ’)나
숫자로 끝나는 말이 "(으)로" · "이(가)" 꼴로 나오지 않게, 호환 자모 자음은 받침 있음
(ㄹ 은 "로"), 모음은 받침 없음, 숫자는 읽는 소리(영 · 일 · 이 …)로 고른다. 라틴 글자 ·
기호로 끝나면 지금처럼 괄호 꼴이다. 알려진 한계: 라틴 글자 뒤의 숫자도 한국어 소리로
센다(‘녹음.mp3’은).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 14:42:43 +09:00
ttipo ca6c68c34f docs: 앱 README 에 옮겨 적은 검색 글을 새 문구로 맞춘다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 14:04:59 +09:00
ttipo 23afa583c1 fix(flutter): 앱 화면 문구 18곳을 사람이 말하듯 다듬는다
2026-11-07 사용자가 "자연스러운 문구가 아니라 시스템, 설계 문구 형식"이라고
짚은 글을 웹과 같은 기준으로 고친다. 화면이 하는 일을 설명하는 말("여기에
표시됩니다")과 번역투("맞는 메일")를 빼고, 빈 메일함 안내 · 읽기 화면 본문 안내 ·
검색 글 · 로그인 글을 내 메일이 어떻게 되는지와 할 일로 쓴다. 본문을 다 보이지
못할 때는 PC 웹메일에서 원문을 받는 길까지 적고, 사용자가 만든 메일함의 빈 안내는
앱의 [이동]이 아이콘이라 길게 눌러 고르는 말로 쓴다. 오류 사전은 이 브랜치를 main
위로 옮길 때 새 글로 다시 만든다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 14:04:59 +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 97a989be07 chore: 앱 짝 시험이 읽는 백엔드 표 두 파일이 바뀌어도 Flutter CI 를 돌린다
첨부 형식 표(attachment.policy.ts)와 검색 표본 표(search-text-samples.ts)만 고친
커밋에서는 앱 시험이 깨진 채로 남고 다음 flutter 커밋에서야 드러났다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:19:45 +09:00
ttipo 37f078e65c feat(flutter): 메일 검색 화면 1차 — 찾기 · 결과 강조 · 이어 찾기 · 안내를 더한다
목록 위 막대의 [찾기]가 탭 안 검색 화면(/mail/search)을 연다. 웹 14단계 검색과
같은 한도(UTF-16 200자 · 낱말 8개)를 먼저 보고, 찾을 곳은 시작한 메일함 | 전체
메일함이다. 결과 줄은 목록 줄 그대로 맞은 말을 노랑으로 칠하고(회색 미리 보기
안은 글자를 진하게), 결과에서 연 읽기 화면도 제목 · 첨부 이름 · 본문을 칠한다.
이어 찾기는 한 차례 5번 · 20통이고, 모자라면 [더 찾기]로 멈춘다. 결과는 새 메일
확인으로 다시 찾지 않고, 읽기 화면에서 한 일과 붙잡힌 메일의 풀림만 반영한다.
낱말 나누기 · 한 글자 판정은 웹 · 서버와 짝이고, 시험이 백엔드 표본 표를 그대로
읽는다. 시험 750 → 870.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:19:45 +09:00
ttipo 88ec6433b9 docs: 앱 검색 1차 보드(App1Search · App1SearchStates)를 디자인 원본에 더한다
2026-11-07 사용자가 추천대로 확정한 앱 메일 검색 화면 13장이다(캔버스 v60).
강조된 미리 보기 글은 진하게(#4A5363 — 노랑 위 6.3:1) 그렸다. 화면의 글은
이어서 다듬는 화면 문구(상수)가 기준이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:18:30 +09:00
ttipo 25280d8e90 chore: 14단계(메일 검색 색인 · 검색 · 대화)를 앱 검색 브랜치에 합친다
앱 검색 화면(App1Search)은 계약 2절 "검색 — 14단계", 웹 글 원본과
백엔드 표본 표(backend/test/search-text-samples.ts)를 짝 시험으로 읽는다.
feat/mail-search-index 는 아직 main 에 없어서, 갈래점이 ba80956 인 앱 브랜치에
먼저 합친다. 14단계가 main 에 들어가면 이 브랜치를 main 위로 옮긴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 09:30:37 +09:00
ttipo 94da1d2031 style(flutter): 소스의 날것 보이지 않는 글자를 이스케이프로 적는다
nbsp(본문 코드 둘 · 시험 하나)와 폭 없는 공백 · BOM(시험 하나)이 소스에 그대로
있었다 — 눈에 보이지 않아 고치다 지우거나 잘못 옮기기 쉽다. flutter 스킬 규칙대로
\u 이스케이프로 바꾼다. 뜻은 같다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 04:22:19 +09:00
ttipo 7546af0243 docs: 앱 쓰기 화면 2차(첨부 고르기 · 올리기)의 구현하며 정한 것과 짝을 적는다
앱 디자인 README 「쓰기」(첨부 · 나가기 · 알려진 한계 · 실기기에서 볼 것),
flutter 스킬(받은 파일 · 고른 파일 · 형식 줄, 메일 쓰기 줄, Kotlin 을 고쳤을 때의
apk 빌드, 시험 함정), 백엔드 형식 표(ATTACHMENT_TYPES)에 앱 형식 표와의 짝 주석.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 04:20:20 +09:00
ttipo 3a126bdf83 feat(flutter): 메일 쓰기 화면 2차 — 첨부 고르기 · 올리기를 더한다
[파일 첨부]가 안드로이드 고르는 창(권한 없음)을 연다. 고른 파일은 앱 임시 폴더에
복사해 두고 그 복사본을 multipart 로 둘씩 올린다 — 올리기에 실패하거나 서버가 첨부를
찾지 못해도(MAIL_004) 다시 고르지 않고 다시 올리려고.

- 사전 검사 · 보내기 막기는 웹과 같은 차례, 한도는 GET /api/mail-quota
- 기기가 같은 차례로 미리 걸러 넣을 파일만 복사한다(content:// 만, 판정은 Dart 가 다시)
- 다 올리면 서버가 정리한 이름 · 크기로 싣는다(이름 255자 — 웹과 같다)
- MAIL_004 뒤 다시 올릴 수 없는 파일은 빼기 전까지 저장 · 보내기를 막는다
- 가져오는 · 올리는 동안 [보내기]를 막고(가져오기는 10분 시한), 올리는 중 나가기는 따로 묻는다
- 화면이 끝나거나 직원이 바뀌거나 앱을 띄울 때 복사본을 지운다

리뷰(M1~M4 · L 다섯 · 다시 본 리뷰의 L 하나)를 고쳤다. 시험 690 → 750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 04:19:57 +09:00
ttipo f39f41ed4b docs: 앱 메일 쓰기 화면 1차의 본문 모양과 구현하며 정한 것을 적는다
계약 2절 "서식 본문"에 앱이 만드는 모양(서명 경계 div · 글로 만든 원문
인용 · 다시 열기 조건), 앱 디자인 README 「쓰기」에 구현하며 정한 것 ·
알려진 한계 · 실기기 점검, flutter 스킬에 쓰기 화면 줄과 위젯 · 시험
함정을 적는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 02:11:01 +09:00
ttipo 80e48d318d feat(flutter): 메일 쓰기 화면 1차를 만든다 (App1Compose)
새 메일 · 답장 · 전체 답장 · 전달 · 이어 쓰기 · mailto 를 연다. 글만 쓰고,
보낼 때 웹과 같은 서식 본문(기본 글꼴 div · 서명 경계 div · "원본 메일"
인용)과 텍스트 본문을 함께 만든다 — 원문 인용은 글로 만든 덩이다.

- 임시 저장은 웹 규칙(3초 · 30초 · 10초 · 한 번에 하나)에 더해 앱이 안
  보이게 되면 바로 저장한다
- 보내기는 같은 Idempotency-Key 규칙(성공 · MAIL_005 에만 새 키), 보낸 뒤
  시작한 화면에 [보내기 취소] 마감 알림 · 남은 시간 막대
- 서명은 따로 된 칸([서명 빼기] · [서명 넣기]), 원본 메일은 접힌 채
- 리뷰 H1: 진행 중인 저장을 기다리는 사이 화면을 떠나면 메일이 나가지
  않던 것 — keepAlive 를 기다림 앞에서 잡고 떠나도 그대로 보낸다
- 리뷰 M1 · M2 · M3: 저장본 읽기는 읽기 화면과 같은 한도 · isolate,
  웹 저장본의 정렬 · 들여쓰기는 남의 저장본 길로, 확인 창의 거짓 "저장했습니다"
- 첨부 고르기 · 올리기는 2차(지금은 전달 원본 · 저장본의 첨부만 싣고 뺀다)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 02:11:00 +09:00
ttipo 695a343878 feat(flutter): 사람 고르기(core/people) — 주소 칩 · 받는 사람 추천 맞추기를 더한다
쓰기 화면의 받는 사람 칸이 쓰고, 나중의 캘린더 · 파일 모듈도 함께 쓰게
core 에 둔다. 주소 검사는 웹 mail-address.ts, 추천 맞추기(초성 · 겹자음 ·
조합 중 글자)는 웹 recipient-match.ts 와 짝이고, 표본 표는 웹 · 백엔드와
같은 70행을 같은 차례로 옮겼다(세 쪽 짝 — 한쪽을 고치면 모두).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 02:11:00 +09:00
ttipo 20cde01454 fix(flutter): 읽기 화면 예약 안내의 조사를 붙여 쓴다
"오전 9:00 에 보낼 메일입니다." → "오전 9:00에 보낼 메일입니다." 웹 9단계 글을
그대로 옮긴 것이었는데, 웹 실사용 시험(2026-11-06)에서 띄어쓰기가 틀렸다고
나와 웹과 함께 고친다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 02:10:59 +09:00
ttipo f4b1d0a206 docs: 앱 메일 읽기 화면의 구현하며 정한 것을 앱 디자인 README · flutter 스킬에 적는다
앱 디자인 README 「읽기」에 짜임 · 두 번째 정화 · 그림 한도 · 첨부 형식 막기 ·
붙잡힌 메일 · 알려진 한계 · 실기기 점검을, 「쓰기」에 mailto 숨은참조를
펼쳐 보일 것을 적는다. flutter 스킬 골격 표에 읽기 화면 · 받은 파일 줄과
인터셉터의 실패 본문 규칙을 더한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:26:09 +09:00
ttipo a37045a553 feat(flutter): 메일 읽기 화면을 만든다 (App1Reader)
머리는 앱 화면, 본문은 스크립트를 끈 WebView 에 앱이 감싼 CSP 문서로
보인다. 받은 HTML 은 서버 정화 뒤 앱이 한 번 더 허용 목록으로 다시 쓰고,
외부 이미지는 [이미지 보기] 뒤 앱이 Dio 로 받아 data: 로 넣는다(토큰이
WebView 에 가지 않는다).

- 읽음 · 중요 · 이동 · 스팸 · 삭제 · 영구 삭제, 붙잡힌 메일의 보내기 취소 ·
  예약 취소는 목록과 같은 규칙. 응답은 그 요청이 바꾼 칸만 지금 값에 덮는다
- 첨부는 앱 임시 폴더(cacheDir/downloads)에 받아 FileProvider + ACTION_VIEW
  로 연다(파일 여는 플러그인 없이). 열 형식은 확장자 먼저, 웹 문서 · svg ·
  설치 파일 · 인증서는 받지 않는다
- 응답 인터셉터가 바이트 · 스트림 실패 본문(64KB 까지)에서 코드를 읽고,
  뜻밖의 실패에도 handler.next 로 끝낸다
- 리뷰 H1: 같은 그림을 수백 번 쓴 메일이 문서를 GB 단위로 키우던 것 —
  문서에 싣는 그림 글을 나온 횟수까지 세어 10M 자에서 멈춘다
- 리뷰 M1: 짧고 깊은 본문을 화면 isolate 에서 정화하던 것 — 길이와 중첩
  비용으로 고르고(6,144자 · 비용 5,000), 그 밖은 Isolate.run
- 새 의존성: webview_flutter 4.14.1 · webview_flutter_android 4.12.0 ·
  html 0.15.7(+ csslib 1.0.2) — 배포 2주 넘음. path_provider · mime 은
  lock 에 있던 판을 직접 의존으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:26:09 +09:00
ttipo ef23afc94c docs: 앱 메일 목록의 구현하며 정한 것을 앱 디자인 README · flutter 스킬에 적는다
- 앱 디자인 README: 고르기(줄 누르기 · 바뀌는 단추 · 영구 삭제 · 읽음 뒤 끝내기 · 선택 풀기),
  붙잡힌 메일, 알림 두 갈래, 서랍, 30일 줄 · [찾기] 준비 중, 새 메일 확인과 병합 규칙(웹과 다른 점),
  크기 맞춤, 메일함 이름 차례
- flutter 스킬 골격 표: 메일 목록 · 앱 틀 줄(틀은 Scaffold 가 아니다 · ShellBarHidden · 알림 두 갈래 ·
  생명주기 안 프로바이더 · 가짜 서버의 한 프레임 응답)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 19:36:31 +09:00
ttipo ad8b126abb feat(flutter): 메일 목록 화면을 만들고 앱 틀이 화면마다 탭 막대를 숨길 수 있게 한다 (App1MailList)
앱 틀(core/router):
- 틀을 Scaffold 가 아니라 Column + 탭 막대로 — 탭 안 화면마다 자기 Scaffold 를 두어 전역 알림이
  [메일 쓰기] 위에 뜬다. 탭 막대는 위 안전 영역을 빼고(실기기에서 상태 표시줄만큼 두꺼워지지 않게),
  화면이 ShellBarHidden 으로 숨길 수 있다(모듈마다 셈 · 보이는지는 TickerMode, 자판이 열리면 뺌)

메일 목록(features/mail):
- 메일함 서랍(바꿔 끼움 · 안 읽은 수는 회색 글), 줄(사진 · 보낸 사람 · 시각 / 제목 · 첨부 /
  미리 보기 한 줄 · 안 읽음 굵게 · 중요 별), 커서로 이어 받기 · 당겨서 새로고침 · 빈 메일함 · 30일 줄
- 고르기 막대(읽음 · 중요 · 이동 · 삭제) — 먼저 바꾸고 failedIds 만 되돌리기, 이동 · 삭제는 응답
  id 만, 100통씩, 휴지통으로 옮기고 [되돌리기], 휴지통에서는 확인 뒤 영구 삭제, 붙잡힌 메일은 고를 수 없음
- 새 메일 확인: 보이고 앱이 앞에 있을 때만 1분마다, 다시 받은 쪽이 끝까지면 지운 옛 메일을 뺀다
- [되돌리기] 알림은 [메일 쓰기]와 한 자리(화면 낭독기를 켠 동안은 닫지 않음), 단추 없는 알림은 전역 스낵바
- 시험 346건(새로 119)

리뷰 반영: 탭 막대 높이, 고르는 중 알림 줄 위치, 이어 받기 중 새로고침 실패, 붙잡힌 메일 다시 보기,
메일함 역할을 알기 전 그리지 않음, 선택 풀기 · 되살리기를 계약에 맞춤, 맞추기 한 번에 하나.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 19:36:31 +09:00
ttipo 4e779dedd2 docs: 앱 로그인 네 단계의 구현하며 정한 것을 앱 디자인 README · flutter 스킬에 적는다
- 앱 디자인 README: 단계마다 실패를 보이는 곳(표), 안내와 오류 함께, 세션 끝은 상자만,
  [다른 계정으로 로그인]을 막는 때, 2단계 켜기의 처음 초점 · 켠 뒤 알림, 다른 곳에서 켠 경우,
  주소 칸 자판, 글자 크기 맞춤, 새 글, 알려진 한계(인증 앱에 다녀오는 사이 앱이 정리되면 새 키 ·
  최근 앱 미리 보기 · 클립보드 · iOS)
- flutter 스킬 골격 표에 로그인 화면 줄(노티파이어 제한 단계 API · 알림 끄는 묶음 · url_launcher
  쓰는 규칙 · KeepAllText)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:57:39 +09:00
ttipo c14580e3a4 feat(flutter): 로그인 네 단계 화면을 만든다 (앱 1단계 — App1Login)
주소 · 비밀번호 → 인증 앱 코드 → 새 비밀번호 정하기(임시 비밀번호) → 2단계 켜기(필수일 때)를
세션 노티파이어의 상태로 고른다. 첫 단계만 큰 브랜드 띠, 그다음은 짙은 띠에 [다른 계정으로 로그인].

- 2단계 켜기는 같은 휴대폰이라 QR 대신 [인증 앱에 추가](otpauth — url_launcher 6.3.3,
  canLaunchUrl 은 주소를 로그에 남겨 쓰지 않는다) · 키 [복사] · 코드 칸
- 실패는 칸 아래 · 위쪽 상자 · 전역 알림으로 웹 로그인 틀과 같게(REQ_001 은 코드 · 제한 단계에서
  상자), 칸의 안내와 오류를 함께, 세션 끝은 로그인 화면 상자만
- 비밀번호 · 코드 · 2단계 키는 화면 메모리와 노티파이어의 10분 기억에만(두 제한 단계에서만 쥔다),
  비밀 요청의 실패 예외는 본문을 비운다 — 시험이 모든 프로바이더 값에 비밀이 없음을 본다
- 제한 단계를 보내는 동안 [다른 계정으로 로그인]을 막는다, 주소 칸만 자판 추천(@ 자판)
- 한글 낱말 단위 줄바꿈(KeepAllText), 시험 227건

리뷰 반영: 모르는 제한에서 비밀번호를 쥐지 않음, REQ_001 자리, 안내 · 오류 함께, 주소 칸 자판,
계약 테스트 목록(AUTH_005 · AUTH_008), AUTH_002 를 두 번 알리지 않음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:57:39 +09:00
ttipo dd02b9621a fix(flutter): 모양이 틀린 로그인 응답의 토큰 · 다시 확인 재사용 창 · 기다리던 다시 맞추기를 고친다
앱 기반 3차 확인에서 나온 낮음 넷이다.

- 토큰은 실렸는데 모양이 틀린 로그인 응답은 응답을 뺀 실패로 던지고, 읽을 수 있는 토큰으로
  조용히 logout 한다(비밀 요청의 실패 예외는 응답 본문도 비운다)
- 확인하지 못한(unknown) 다시 확인 결과는 처음 물은 때부터 5초만 다시 쓴다
- 로그아웃 · 다른 계정 · 세션 끝 때 기다리던 다시 맞추기에 stale 로 답한다 — 새 로그인의 오류가
  앞 세션의 me 를 기다리지 않게
- postAppLogout 주석을 사실대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:05:46 +09:00