Commit Graph

232 Commits

Author SHA1 Message Date
ttipo 5ce614cc0c docs: 업데이트 내역을 계약 7절 · 디자인 README · 스킬에 적는다
계약 7절(API 둘 · 글 파일 규칙)과 머리말의 달력 날짜 예외, 웹 · 앱 디자인 README 의 업데이트 내역 절, 세 스택 스킬 행을 더했다.
보드의 목록 최대 개수를 구현(100)에 맞추고, 편집 도구가 보이지 않는 글자의 이스케이프를 실제 글자로 푼 일을 공통 규칙에 적는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:37:01 +09:00
ttipo def96e833b feat(frontend): 설정 › 정보 › 업데이트 내역(목록 → 글)을 더한다
- 설정 메뉴 셋째 묶음 "정보", 목록은 게시판 표(줄 전체 링크 · [더 보기]), 글은 [← 목록으로] · 다음 글 · 이전 글 줄 · [목록]
- 글을 보는 동안에도 메뉴 항목이 고른 상태다(aria-current 는 목록 page · 글 true)
- 문서가 스크롤하는 좁은 폭에서도 글 · 목록이 맨 위부터 보이고(라우터 전역 scrollBehavior 없이), 위에 붙은 줄에 초점이 가리지 않는다
- 날짜는 달력 날짜 글이라 시간대를 거치지 않는다, /settings/info 는 목록으로 바꿔 끼운다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:37:00 +09:00
ttipo 1d01130c9d feat(flutter): 설정 › 앱 정보에 업데이트 내역(목록 → 글)을 더한다
앱 버전 다음 줄에서 목록(제목 · 날짜, [더 보기])으로 들어가 줄을 누르면 글(날짜 · 제목 · 묶음, 다음 글 · 이전 글 줄)이 열린다.
없는 글 · 받지 못함 · 글 없음 상태를 두고, 날짜는 달력 날짜 글이라 시각으로 바꾸지 않는다(CalendarDate). 다른 글 줄은 지금 글을 바꿔 끼워 뒤로는 늘 목록이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:37:00 +09:00
ttipo 1b3f9ba7fe refactor(flutter): 메일 화면의 불러오기 상태 위젯을 shared 로 옮긴다
업데이트 내역(설정 모듈)도 같은 상태 틀을 쓰는데 모듈끼리 import 하지 않으므로 shared/widgets/load_states.dart 로 옮겼다.
모양 · 동작은 그대로다(MailStateLayout → StateLayout · MailListError → LoadErrorView · 막대 SkeletonBar · LoadingSkeleton). 메일 줄 꼴의 막대와 빈 메일함 등은 메일에 남는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:36:59 +09:00
ttipo cd958f3bf4 feat(backend): 업데이트 내역 읽기 API 와 첫 글을 더한다
직원이 웹메일 · 휴대폰 앱에서 무엇이 새로 생겼는지 읽는 게시판의 서버 몫이다. 글은 운영자가 backend/release-notes/ 에 글마다 파일 하나로 쓰고
(올리는 API · DB 표 · 새 에러 코드 없음), GET /api/release-notes(커서 목록)와 GET /api/release-notes/{id}(글 + 바로 옛 글 · 새 글)로 웹 · 앱에 똑같이 준다.

- 글 폴더는 src 밖이라 개발 컨테이너(바인드 마운트)는 다시 띄우지 않아도 새 글이 보이고, 운영 이미지는 Dockerfile 이 폴더를 함께 복사한다
- 틀린 글은 실행 때 그 글만 건너뛰고 error 로그(파일 이름 · 줄 · 까닭만 — 기동 때 한 번 읽어 배포 직후 보인다), 실제 폴더를 엄격하게 읽는 시험이 CI 에서 운영자의 오타를 잡는다
- 파일은 O_NOFOLLOW 로 한 번 열어 일반 파일 · 256KB 까지만 읽고, 모양이 틀린 id 도 없는 글과 같은 404 RES_001 이다
- 첫 글(2026-11-18.md)은 다음 배포 소식이다 — 파일 이름(날짜)은 배포 날로 고친다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:36:59 +09:00
ttipo abe4c1364c docs: 업데이트 내역 보드를 더한다
웹 설정 › 정보 › 업데이트 내역과 앱 설정 › 앱 정보 › 업데이트 내역(게시판 — 목록에서 글로), 글 파일 쓰는 법을 한 보드에 그렸다(캔버스 v80 확정).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 16:43:26 +09:00
ttipo 1962f07d79 docs: 로드맵에 16단계 합침과 업데이트 내역 시작을 적는다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 16:33:55 +09:00
ttipo d6b804ba83 fix(frontend): 16단계 실사용 시험에서 나온 둘을 고친다
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m52s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m4s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m39s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m53s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 14m39s
CI / Frontend (lint · type-check · test · build) (push) Successful in 14m11s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 2m4s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m34s
- 주소록 가져오기 창의 미리 보기가 1366×768 · 1440×900 에서 잘리고 창도 스크롤하지 않았다 — 창(<dialog>)은 flex 열이라 overflow 를 건 세 묶음 상자가 내용보다 작게 눌렸다. 상자에 flex-shrink: 0 을 두고 가져오기 · 내보내기 창은 창 안이 스크롤한다
- 서명 위 빈 줄에 한글을 조합해 쓰면 띄어쓰기를 칠 때까지 빈 줄이 돌아오지 않았다 — 조합이 끝나면(compositionend 뒤 ProseMirror 가 조합을 마무리한 다음 차례) 편집기가 스스로 빈 줄을 넣는다. 조합 중에 임시 저장한 뒤 편집기가 넣은 빈 줄은 바꿈으로 세지 않는다
- 디자인 README · vue3 스킬에 두 가지를 적는다(모달 창의 flex 열 — 창 바로 아래 칸에 overflow: hidden 을 걸면 flex-shrink: 0 을 함께)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 13:30:08 +09:00
ttipo 19dbb2babb docs: 메일 16단계(개인 설정)를 계약 · 디자인 README · 스킬 · 로드맵에 적는다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 15m17s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 14m10s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m54s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 14m55s
CI / Frontend (lint · type-check · test · build) (push) Successful in 13m43s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 2m2s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m39s
계약 2절(서명 · 부재중 — 원본 판 3 과 되돌리기 계획) · 3절(가져오기 · 내보내기) · 5절(휴대폰 바꾸기),
디자인 README 16단계 절과 구현하며 정한 보완, nestjs · vue3 스킬 표, 로드맵의 추후 후보(16단계에서 미룬 것).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:44:43 +09:00
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 0c3238936a feat(backend): 주소록 가져오기 · 내보내기 · 여러 서명 · 부재중 서식 · 2단계 인증 휴대폰 바꾸기
메일 16단계(개인 설정)의 서버다.
- 주소록: POST /api/contact-import-checks · contact-imports · contact-deletions 로 여러 명을 한 번에
  확인 · 만들기 · 지우기(화면이 준 uid 로 다시 보내도 두 번 들어가지 않는다), GET /api/contact-exports 로
  vCard 3.0 · CSV(BOM · 한국어 머리 · 수식 막기) 내보내기 — 하나씩 만들면 사무실 요청 수 한도에 걸린다.
- 여러 서명: /api/mail-signatures · /api/mail-signature-defaults, 서명마다 한 줄로 바꾸는 마이그레이션
  (지금 서명은 "기본 서명"으로 — 값 보존), 그림 정리는 모든 서명의 합집합. 옛 /api/mail-signature 는 앱 첫 판을 위해 그대로.
- 부재중: 서식 본문(multipart — 문자열 4,096바이트는 variables 조각으로)과 회사 밖 응답(봉투 도메인으로 가름 —
  답장도 그 주소로만), 원본 판 3 은 새 칸을 쓴 직원만.
- 2단계 인증: 켠 채 휴대폰 바꾸기(새 코드 확인 → 지금 코드 · 비밀번호로 엔진 요청 하나 — 꺼지는 틈 없음).
새 에러 코드 · 새 의존성 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:44:41 +09:00
ttipo fceb6ed3e4 docs: 메일 16단계(개인 설정) 보드 셋을 더한다
주소록 가져오기 · 내보내기(Contacts16), 여러 서명과 쓰기 화면 서명 바꾸기(Signature16),
부재중 서식 · 회사 밖 응답과 2단계 인증 휴대폰 바꾸기(Settings16). 캔버스 v74 에서 확정했다 — 글은 화면 상수가 기준이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 08:55:04 +09:00
ttipo 060265c8b0 docs: 로드맵에 15단계 합침과 16단계 시작을 적는다
15단계(메일 · 메일함 다루기)는 main 에 합쳐 14단계와 함께 배포를 기다리고,
16단계(개인 설정)를 개발하기 시작했다. 16단계 가져오기는 엑셀(.xlsx)도 받는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 08:53:04 +09:00
ttipo 071fe9a60f fix(frontend): 15단계 실사용 시험에서 나온 다섯 가지를 고친다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m24s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m55s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m1s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m49s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m34s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m31s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m52s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m15s
- 폭 360 에서 검색 칸이 상단 바 밖으로 넘쳐 화면이 옆으로 밀렸다(14단계부터) — 메일 검색 칸 · 비활성 통합 검색 칸의 최소 폭을 0 으로 둔다
- 읽기 칸 도구 줄이 이름을 숨기는 폭을 570 · 595 에서 580 · 605 로 올린다 — 실측한 필요 폭(569.19 · 594.83)에 여유가 1px 도 없어 글자 · 여백이 조금만 늘어도 두 줄로 꺾였다
- 잘못 놓은 파일 무시를 메일 화면(MailLayout)에서 앱 틀(AppShell)로 옮긴다 — 주소록 · 설정에 놓은 파일은 브라우저가 열어 웹메일을 떠났다. 쓰기 화면 · 서명 설정은 지금처럼 받는다
- 받은편지함 정리를 켠 차단 뒤에는 다음 메일 대신 목록으로 간다 — 다음 메일이 같은 보낸 사람의 것이면 열자마자 읽음이 되고 곧 정리가 스팸메일함으로 옮겼다
- 모달 창의 제목 · 글은 낱말 가운데서 줄을 바꾸지 않고("지울까/요?") 띄어쓰기 없는 긴 글만 아무 데서나 끊는다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:42:17 +09:00
ttipo 3c9b3727c6 docs: 저장소 소개를 ARA SYSTEM 의 지금 구성에 맞게 고치고 로드맵을 더한다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m49s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m45s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m55s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m12s
루트 README · CLAUDE.md · 점검 목록이 아직 "풀스택 모노레포 템플릿"으로 소개하고 있었다 — 사내 그룹웨어(메일 운영 중)에 맞게
지금 상태 · 구성(메일 엔진 · 리졸버 · ClamAV · 운영 스크립트 포함) · 문서 지도 · 디렉터리 구조로 다시 쓰고,
백엔드를 호스트에서 npm ci 하라던 옛 안내(컨테이너 안에서만 — 규칙과 반대)를 뺐다.
docs/roadmap.md 에 메일 1~16단계 상태(16단계 첫 항목: 주소록 가져오기 · 내보내기) · 공통 기반 · 2부 · 앱 · 추후 후보 모음을 둔다.
backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 · 참고 문서로,
규칙 · 스킬 · 역할 문서의 "템플릿" 표현("템플릿 기본 제공 골격" 등)은 "기본 골격"으로 바꿨다(nginx 설정 템플릿은 그대로).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 20:19:20 +09:00
ttipo 7f39aa4eb2 docs: 메일 15단계(메일 · 메일함 다루기)를 계약 · 디자인 README · 스킬에 적는다
계약 2절에 메일함 옮기기 · 하위까지 지우기 · 받은편지함 정리 · 지금 보내기(결과를 모르는 실패와 알려진 한계까지),
디자인 README 에 15단계 절과 구현하며 정한 보완, nestjs · vue3 스킬 표에 새 파일과 잠금(여러 대로 늘릴 때 옮길 것).
14단계 "미룬 것"에서 15단계에 한 것(대화째 끌기 · 모두 펼치기 · 줄 머리 답장)을 뺐다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 18:38:11 +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 1ca210dec4 feat(backend): 메일함 옮기기 · 하위까지 지우기 · 차단할 때 받은편지함 정리 · 예약 메일 지금 보내기
- PATCH /api/mailboxes/{id} 에 parentId — 맨 위(null) · 자기 하위로는 VAL_001 · 새 상위에 같은 이름(대소문자 무시)은 MAIL_009 · 역할 메일함 막기
- DELETE /api/mailboxes/{id}?withChildren=true — 나무를 깊은 것부터 지금 지우기와 같은 차례로(휴지통으로 → destroy)
- POST /api/mail-block-runs — 받은편지함에서 차단한 보낸 사람의 메일을 스팸메일함으로(Sieve 차단 규칙과 같은 판정,
  필터 적용과 같은 cursor · 잠금, 도는 동안 스팸메일함 잠금)
- POST /api/scheduled-messages/{id}/release — 예약 제출을 취소하고 원래 envelope 으로 다시 제출(보내기 취소 설정만큼 붙잡음).
  결과를 모르는 실패면 다시 내지 않고 503 — 같은 요청이 이어 낸다(두 통이 나가지 않게), 엔진이 분명히 거절했을 때만 임시보관함으로

엔진 사실은 개발 엔진(Stalwart 0.16.27)으로 확인했다: parentId 바꾸기 · 고리 거절, destroy 는 적은 차례대로,
from 검색은 비슷한 주소 · 표시 이름의 주소까지 넓게 잡힘, 취소 뒤 다시 제출하면 받는 사람에게 한 통.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 18:02:40 +09:00
ttipo 099faeaafc docs: 메일 15단계(메일 · 메일함 다루기) 보드 셋을 더한다
읽기 화면 [이동 ▾] · 예약 메일 지금 보내기 · 같은 대화 모두 펼치기(Reader15),
목록 끌어다 놓기 · 좁은 폭 상단 바(ListDrag15), 메일함 옮기기 · 하위까지 지우기 ·
차단할 때 받은편지함 정리(Mailbox15). 캔버스 v71 에서 확정 — 글은 화면 상수가 기준이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 13:56:00 +09:00
ttipo ff1948b7df docs: 휴대폰 앱 사내 배포를 계약 · 운영 · 앱 · 디자인 문서와 스킬에 적는다
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m41s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m13s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m13s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m5s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 21m49s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m13s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m54s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m47s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 10m26s
계약 1절 "휴대폰 앱 받기"와 6절 운영 도구 명령, 서버 B 에 APK 올리는 차례와 직원 안내(링크 없이),
앱의 서명 키 · release 빌드 · 올리기 전 확인, 웹 화면 절, 스킬 표, App1Shell 보드의 앱 정보 줄.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:06 +09:00
ttipo 7c70862647 feat(docker): 휴대폰 앱 받기 경로에 앱 안 받기와 같은 상한 · 흘려보내기를 둔다
APK 받기가 일반 /api 로 가면 X-Accel-Buffering 이 외부 프록시로 넘어가지 않아 받는 사람마다 임시 파일이 쌓이고,
버퍼링을 끈 받기에 둘 동시 연결 상한도 없다(리뷰). 앱 안 대용량 받기와 같은 location(대소문자를 가리지 않는 정규식 ·
같은 그릇)을 두고, CI 의 받기 경로 시험에 변형 · 정보 경로 · 그릇 공유를 더했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:05 +09:00
ttipo e9acb7809c feat(flutter): [새 버전 받기]를 포털 받기 화면으로 바꾸고 release 는 우리 키 · 운영 주소로만 만든다
스토어에 올리지 않으니 업데이트 화면의 Play 스토어 열기를 포털 /app 을 바깥 브라우저로 여는 것으로 바꾸고,
설정 › 앱 정보에도 같은 줄을 더했다. release 빌드는 우리 서명 키(key.properties) · google-services.json ·
운영 포털의 https API 주소가 갖춰지지 않으면 까닭을 알리고 멈춘다 — debug 키로 조용히 서명되거나
개발 주소가 든 판이 직원에게 나가지 않게(알리는 글에 주소 값은 싣지 않는다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:05 +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 f2ba81513e feat(backend): 휴대폰 앱 받기 API 와 운영 도구 올리기 · 목록 명령을 더한다
앱을 스토어 없이 사내에만 나눠 주기로 해(2026-11-08), 로그인한 직원만 최신 안드로이드 APK 의
정보와 파일을 받게 한다(GET /api/app-releases/android/latest · /latest/file — 제한된 세션은 막힌다).
올리기는 API 없이 서버의 운영 도구로 한다(파일 검사 · SHA-256 · 임시 이름에 쓴 뒤 바꾸기 · 실행 기록).
최신은 가장 늦게 올린 판이라 옛 APK 를 다시 올려 되돌릴 수 있고, app-config 의 최소 판보다 낮은 판은 올리지 않는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:03 +09:00
ttipo 1f8555340e refactor(backend): 내려받기 머리글을 download-headers 한 곳으로 모은다
휴대폰 앱 받기(다음 커밋)가 대용량 첨부 받기와 같은 머리글을 쓰게 하려고,
file-sharing 의 writeFileHeaders 가 만들던 머리글을 shared/utils/download-headers.ts 로 옮겼다.
머리글 이름 · 값 · 차례는 그대로다(새 단위 시험과 대용량 받기의 단위 · e2e 가 지킨다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:29:02 +09:00
ttipo 178a53fd6f docs: 휴대폰 앱 받기 보드를 더하고 앱 업데이트 화면을 사내 배포에 맞춘다
앱을 스토어에 올리지 않고 직접 빌드한 APK 를 사내에만 나눠 주기로 했다(2026-11-08).
포털에 로그인한 직원만 받는 화면(설정 › 계정 › 휴대폰 앱, 셸 밖 /app)을 새 보드로 두고,
앱 업데이트 화면의 [Play 스토어 열기]를 [새 버전 받기](받기 화면을 브라우저로 연다)로,
설정 › 앱 정보에는 「새 버전 받기」 줄을 더했다. 캔버스 v68 에서 확정했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 21:02:36 +09:00
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 644c6e9424 chore: 문구 다듬기 브랜치(14단계 · main 을 합친 판)를 앱 서버 브랜치에 합친다
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 23m47s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 14m26s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m42s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m20s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m4s
CI (Flutter) / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Has been cancelled
이 PR 이 문구 다듬기 PR 뒤에 빨리 감기로 합쳐지고 CI 가 합친 모습 그대로를 시험하게 한다.
충돌 넷은 모두 설정 › 로그인된 기기의 글이다 — 앱 쪽은 앱 세션이 함께 보여 "브라우저"를 "기기"로,
다듬기 쪽은 같은 안내 문장을 규칙 9 로("여기에 보이지 않습니다" → "이 목록에 없습니다") 바꿨다.
뜻은 앱 쪽 · 문장은 다듬은 글로 풀었고(account-security.ts · LoginSessionList.vue 주석 · 디자인 README),
시험은 두 쪽이 더한 상수를 모두 남기고 글자 그대로 견주던 줄을 상수로 바꿨다(AccountLoginHistoryPage.spec.ts).
합친 뒤 backend tsc · lint 0 · 단위 3,701 · e2e 333, frontend type-check · lint 0 · 단위 2,774 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:39:07 +09:00
ttipo c1d0808764 chore: 14단계 검색 브랜치(main 을 합친 판)를 문구 다듬기 브랜치에 합친다
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m25s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m42s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 10m58s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 20m34s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 14m34s
CI (Flutter) / Flutter (analyze · test) (push) Has been cancelled
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
14단계 검색 브랜치에 main(#16 — 백엔드 의존성 넷 · 유예 표)을 합친 판을 받는다.
이 PR 이 14단계 PR 뒤에 빨리 감기로 합쳐지고 CI 가 합친 모습 그대로를 시험하게 한다. 충돌 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:22:47 +09:00
ttipo 9bfa53c03a chore: main 을 14단계 검색 브랜치에 합친다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 15m8s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m50s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m55s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m6s
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
#16(백엔드 의존성 넷 · 유예 표)이 갈래점 ba80956 뒤에 main 에 들어갔다 — 이 PR 이 빨리 감기로 합쳐지고 CI 가 합친 모습 그대로를 시험하게 한다. 충돌 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:22:11 +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 41f220bf61 docs: 계약의 앱 알림 실기기 확인에 바꿔 쓴 알림의 소리를 더한다
2026-11-08 갤럭시 S23 에서 사용자가 확인했다. 남은 확인(절전 중 도착 · 다른 곳 비밀번호 바꿈의 세션 끝 알림)은 사용자가 나중에 본다.

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 f433f941e6 docs: 계약의 앱 알림 실기기 확인에 런처 배지를 더한다
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 57340d43df docs: 앱 알림 실기기 확인 결과와 FID 등록 표시 · 봤음 1.5초를 계약에 적는다
2026-11-08 갤럭시 S23(Android 16)에서 FCM 보내기 · 표시 · 같은 tag 바꿔 쓰기 · 닫힌 앱 깨우기 · 누르면 받은편지함을 확인했다.
앱이 고친 두 가지(매니페스트의 firebase_messaging_installation_id_enabled · 보는 중 받은 알림 뒤의 봤음 1.5초)를
"클라이언트 — 앱"에, 같은 초를 세는 N 의 곁 효과를 N 줄에, 옛 Google Play 서비스 휴대폰의 등록 한계를 알려진 한계에 적었다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:56:52 +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 d4ab06183c fix(backend): 엔진 검색의 기간 시작이 그 초에 받은 메일도 찾게 한다
Stalwart 0.16.27 의 Email/query after 는 그 초를 넣지 않는다(2026-11-08 개발 엔진에서 확인 — RFC 8621 은 "같거나 뒤").
그래서 색인이 준비되기 전에 쓰는 엔진 길의 조건 검색이 계약의 "after 이상"보다 1초 좁았다(시작 날 0시 정각에 받은 메일이 빠짐).
시각을 초로 올린 뒤 한 초 앞을 넘겨 색인 길(>=)과 같은 경계로 맞춘다(toJmapAfterInclusive).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:22:59 +09:00
ttipo 3f66f39f9d fix(backend): 붙잡은 직후 들어온 메일 하나에 알림이 두 번 가지 않게 한다
엔진 연결을 붙잡자마자 메일이 오면 따라잡기와 그 메일의 새 메일 신호가 함께 보내 알림이 두 번 갔다(커밋 전 다시 리뷰 N1).
붙잡은 뒤 신호를 이미 받았으면 따라잡기를 건너뛰고, 따라잡기 뒤 간격 끝의 보내기는 따라잡기가 센 뒤에 받은 메일이 있을 때만 보낸다
(기록 저장이 실패해도 겹치지 않게 메모리로 본다). 옛 주석의 "로그인이 저절로 끝남"을 끝난 까닭 표로 고치고 계약 · 스킬의 문장도 맞췄다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:22:58 +09:00
ttipo b77f95fdff docs: 앱 새 메일 알림 · 앱 설정을 계약 · 스킬 · 규칙에 적는다
계약 1절 끝의 옛 메모(/api/push-devices · { minVersion, latestVersion, storeUrl })를 확정한 경로 · 모양 · 규칙으로 바꾸고
(N · 간격 · 따라잡기 · 엔진 after 의 초 경계 · "알림이 멈췄습니다"를 보내는 까닭 표 · 실패 처리 · 실기기로 확인할 것),
"클라이언트 — 앱"에 앱이 실제로 부르는 때를 적었다. 2절 실시간 알림의 붙잡기와 6절 퇴사 마무리 목록도 더했다.
nestjs · docker 스킬과 env · 보안 · 로그 규칙에 FCM 키 자리 · 바깥 연결 · 가릴 값을 더했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:51:58 +09:00
ttipo c0edff10d1 feat(backend): 앱 새 메일 알림(FCM)과 앱 최소 판 확인을 더한다
앱 첫 판의 새 메일 알림을 서버가 보낸다. 알림 글은 "새 메일 N통"뿐이라 보낸 사람 · 제목 · 메일 id 는 Google 을 거치지 않는다.

- 기기 등록: PUT · DELETE /api/push-registrations/current, 봤음 DELETE /api/push-registrations/current/unseen.
  앱 토큰만 받고(AppClientGuard — 웹 세션은 AUTH_003), 세션마다 한 줄, 같은 FID 는 한 줄만 둔다
  (같은 휴대폰에 다른 직원이 로그인하면 앞 직원의 줄을 지운다). 마이그레이션 AddPushRegistrations.
- 새 메일 알아채기: 5단계 엔진 연결을 푸시를 켠 직원에게 붙잡아 둔다(MailEventsService.hold).
  웹 실시간 알림과 같은 판정이고, 같은 직원은 30초에 한 번까지, N 은 보낼 때 엔진에서 센다(웹에서 읽은 메일은 빠진다).
  Stalwart 의 Email/query after 는 그 초를 넣지 않아 어댑터가 한 초 앞을 넘긴다(dev 엔진에서 확인).
- 보내기는 새 의존성 없이 FCM HTTP v1 의 fid 대상으로(RS256 JWT · OAuth 토큰 기억 · 다시 하기 · 보내기 직전 다시 보기).
- 직원이 그 휴대폰을 건드리지 않았는데 앱 세션이 끝나면(저절로 끝남 · 다른 곳에서 비밀번호 바꿈 등)
  그 기기에 "새 메일 알림이 멈췄습니다"를 한 번 보낸다(SESSION_ENDED_NOTICE).
- GET /api/app-config(로그인 없음 · no-store) — 최소 판과 알림을 쓸 수 있는지.
- 서비스 계정 키 FCM_SERVICE_ACCOUNT_KEY(backend env — 없으면 기동하고 알림만 끈다), 퇴사 마무리에 알림 등록을 더한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:49:18 +09:00
ttipo 6ecc38d927 refactor(backend): 동시 수 제한과 직원마다 차례를 shared/utils 로 옮긴다
앱 새 메일 알림(푸시)도 14단계 검색 색인처럼 서버 전체 동시 수와 직원마다 한 번에 하나를 쓴다.
푸시 모듈이 mail-search 를 가져오지 않게 ConcurrencyGate 를 shared/utils 로 옮기고,
직원마다 차례를 KeyedQueue 로 뽑아 MailSearchRuns 가 상속하게 했다. 14단계 동작은 같다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:48:37 +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 f0ef782b24 chore: 14단계 검색 색인을 앱 로그인 브랜치에 합친다
앱 새 메일 알림(푸시)의 서버 일을 앱 세션과 14단계 위에서 하려고 합친다.
겹친 곳은 두 곳이다. auth.module.ts 는 import 둘을 함께 두고,
AuthService.login() 은 enrollAfterLogin(14단계)을 남긴 뒤 recordSignIn(앱 로그인)으로 기록한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:44:45 +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