generated from ttipo/project-base
docs: 14단계 실사용 시험 · 사용자 관점 점검의 고침을 계약 · 디자인 README · 스킬에 적는다
계약 2절에 본문 강조 · 미리 보기 조각 고르기 · 같은 대화 줄 차례 · 설정 다시 받기, 디자인 README 14단계 보완 절에 바뀐 글 · 크기 · 알림과 알려진 한계, nestjs 스킬에 찾기 GIN 하나 규칙, vue3 스킬에 새 도우미를 적는다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -686,10 +686,17 @@ interface MailState {
|
||||
- 부분 일치 — 낱말이 그 칸의 글 어디에 있어도 맞는다('계약' → 계약서 · 계약은 · 계약직, 제목 맨 앞의 [공지] · Re: 도). 영문 대소문자 · 전각 영숫자는 가리지 않는다(NFC).
|
||||
- 여러 낱말은 모두 맞아야 하고 큰따옴표는 구절이다(3단계와 같다). 구절은 줄바꿈 · NBSP 로 갈린 글에도 맞는다 — 띄어쓰기 두 칸에는 맞지 않는다.
|
||||
- **한 글자 낱말**: 두 글자 넘는 낱말과 함께면 모두 찾는다. 한 글자 낱말뿐이면 제목 · 사람(보낸 사람 · 받는 사람 · 참조) · 첨부 이름에서만 찾고 본문은 찾지 않는다(2026-11-05 사용자 확정 — 본문을 다 훑으면 메일이 많은 직원은 수 초. 화면은 결과 머리에 한 줄 알린다).
|
||||
- **본문 강조**(목록 미리 보기 · 읽기 화면)는 본문을 찾은 두 글자 이상 낱말만이다. 한 글자 낱말뿐이면 본문에 강조하지 않고(제목 · 첨부 이름은 그대로), 두 글자 이상 낱말과 섞이면 그 낱말만 강조한다 —
|
||||
함께 찾은 한 글자 낱말은 본문에서 맞았어도 강조하지 않는다(본문 곳곳에 흩어진다 — 2026-11-06 실사용 시험). 두 글자 이상 낱말이 보낸 사람 · 받는 사람 조건에만 있어도 섞인 검색이다.
|
||||
- 본문은 앞 2만 자까지 찾는다. 첨부 파일 안의 글 · 끝이 바뀐 말('보내' → '보냈습니다')은 찾지 않는다(첨부 이름은 찾는다). 받는 사람 칸은 주소들을 이어 보므로 구절이 이웃한 두 주소에 걸쳐 맞을 수 있다(작은 한계).
|
||||
- **응답 (목록)**
|
||||
- 항목(MessageSummary)에 `searchHighlight: { subject: [start, end][], preview: { text, ranges: [start, end][] } } | null` — 색인 방식 검색에서만 차고, 메일함 목록 · 엔진 방식은 `null`.
|
||||
위치는 NFC 글의 UTF-16 [시작, 끝)이고, 강조가 있으면 `subject` 도 NFC 로 싣는다. `preview.text` 는 본문에서 맞았으면 맞은 말 둘레 한 줄(잘린 쪽에 "…"), 아니면 엔진 미리 보기다.
|
||||
본문의 맞은 곳은 이 차례로 고른다(2026-11-06 실사용 시험 — 답장은 처음 맞은 곳이 거의 늘 원문 인용 안이었다).
|
||||
① 인용 줄(앞 공백 뒤 `>`) 밖의 첫 맞은 곳 — 둘레는 인용 줄 전까지.
|
||||
② 인용 안에서만 맞았고, 본문에서 맞은 말이 모두 검색어(`q`)의 낱말로 제목 · 보낸 사람 · 받는 사람과 참조 · 첨부 이름에서도 맞았으면 인용 밖의 첫 글 — 맞은 곳이 없으면 `ranges` 는 빈 배열, 인용 밖 글이 없으면 ③.
|
||||
③ 그 밖에는 줄 앞 `>` 표시(겹친 것 · 사이 공백)를 뺀 인용 조각을 그 인용 안에서만 — `ranges` 는 보이는 글 기준.
|
||||
내용 조건(`body`)의 낱말은 ②에 들지 않는다. `>` 없는 인용 꼴("-----Original Message-----" 등)과 인용 머리 줄("…님이 작성:")은 알아보지 않는다(알려진 한계).
|
||||
- **`hasNext: true` 인데 항목이 `limit` 보다 적거나 0개일 수 있다**(색인 방식) — 한 요청이 확인하는 메일 수에 상한이 있어(3,000 — 1초 안에 답하려고) 그때까지 찾은 것과 이어 찾을 커서를 준다. 화면은 커서가 있으면 이어 묻는다(빈 쪽이 이어지면 몇 번까지만 — 화면 상수).
|
||||
- `total`: 첫 쪽에서 셀 수 있으면 0~1,000, 1,000 을 넘거나 이 요청에서 다 세지 못했으면 `null`, 다음 쪽은 늘 `null`(엔진 방식은 3단계와 같다).
|
||||
**화면은 `null` 이면 통 수를 보이지 않는다** — "1,000통 넘게"라고 쓰지 않는다(다 세지 못한 것일 수도 있다).
|
||||
@@ -698,7 +705,7 @@ interface MailState {
|
||||
- `images` 에 더해 `q` · `from` · `to` · `subject` · `body` 를 받는다(검사는 목록과 같다 — `from` · `to` 는 받기만 하고 강조하지 않는다). `after` · `before` · `hasAttachment` · `cursor` · `limit` 은 받지 않는다(400 `VAL_001`) — 화면은 검색 주소의 조건 가운데 이 다섯만 넘긴다.
|
||||
- 강조할 말이 있으면 `searchHighlight: { subject: Range[], attachments: { [첨부 id]: Range[] }(맞은 첨부만), textBody: Range[] }` 를 싣고, `subject` · 첨부 이름 · `textBody` 를 NFC 로 싣는다. 없으면 `null`.
|
||||
- `htmlBody` 는 **정화한 뒤** 맞은 글 조각에 속성 없는 `<mark>` 를 넣는다 — 태그를 건너는 말 · 속성 · 주석 · `style` 안은 건드리지 않고, 한 통에 1,000개까지.
|
||||
- 강조할 말: `q` 는 제목 · 본문 · 첨부 이름에, `subject` 는 제목에, `body` 는 본문에. `from` · `to` 는 강조하지 않는다. 화면은 `<mark>` 를 노랑 `#FCE7A4` · 글자색 그대로로 그린다(디자인 README 14단계).
|
||||
- 강조할 말: `q` 는 제목 · 본문 · 첨부 이름에, `subject` 는 제목에, `body` 는 본문에. `from` · `to` 는 강조하지 않는다(섞인 검색인지 정하는 데만 쓴다). 본문에는 목록과 같이 본문을 찾은 두 글자 이상 낱말만이다. 화면은 `<mark>` 를 노랑 `#FCE7A4` · 글자색 그대로로 그린다(디자인 README 14단계).
|
||||
- **강조가 든 응답은 보이기만 한다** — 답장 · 전달의 원문 인용 · 인쇄 · 다른 화면의 메일 기억(목록 · 스토어)에는 검색 쿼리 없이 받은 응답을 쓴다(보내는 정화는 `mark` 를 지우지만, 인용 · 인쇄 모양이 검색에 따라 달라지지 않게).
|
||||
- 한 글자 낱말뿐이라 본문을 찾지 않았다는 표시는 응답에 없다 — 화면은 서버와 같은 낱말 나누기(짝 — backend `shared/utils/search-text.ts` · 표본 표 `backend/test/search-text-samples.ts`)로 알아보고 결과 머리에 한 줄 알린다.
|
||||
- 보낸 사람이 본문에 직접 쓴 `<mark>`(받은 메일 정화가 남긴다)도 같은 노랑으로 보여 검색 강조와 가르지 않는다(드물고 브라우저 기본도 노랑이다 — 알려진 한계).
|
||||
@@ -747,6 +754,7 @@ interface MailState {
|
||||
받기가 끝난 때부터 10초 안에 온 소식은 모아 한 번 받고, 받는 중에 온 소식은 끝난 뒤 한 번 더 받는다. [더 보기]로 늘린 대화는 저절로 다시 받지 않는다(붙잡힌 메일만 `GET /api/messages/{id}` 로 고친다).
|
||||
알려진 한계: 같은 메일함에서 한 통이 들어오며 한 통이 빠지면 통 수가 같아 그 소식으로는 다시 받지 않는다.
|
||||
- 펼친 줄은 검색 쿼리 없이 `GET /api/messages/{id}` 로 받고, 받은 뒤에도 펼쳐 있으며 안 읽은 메일이면 `PATCH` 로 읽음으로 바꾼다(읽기 화면과 같은 길). 그 메일이 그사이 지워졌으면(404) 대화를 다시 받아 그 줄을 뺀다.
|
||||
- 줄 차례: 받은 메일은 응답 차례 그대로, 붙잡힌 메일(`pendingSend` — 보내기 취소 · 예약)은 받은 줄 뒤에 `sendAt` 차례로 둔다. 줄의 미리 보기는 우리 원문 인용 머리 "> 원본 메일"부터 잘라 내고(남는 글이 없으면 제목), 펼친 줄은 첨부 목록 대신 첨부 수만 안내한다(2026-11-07).
|
||||
|
||||
#### `GET /api/conversations` — 대화로 묶어 보기
|
||||
|
||||
@@ -786,6 +794,7 @@ interface ConversationSummary {
|
||||
로그아웃 · 세션 만료는 짐작을 바꾸지 않고, 세션이 끝나면(제한된 세션 포함) 목록을 다시 부르지 않는다.
|
||||
- 쪽은 20줄 · 받은 커서 그대로. 켜고 끔 · 메일함 바꿈 · 다른 목록으로 가면 앞 목록의 응답을 버리고 그 요청을 끊는다(`AbortSignal` — `engine` 기준은 한 요청이 15초까지). 끊긴 요청은 알리지 않는다.
|
||||
- 새 메일 확인 · 실시간: 메일 목록 대신 불러온 줄 수만큼(최대 100줄) `silent` 로 처음 쪽부터 다시 받는다 — 요청 수는 메일 목록과 같다.
|
||||
- 다른 탭 · PC 에서 바꾼 설정: 메일 화면은 탭이 다시 보일 때 설정을 마지막으로 받은 지 60초가 지났으면 `GET /api/mail-settings` 를 `silent` 로 다시 받는다 — 저장하는 중이면 받지 않고, 받는 사이 고른 값이 있으면 받은 값을 버리고, 세션이 끝났으면 받지 않는다. 받은 `groupByConversation` 이 다르면 그 값으로 목록을 다시 연다(2026-11-07).
|
||||
- 줄에 하는 일: 할 일 메일 = `messageIds` − `pendingSendIds` − (마지막 메일이 붙잡힌 메일이면) 그 메일. 100통씩 나눠 보낸다.
|
||||
- 화면을 먼저 바꾸는 것은 줄의 메일을 모두 바꿨을 때만 줄 전체(읽음 · 중요 해제)다 — 일부만이면 그대로 두고 다음 맞추기가 서버 값으로 고친다. 중요 해제는 붙잡힌 메일까지 바꿨을 때만이다(붙잡힌 메일의 중요 표시는 남는다).
|
||||
- 줄의 별: 중요 표시가 하나라도 있으면 모두 풀고, 없으면 할 일 메일을 모두 표시한다. 붙잡힌 메일이 든 줄을 풀면 줄을 미리 풀지 않고 묶은 목록을 한 번 다시 받는다 — 그래도 중요 표시가 남으면 알린다.
|
||||
@@ -1259,7 +1268,7 @@ interface PendingSend {
|
||||
- **로그**: 취소 · 예약은 log(메일 id · 보낼 시각만) — 받는 사람 · 제목은 남기지 않는다.
|
||||
- **클라이언트**: [보내기]를 누르면 지금처럼 쓰기 화면을 닫고 떠난다. 알림 "메일을 보냈습니다." + [보내기 취소] + 남은 시간 막대 — `pendingSend.sendAt` 에 닫는다(마우스를 올려도 멈추지 않는다).
|
||||
일부 거절의 실패 알림에도 [보내기 취소]를 둔다. [보내기 취소] → `DELETE` → 쓰기 화면을 `?draft=<draftId>` 로 다시 연다(새 키) + 알림 "보내기를 취소했습니다. 쓰던 메일로 돌아왔습니다.".
|
||||
`MAIL_014` 는 인터셉터 알림. 예약은 알림 "‘시각’ 에 보냅니다." + [예약됨 보기]. 세션이 바뀌면 알림을 닫는다 — 메일은 그대로 보내진다.
|
||||
`MAIL_014` 는 인터셉터 알림. 예약은 알림 "‘시각’에 보냅니다." + [예약됨 보기]. 세션이 바뀌면 알림을 닫는다 — 메일은 그대로 보내진다.
|
||||
|
||||
### 외부 이미지 보기 (`images=show` · `/api/mail-images`) — 9단계
|
||||
|
||||
@@ -1357,6 +1366,7 @@ interface MailSettings {
|
||||
서버마다 60초 기억하고, 읽지 못하면(DB 장애) 처음 값(10초 · `ask`)으로 보고 warn 을 남긴다(구현하며 정함).
|
||||
- 화면(설정 › 메일 › 보내기 · 읽기)은 고르는 대로 `PATCH` 하고 "저장했습니다"를 보인다(저장 단추 없음).
|
||||
칸마다 저장은 한 번에 하나다 — 끝났을 때 마지막으로 고른 값이 서버의 답과 다르면 그 값을 다시 보낸다(늦게 온 앞 응답 때문에 화면과 서버가 갈리지 않게 — 2026-10-29 리뷰).
|
||||
메일 화면은 탭이 다시 보일 때 마지막으로 받은 지 60초가 지났으면 조용히 다시 받는다(14단계 — 위 "대화 — 14단계"의 묶어 보기 클라이언트 줄).
|
||||
"저장했습니다"는 기다리던 저장이 모두 끝나고 마지막이 성공했을 때 보인다. 끝내 실패하면 서버가 마지막으로 확인해 준 값으로 되돌리고 인터셉터가 알린다.
|
||||
|
||||
### 이 단계에서 하지 않는 것
|
||||
|
||||
Reference in New Issue
Block a user