[{"data":1,"prerenderedAt":61644},["ShallowReactive",2],{"docs:all":3},[4,4263,7648,10625,11487,12012,12425,12654,13214,14607,16824,19151,20047,24455,26849,30085,39602,43622,44497,45544,48763,51091,52334,54635,55784,57331],{"id":5,"title":6,"body":7,"description":4208,"extension":4257,"meta":4258,"navigation":4259,"path":34,"seo":4260,"stem":4261,"__hash__":4262},"docs\u002FBOARD.md","맑은노티 현황판 (정본)",{"type":8,"value":9,"toc":4207},"minimark",[10,14,72,75,80,100,230,232,236,244,251,256,345,349,445,449,529,533,609,613,725,727,734,741,745,857,861,919,923,962,966,1100,1104,1219,1223,1300,1302,1309,1316,1320,1397,1401,1611,1615,1779,1781,1788,1795,1854,1856,1863,1870,1874,2029,2033,2365,2369,2487,2491,2798,2802,3224,3228,3546,3550,3805,3807,3811,3824,3828,3838,3950,3954,4075,4079,4184,4186,4190],[11,12,6],"h1",{"id":13},"맑은노티-현황판-정본",[15,16,17],"blockquote",{},[18,19,20,24,25,35,36,39,40,43,44,47,48,51,52,55,56,59,60,63,64,67,68,71],"p",{},[21,22,23],"strong",{},"목적",": 현황판(",[26,27,31],"a",{"href":28,"rel":29},"https:\u002F\u002Fmalgn-noti-mng.pages.dev\u002Fboard",[30],"nofollow",[32,33,34],"code",{},"\u002Fboard",")이 표시하는 ",[21,37,38],{},"모든 내용","(전체 진행률·단계별 진행률·전 단계 세부 작업)을 담는 정본 문서. ",[32,41,42],{},"doc\u002FWBS.md","와 동일하게 현황판 전체를 반영한다.\n",[21,45,46],{},"데이터 출처",": ",[32,49,50],{},"malgn-noti-api"," ",[32,53,54],{},"GET \u002Fwbs"," (R2 ",[32,57,58],{},"wbs\u002Fwbs.json","). 현황판과 동일 소스.\n",[21,61,62],{},"현황판 데이터 기준",": 2026-06-04 · ",[21,65,66],{},"문서 현행화",": 2026-06-05\n",[21,69,70],{},"상태 범례",": ✅ 완료 · 🟡 진행 중 · ⚪ 대기 · ⛔ 보류",[73,74],"hr",{},[76,77,79],"h2",{"id":78},"진행률-스냅샷","진행률 스냅샷",[81,82,83,94],"ul",{},[84,85,86,89,90,93],"li",{},[21,87,88],{},"전체 진행률(가중평균): 47.5%"," — ",[32,91,92],{},"0.10×55 + 0.15×55 + 0.20×35 + 0.10×20 + 0.45×55"," \u002F 100",[84,95,96,99],{},[21,97,98],{},"작업 합계: 155건"," — ✅ 63 · 🟡 25 · ⚪ 67",[101,102,103,125],"table",{},[104,105,106],"thead",{},[107,108,109,113,116,119,122],"tr",{},[110,111,112],"th",{},"단계",[110,114,115],{},"가중치",[110,117,118],{},"진행률",[110,120,121],{},"작업 수",[110,123,124],{},"요약",[126,127,128,150,170,191,210],"tbody",{},[107,129,130,136,139,144,147],{},[131,132,133],"td",{},[21,134,135],{},"Step 1 · 프로젝트 준비",[131,137,138],{},"10%",[131,140,141],{},[21,142,143],{},"55%",[131,145,146],{},"18건",[131,148,149],{},"R&R · 사업 기획 · 계약서 초안 · 커뮤니케이션 · 환경 셋팅",[107,151,152,157,160,164,167],{},[131,153,154],{},[21,155,156],{},"Step 2 · 주요 서비스 정책 이슈 정리",[131,158,159],{},"15%",[131,161,162],{},[21,163,143],{},[131,165,166],{},"22건",[131,168,169],{},"프로토타입 · 회원\u002F결제\u002F계약 · 메시지 채널 · 캠페인 · 주소록 정책",[107,171,172,177,180,185,188],{},[131,173,174],{},[21,175,176],{},"Step 3 · 서비스 기획 (화면설계)",[131,178,179],{},"20%",[131,181,182],{},[21,183,184],{},"35%",[131,186,187],{},"21건",[131,189,190],{},"Front 프로토타입 대체 + BackOffice 1·2차 화면 명세",[107,192,193,198,200,204,207],{},[131,194,195],{},[21,196,197],{},"Step 4 · 디자인 \u002F 퍼블리싱",[131,199,138],{},[131,201,202],{},[21,203,179],{},[131,205,206],{},"2건",[131,208,209],{},"디자인 스타일 가이드 + 퍼블리싱 MD (개발 측 DESIGN.md + \u002Fguide 카탈로그로 대체 운영 중)",[107,211,212,217,220,224,227],{},[131,213,214],{},[21,215,216],{},"Step 5 · 서비스 개발",[131,218,219],{},"45%",[131,221,222],{},[21,223,143],{},[131,225,226],{},"92건",[131,228,229],{},"6\u002F4 §1~§5 + NHN OAuth 어댑터·Email 활성화·이메일 변경 라우트: UI 거의 완료 · API 약 72%(13 done) · 화면↔API 연동 약 40%(10 done) · 관리자단 핸드오프 17 페이지(화면만 ✅, API 연동 후속) · 통합·배포 Hyperdrive Tunnel + Email real",[73,231],{},[76,233,235],{"id":234},"단계별-상세","단계별 상세",[237,238,240,241],"h3",{"id":239},"step-1-프로젝트-준비-step-1","🎯 Step 1 · 프로젝트 준비  ",[32,242,243],{},"step-1",[81,245,246,249],{},[84,247,248],{},"비중 10% · 진행 55% · 18건",[84,250,149],{},[252,253,255],"h4",{"id":254},"rr-사업-기획-3","R&R · 사업 기획 (3)",[101,257,258,280],{},[104,259,260],{},[107,261,262,265,268,271,274,277],{},[110,263,264],{},"ID",[110,266,267],{},"작업",[110,269,270],{},"상태",[110,272,273],{},"담당",[110,275,276],{},"목표",[110,278,279],{},"완료",[126,281,282,304,325],{},[107,283,284,287,293,296,299,302],{},[131,285,286],{},"1-1-1",[131,288,289,292],{},[21,290,291],{},"작업 R&R 분배"," — 메모 확인",[131,294,295],{},"✅ 완료",[131,297,298],{},"김덕조",[131,300,301],{},"2026.05.08",[131,303,301],{},[107,305,306,309,315,317,320,323],{},[131,307,308],{},"1-1-2",[131,310,311,314],{},[21,312,313],{},"경쟁 서비스 가격 분석"," — 경쟁사 단가표",[131,316,295],{},[131,318,319],{},"컨설팅팀",[131,321,322],{},"—",[131,324,322],{},[107,326,327,330,336,339,341,343],{},[131,328,329],{},"1-1-3",[131,331,332,335],{},[21,333,334],{},"당사 원가 확인 및 가격 정책 결정 (단가)"," — 기본 단가 책정(할인률 정책) · MMS 이미지 3장까지 비용설계 · 단가표(기획안)",[131,337,338],{},"🟡 진행 중",[131,340,319],{},[131,342,322],{},[131,344,322],{},[252,346,348],{"id":347},"사업-준비-4","사업 준비 (4)",[101,350,351,367],{},[104,352,353],{},[107,354,355,357,359,361,363,365],{},[110,356,264],{},[110,358,267],{},[110,360,270],{},[110,362,273],{},[110,364,276],{},[110,366,279],{},[126,368,369,389,408,426],{},[107,370,371,374,380,383,385,387],{},[131,372,373],{},"1-2-1",[131,375,376,379],{},[21,377,378],{},"특수 유형의 메시징 사업자 신청"," — 프로젝트 추진 중간평가 이후",[131,381,382],{},"⚪ 대기",[131,384,319],{},[131,386,322],{},[131,388,322],{},[107,390,391,394,400,402,404,406],{},[131,392,393],{},"1-2-2",[131,395,396,399],{},[21,397,398],{},"통신판매사업자 신청"," — 중간평가 이후",[131,401,382],{},[131,403,319],{},[131,405,322],{},[131,407,322],{},[107,409,410,413,418,420,422,424],{},[131,411,412],{},"1-2-3",[131,414,415,399],{},[21,416,417],{},"자본 Up 방안",[131,419,382],{},[131,421,322],{},[131,423,322],{},[131,425,322],{},[107,427,428,431,437,439,441,443],{},[131,429,430],{},"1-2-4",[131,432,433,436],{},[21,434,435],{},"관련 계약서 작성"," — 가입신청서·이용약관·개인정보처리방침·요금신고내역 초안 \u002F 1차 검토 완료 → 2차 수정본 \u002F 전무님 검토 필요",[131,438,338],{},[131,440,319],{},[131,442,322],{},[131,444,322],{},[252,446,448],{"id":447},"커뮤니케이션-3","커뮤니케이션 (3)",[101,450,451,467],{},[104,452,453],{},[107,454,455,457,459,461,463,465],{},[110,456,264],{},[110,458,267],{},[110,460,270],{},[110,462,273],{},[110,464,276],{},[110,466,279],{},[126,468,469,489,510],{},[107,470,471,474,480,482,485,487],{},[131,472,473],{},"1-3-1",[131,475,476,479],{},[21,477,478],{},"그룹 텔레그램 개설"," — 맑은메시지 TF",[131,481,295],{},[131,483,484],{},"김도형",[131,486,301],{},[131,488,301],{},[107,490,491,494,500,502,505,508],{},[131,492,493],{},"1-3-2",[131,495,496,499],{},[21,497,498],{},"화면설계 · 피그마 정본"," — 피그마",[131,501,295],{},[131,503,504],{},"김경은",[131,506,507],{},"2026.05.11",[131,509,507],{},[107,511,512,515,521,523,525,527],{},[131,513,514],{},"1-3-3",[131,516,517,520],{},[21,518,519],{},"문서 공유 폴더"," — 프로젝트 폴더",[131,522,382],{},[131,524,298],{},[131,526,322],{},[131,528,322],{},[252,530,532],{"id":531},"서비스-메타-3","서비스 메타 (3)",[101,534,535,551],{},[104,536,537],{},[107,538,539,541,543,545,547,549],{},[110,540,264],{},[110,542,267],{},[110,544,270],{},[110,546,273],{},[110,548,276],{},[110,550,279],{},[126,552,553,571,589],{},[107,554,555,558,563,565,567,569],{},[131,556,557],{},"1-4-1",[131,559,560],{},[21,561,562],{},"서비스 도메인 결정",[131,564,382],{},[131,566,298],{},[131,568,322],{},[131,570,322],{},[107,572,573,576,581,583,585,587],{},[131,574,575],{},"1-4-2",[131,577,578],{},[21,579,580],{},"브랜딩 (맑은메시지 외 아이데이션)",[131,582,382],{},[131,584,298],{},[131,586,322],{},[131,588,322],{},[107,590,591,594,600,602,605,607],{},[131,592,593],{},"1-4-3",[131,595,596,599],{},[21,597,598],{},"마케팅 기획"," — 기존 고객군 & 메시징 only 고객군",[131,601,382],{},[131,603,604],{},"안병훈",[131,606,322],{},[131,608,322],{},[252,610,612],{"id":611},"환경-셋팅-5","환경 셋팅 (5)",[101,614,615,631],{},[104,616,617],{},[107,618,619,621,623,625,627,629],{},[110,620,264],{},[110,622,267],{},[110,624,270],{},[110,626,273],{},[110,628,276],{},[110,630,279],{},[126,632,633,652,671,689,707],{},[107,634,635,638,644,646,648,650],{},[131,636,637],{},"1-5-1",[131,639,640,643],{},[21,641,642],{},"커뮤니케이션 문서 폴더 운영"," — 폴더 셋팅",[131,645,295],{},[131,647,298],{},[131,649,301],{},[131,651,301],{},[107,653,654,657,663,665,667,669],{},[131,655,656],{},"1-5-2",[131,658,659,662],{},[21,660,661],{},"GitHub(malgnsoft) · Cloudflare 셋팅"," — 3 레포 + Pages 2 + Workers 1",[131,664,295],{},[131,666,484],{},[131,668,507],{},[131,670,507],{},[107,672,673,676,681,683,685,687],{},[131,674,675],{},"1-5-3",[131,677,678],{},[21,679,680],{},"사용자단",[131,682,295],{},[131,684,484],{},[131,686,507],{},[131,688,507],{},[107,690,691,694,699,701,703,705],{},[131,692,693],{},"1-5-4",[131,695,696],{},[21,697,698],{},"관리자단",[131,700,295],{},[131,702,484],{},[131,704,507],{},[131,706,507],{},[107,708,709,712,717,719,721,723],{},[131,710,711],{},"1-5-5",[131,713,714],{},[21,715,716],{},"API 서버",[131,718,295],{},[131,720,484],{},[131,722,507],{},[131,724,507],{},[73,726],{},[237,728,730,731],{"id":729},"step-2-주요-서비스-정책-이슈-정리-step-2","📐 Step 2 · 주요 서비스 정책 이슈 정리  ",[32,732,733],{},"step-2",[81,735,736,739],{},[84,737,738],{},"비중 15% · 진행 55% · 22건",[84,740,169],{},[252,742,744],{"id":743},"프로토타입-문서-5","프로토타입 · 문서 (5)",[101,746,747,763],{},[104,748,749],{},[107,750,751,753,755,757,759,761],{},[110,752,264],{},[110,754,267],{},[110,756,270],{},[110,758,273],{},[110,760,276],{},[110,762,279],{},[126,764,765,784,802,820,839],{},[107,766,767,770,776,778,780,782],{},[131,768,769],{},"2-1-1",[131,771,772,775],{},[21,773,774],{},"Front 프로토타입"," — IA 정본(263 페이지)",[131,777,338],{},[131,779,298],{},[131,781,322],{},[131,783,322],{},[107,785,786,789,794,796,798,800],{},[131,787,788],{},"2-1-2",[131,790,791],{},[21,792,793],{},"Front 메뉴 및 스펙",[131,795,382],{},[131,797,322],{},[131,799,322],{},[131,801,322],{},[107,803,804,807,812,814,816,818],{},[131,805,806],{},"2-1-3",[131,808,809],{},[21,810,811],{},"Front 페이지 리스트",[131,813,382],{},[131,815,298],{},[131,817,322],{},[131,819,322],{},[107,821,822,825,831,833,835,837],{},[131,823,824],{},"2-1-4",[131,826,827,830],{},[21,828,829],{},"BackOffice 프로토타입"," — 만들지 말지 결정",[131,832,382],{},[131,834,504],{},[131,836,322],{},[131,838,322],{},[107,840,841,844,849,851,853,855],{},[131,842,843],{},"2-1-5",[131,845,846],{},[21,847,848],{},"BackOffice 메뉴 및 스펙",[131,850,382],{},[131,852,322],{},[131,854,322],{},[131,856,322],{},[252,858,860],{"id":859},"주요-서비스-참조-2","주요 서비스 참조 (2)",[101,862,863,879],{},[104,864,865],{},[107,866,867,869,871,873,875,877],{},[110,868,264],{},[110,870,267],{},[110,872,270],{},[110,874,273],{},[110,876,276],{},[110,878,279],{},[126,880,881,900],{},[107,882,883,886,892,894,896,898],{},[131,884,885],{},"2-2-1",[131,887,888,891],{},[21,889,890],{},"NHN Cloud Notification 서비스"," — 통합 대상",[131,893,382],{},[131,895,322],{},[131,897,322],{},[131,899,322],{},[107,901,902,905,911,913,915,917],{},[131,903,904],{},"2-2-2",[131,906,907,910],{},[21,908,909],{},"비즈 뿌리오 서비스"," — 참조",[131,912,382],{},[131,914,322],{},[131,916,322],{},[131,918,322],{},[252,920,922],{"id":921},"캠페인-1","캠페인 (1)",[101,924,925,941],{},[104,926,927],{},[107,928,929,931,933,935,937,939],{},[110,930,264],{},[110,932,267],{},[110,934,270],{},[110,936,273],{},[110,938,276],{},[110,940,279],{},[126,942,943],{},[107,944,945,948,954,956,958,960],{},[131,946,947],{},"2-3-1",[131,949,950,953],{},[21,951,952],{},"벤치마킹 조사"," — 솔라피(CRM 결합) + 개별 문자 발송",[131,955,382],{},[131,957,604],{},[131,959,322],{},[131,961,322],{},[252,963,965],{"id":964},"회원결제계약-6","회원·결제·계약 (6)",[101,967,968,984],{},[104,969,970],{},[107,971,972,974,976,978,980,982],{},[110,973,264],{},[110,975,267],{},[110,977,270],{},[110,979,273],{},[110,981,276],{},[110,983,279],{},[126,985,986,1006,1025,1044,1062,1081],{},[107,987,988,991,997,999,1001,1004],{},[131,989,990],{},"2-4-1",[131,992,993,996],{},[21,994,995],{},"회원가입·판매방식 — 후불 정산 \u002F 개인 회원 추가"," — 법인·개인사업자·개인 3유형 \u002F 카드 충전식 vs 후불 결제 \u002F 계약관리에 지급이행보증보험 첨부",[131,998,338],{},[131,1000,298],{},[131,1002,1003],{},"2026.05.12",[131,1005,1003],{},[107,1007,1008,1011,1017,1019,1021,1023],{},[131,1009,1010],{},"2-4-2",[131,1012,1013,1016],{},[21,1014,1015],{},"회원 구조 — 멀티 계정 (주·보조)"," — 법인·개인사업자만 멀티계정 탭 노출, 개인은 미노출",[131,1018,338],{},[131,1020,298],{},[131,1022,1003],{},[131,1024,1003],{},[107,1026,1027,1030,1036,1038,1040,1042],{},[131,1028,1029],{},"2-4-3",[131,1031,1032,1035],{},[21,1033,1034],{},"결제 — 자동충전"," — 향후 재논의",[131,1037,382],{},[131,1039,298],{},[131,1041,322],{},[131,1043,322],{},[107,1045,1046,1049,1054,1056,1058,1060],{},[131,1047,1048],{},"2-4-4",[131,1050,1051],{},[21,1052,1053],{},"결제내역 — 결제 페이지 추가",[131,1055,382],{},[131,1057,298],{},[131,1059,322],{},[131,1061,322],{},[107,1063,1064,1067,1073,1075,1077,1079],{},[131,1065,1066],{},"2-4-5",[131,1068,1069,1072],{},[21,1070,1071],{},"결제 — 후불 결제 고려"," — 내부로직 -크레딧 \u002F 후불시 사용 크레딧 \u002F 다음 결제일",[131,1074,382],{},[131,1076,298],{},[131,1078,322],{},[131,1080,322],{},[107,1082,1083,1086,1092,1094,1096,1098],{},[131,1084,1085],{},"2-4-6",[131,1087,1088,1091],{},[21,1089,1090],{},"계약관리 정책"," — 법인·개인사업자 온라인 계약 + BackOffice 승인 \u002F 개인은 즉시 사용",[131,1093,382],{},[131,1095,322],{},[131,1097,322],{},[131,1099,322],{},[252,1101,1103],{"id":1102},"메시지-채널-정책-5","메시지 채널 정책 (5)",[101,1105,1106,1122],{},[104,1107,1108],{},[107,1109,1110,1112,1114,1116,1118,1120],{},[110,1111,264],{},[110,1113,267],{},[110,1115,270],{},[110,1117,273],{},[110,1119,276],{},[110,1121,279],{},[126,1123,1124,1143,1162,1181,1200],{},[107,1125,1126,1129,1135,1137,1139,1141],{},[131,1127,1128],{},"2-5-1",[131,1130,1131,1134],{},[21,1132,1133],{},"AI 문장 다듬기 기능"," — 발송창(알림톡 제외) AI검토 \u002F 문자·RCS·이메일 적용",[131,1136,338],{},[131,1138,298],{},[131,1140,1003],{},[131,1142,1003],{},[107,1144,1145,1148,1154,1156,1158,1160],{},[131,1146,1147],{},"2-5-2",[131,1149,1150,1153],{},[21,1151,1152],{},"광고용 선택 시 수신거부 전화번호 이슈"," — 맨 마지막에 입력창 분리 \u002F 재확인 후 설계",[131,1155,382],{},[131,1157,298],{},[131,1159,322],{},[131,1161,322],{},[107,1163,1164,1167,1173,1175,1177,1179],{},[131,1165,1166],{},"2-5-3",[131,1168,1169,1172],{},[21,1170,1171],{},"순차발송"," — 알림톡 미수신시 SMS\u002FLMS 폴백 \u002F 복합(플로우) Default 알림톡→SMS→이메일",[131,1174,338],{},[131,1176,298],{},[131,1178,1003],{},[131,1180,1003],{},[107,1182,1183,1186,1192,1194,1196,1198],{},[131,1184,1185],{},"2-5-4",[131,1187,1188,1191],{},[21,1189,1190],{},"랜딩페이지 만들기 추가"," — 기본형·확장형 화면 추가",[131,1193,338],{},[131,1195,298],{},[131,1197,1003],{},[131,1199,1003],{},[107,1201,1202,1205,1211,1213,1215,1217],{},[131,1203,1204],{},"2-5-5",[131,1206,1207,1210],{},[21,1208,1209],{},"발신번호 관리에 휴대폰번호 추가"," — 유선(증명서) + 휴대폰(본인인증 PASS)",[131,1212,338],{},[131,1214,298],{},[131,1216,1003],{},[131,1218,1003],{},[252,1220,1222],{"id":1221},"캠페인-주소록-브랜드-3","캠페인 · 주소록 · 브랜드 (3)",[101,1224,1225,1241],{},[104,1226,1227],{},[107,1228,1229,1231,1233,1235,1237,1239],{},[110,1230,264],{},[110,1232,267],{},[110,1234,270],{},[110,1236,273],{},[110,1238,276],{},[110,1240,279],{},[126,1242,1243,1262,1281],{},[107,1244,1245,1248,1254,1256,1258,1260],{},[131,1246,1247],{},"2-6-1",[131,1249,1250,1253],{},[21,1251,1252],{},"캠페인 관리 — AB 테스트 기능"," — 캠페인 관리 기능 최종 정의 후",[131,1255,382],{},[131,1257,298],{},[131,1259,322],{},[131,1261,322],{},[107,1263,1264,1267,1273,1275,1277,1279],{},[131,1265,1266],{},"2-6-2",[131,1268,1269,1272],{},[21,1270,1271],{},"주소록 — CRM 기능 확대"," — 단건 발송 레이어 팝업 \u002F 연락처·그룹 채널 바로가기 \u002F CRM 예제 화면 수집",[131,1274,338],{},[131,1276,298],{},[131,1278,1003],{},[131,1280,1003],{},[107,1282,1283,1286,1291,1293,1296,1298],{},[131,1284,1285],{},"2-6-3",[131,1287,1288],{},[21,1289,1290],{},"브랜드 네임",[131,1292,382],{},[131,1294,1295],{},"안병훈 외 전체",[131,1297,322],{},[131,1299,322],{},[73,1301],{},[237,1303,1305,1306],{"id":1304},"step-3-서비스-기획-화면설계-step-3","📋 Step 3 · 서비스 기획 (화면설계)  ",[32,1307,1308],{},"step-3",[81,1310,1311,1314],{},[84,1312,1313],{},"비중 20% · 진행 35% · 21건",[84,1315,190],{},[252,1317,1319],{"id":1318},"front-3","Front (3)",[101,1321,1322,1338],{},[104,1323,1324],{},[107,1325,1326,1328,1330,1332,1334,1336],{},[110,1327,264],{},[110,1329,267],{},[110,1331,270],{},[110,1333,273],{},[110,1335,276],{},[110,1337,279],{},[126,1339,1340,1359,1378],{},[107,1341,1342,1345,1350,1352,1355,1357],{},[131,1343,1344],{},"3-1-1",[131,1346,1347],{},[21,1348,1349],{},"프로토타입으로 대체",[131,1351,338],{},[131,1353,1354],{},"김덕조·김경은",[131,1356,322],{},[131,1358,322],{},[107,1360,1361,1364,1369,1371,1374,1376],{},[131,1362,1363],{},"3-1-2",[131,1365,1366],{},[21,1367,1368],{},"서비스 메뉴 콘텐츠",[131,1370,382],{},[131,1372,1373],{},"컨설팅팀·김경은",[131,1375,322],{},[131,1377,322],{},[107,1379,1380,1383,1389,1391,1393,1395],{},[131,1381,1382],{},"3-1-3",[131,1384,1385,1388],{},[21,1386,1387],{},"운영가이드"," — 사용자단 \u002Fhelp 라이브 — 컨텐츠 보강 필요",[131,1390,382],{},[131,1392,1354],{},[131,1394,322],{},[131,1396,322],{},[252,1398,1400],{"id":1399},"backoffice-1차-10","BackOffice 1차 (10)",[101,1402,1403,1419],{},[104,1404,1405],{},[107,1406,1407,1409,1411,1413,1415,1417],{},[110,1408,264],{},[110,1410,267],{},[110,1412,270],{},[110,1414,273],{},[110,1416,276],{},[110,1418,279],{},[126,1420,1421,1440,1459,1478,1497,1516,1536,1554,1573,1593],{},[107,1422,1423,1426,1431,1433,1435,1438],{},[131,1424,1425],{},"3-2-1",[131,1427,1428],{},[21,1429,1430],{},"공통 · 로그인 · 계정 관리",[131,1432,338],{},[131,1434,504],{},[131,1436,1437],{},"2026.05.22",[131,1439,322],{},[107,1441,1442,1445,1451,1453,1455,1457],{},[131,1443,1444],{},"3-2-2",[131,1446,1447,1450],{},[21,1448,1449],{},"회원 · 고객사 관리"," — 회원 발송 이력 \u002F 결제 상세 \u002F 환불신청 제외",[131,1452,338],{},[131,1454,504],{},[131,1456,1437],{},[131,1458,322],{},[107,1460,1461,1464,1470,1472,1474,1476],{},[131,1462,1463],{},"3-2-3",[131,1465,1466,1469],{},[21,1467,1468],{},"시스템 관리"," — 운영자 계정 \u002F RBAC \u002F 감사 로그",[131,1471,338],{},[131,1473,504],{},[131,1475,1437],{},[131,1477,322],{},[107,1479,1480,1483,1488,1490,1492,1495],{},[131,1481,1482],{},"3-2-4",[131,1484,1485],{},[21,1486,1487],{},"요금 · 단가 관리",[131,1489,338],{},[131,1491,504],{},[131,1493,1494],{},"2026.05.29",[131,1496,322],{},[107,1498,1499,1502,1508,1510,1512,1514],{},[131,1500,1501],{},"3-2-5",[131,1503,1504,1507],{},[21,1505,1506],{},"고객지원"," — 운영 가이드 관리 제외",[131,1509,338],{},[131,1511,504],{},[131,1513,1494],{},[131,1515,322],{},[107,1517,1518,1521,1527,1529,1531,1534],{},[131,1519,1520],{},"3-2-6",[131,1522,1523,1526],{},[21,1524,1525],{},"발송 운영 모니터링"," — 캠페인 제외",[131,1528,382],{},[131,1530,504],{},[131,1532,1533],{},"2026.06.12",[131,1535,322],{},[107,1537,1538,1541,1546,1548,1550,1552],{},[131,1539,1540],{},"3-2-7",[131,1542,1543],{},[21,1544,1545],{},"발신 정보 검수",[131,1547,382],{},[131,1549,504],{},[131,1551,1533],{},[131,1553,322],{},[107,1555,1556,1559,1564,1566,1568,1571],{},[131,1557,1558],{},"3-2-8",[131,1560,1561],{},[21,1562,1563],{},"결제 · 크레딧 관리 + 고객사 상세 결제 탭",[131,1565,382],{},[131,1567,504],{},[131,1569,1570],{},"2026.06.19",[131,1572,322],{},[107,1574,1575,1578,1584,1586,1588,1591],{},[131,1576,1577],{},"3-2-9",[131,1579,1580,1583],{},[21,1581,1582],{},"템플릿 검수 · 관리"," — 샘플·AI 템플릿 정책 제외",[131,1585,382],{},[131,1587,504],{},[131,1589,1590],{},"2026.06.24",[131,1592,322],{},[107,1594,1595,1598,1603,1605,1607,1609],{},[131,1596,1597],{},"3-2-10",[131,1599,1600],{},[21,1601,1602],{},"수신거부 (운영)",[131,1604,382],{},[131,1606,504],{},[131,1608,1590],{},[131,1610,322],{},[252,1612,1614],{"id":1613},"backoffice-2차-8","BackOffice 2차 (8)",[101,1616,1617,1633],{},[104,1618,1619],{},[107,1620,1621,1623,1625,1627,1629,1631],{},[110,1622,264],{},[110,1624,267],{},[110,1626,270],{},[110,1628,273],{},[110,1630,276],{},[110,1632,279],{},[126,1634,1635,1653,1671,1689,1707,1725,1744,1761],{},[107,1636,1637,1640,1645,1647,1649,1651],{},[131,1638,1639],{},"3-3-1",[131,1641,1642],{},[21,1643,1644],{},"통계 · 리포트",[131,1646,382],{},[131,1648,504],{},[131,1650,322],{},[131,1652,322],{},[107,1654,1655,1658,1663,1665,1667,1669],{},[131,1656,1657],{},"3-3-2",[131,1659,1660],{},[21,1661,1662],{},"대시보드",[131,1664,382],{},[131,1666,504],{},[131,1668,322],{},[131,1670,322],{},[107,1672,1673,1676,1681,1683,1685,1687],{},[131,1674,1675],{},"3-3-3",[131,1677,1678],{},[21,1679,1680],{},"템플릿 검수 · 관리 (AI 템플릿 정책)",[131,1682,382],{},[131,1684,504],{},[131,1686,322],{},[131,1688,322],{},[107,1690,1691,1694,1699,1701,1703,1705],{},[131,1692,1693],{},"3-3-4",[131,1695,1696],{},[21,1697,1698],{},"발송 운영 모니터링 (캠페인)",[131,1700,382],{},[131,1702,504],{},[131,1704,322],{},[131,1706,322],{},[107,1708,1709,1712,1717,1719,1721,1723],{},[131,1710,1711],{},"3-3-5",[131,1713,1714,1716],{},[21,1715,1506],{}," — 운영 가이드 관리",[131,1718,382],{},[131,1720,504],{},[131,1722,322],{},[131,1724,322],{},[107,1726,1727,1730,1736,1738,1740,1742],{},[131,1728,1729],{},"3-3-6",[131,1731,1732,1735],{},[21,1733,1734],{},"콘텐츠 · 사이트 관리"," — 시스템 설정 \u002F 점검 모드 \u002F 외부 연동",[131,1737,382],{},[131,1739,504],{},[131,1741,322],{},[131,1743,322],{},[107,1745,1746,1749,1753,1755,1757,1759],{},[131,1747,1748],{},"3-3-7",[131,1750,1751],{},[21,1752,1468],{},[131,1754,382],{},[131,1756,504],{},[131,1758,322],{},[131,1760,322],{},[107,1762,1763,1766,1771,1773,1775,1777],{},[131,1764,1765],{},"3-3-8",[131,1767,1768],{},[21,1769,1770],{},"API 관리",[131,1772,382],{},[131,1774,504],{},[131,1776,322],{},[131,1778,322],{},[73,1780],{},[237,1782,1784,1785],{"id":1783},"step-4-디자인-퍼블리싱-step-4","🎨 Step 4 · 디자인 \u002F 퍼블리싱  ",[32,1786,1787],{},"step-4",[81,1789,1790,1793],{},[84,1791,1792],{},"비중 10% · 진행 20% · 2건",[84,1794,209],{},[101,1796,1797,1813],{},[104,1798,1799],{},[107,1800,1801,1803,1805,1807,1809,1811],{},[110,1802,264],{},[110,1804,267],{},[110,1806,270],{},[110,1808,273],{},[110,1810,276],{},[110,1812,279],{},[126,1814,1815,1835],{},[107,1816,1817,1820,1826,1828,1831,1833],{},[131,1818,1819],{},"4-1",[131,1821,1822,1825],{},[21,1823,1824],{},"디자인 스타일 가이드"," — (개발: doc\u002FDESIGN.md Relay-inspired v1.0 + \u002Fguide 카탈로그 운영). 디자인팀 정식 산출물은 별도 필요.",[131,1827,382],{},[131,1829,1830],{},"김양현",[131,1832,322],{},[131,1834,322],{},[107,1836,1837,1840,1846,1848,1850,1852],{},[131,1838,1839],{},"4-2",[131,1841,1842,1845],{},[21,1843,1844],{},"퍼블리싱 MD 파일"," — (개발: Nuxt 3 + Nuxt UI v3 + Tailwind v4로 직접 퍼블리싱 중)",[131,1847,382],{},[131,1849,1830],{},[131,1851,322],{},[131,1853,322],{},[73,1855],{},[237,1857,1859,1860],{"id":1858},"️-step-5-서비스-개발-step-5","🛠️ Step 5 · 서비스 개발  ",[32,1861,1862],{},"step-5",[81,1864,1865,1868],{},[84,1866,1867],{},"비중 45% · 진행 55% · 92건",[84,1869,229],{},[252,1871,1873],{"id":1872},"설계-및-준비-7","설계 및 준비 (7)",[101,1875,1876,1892],{},[104,1877,1878],{},[107,1879,1880,1882,1884,1886,1888,1890],{},[110,1881,264],{},[110,1883,267],{},[110,1885,270],{},[110,1887,273],{},[110,1889,276],{},[110,1891,279],{},[126,1893,1894,1914,1934,1954,1973,1992,2010],{},[107,1895,1896,1899,1905,1907,1909,1912],{},[131,1897,1898],{},"5-1-1",[131,1900,1901,1904],{},[21,1902,1903],{},"아키텍처 설계"," — STACK.md — 3 레포 책임 + Cloudflare\u002FAWS 혼합 + NHN 통합",[131,1906,295],{},[131,1908,484],{},[131,1910,1911],{},"2026.05.14",[131,1913,1911],{},[107,1915,1916,1919,1925,1927,1929,1932],{},[131,1917,1918],{},"5-1-2",[131,1920,1921,1924],{},[21,1922,1923],{},"데이터 모델링"," — 49 테이블 + Mermaid ERD 9종 + 확장성 전략(파티셔닝·Hot\u002FWarm\u002FCold·R2 오프로드)",[131,1926,295],{},[131,1928,484],{},[131,1930,1931],{},"2026.05.27",[131,1933,1931],{},[107,1935,1936,1939,1945,1947,1949,1952],{},[131,1937,1938],{},"5-1-3",[131,1940,1941,1944],{},[21,1942,1943],{},"사용자단 디자인 시스템"," — Relay-inspired v1.0 — ink 11단 + 그린 #00DC82 + Inter\u002FJetBrains Mono\u002FPretendard",[131,1946,295],{},[131,1948,484],{},[131,1950,1951],{},"2026.05.18",[131,1953,1951],{},[107,1955,1956,1959,1964,1966,1968,1971],{},[131,1957,1958],{},"5-1-4",[131,1960,1961],{},[21,1962,1963],{},"사용자단 디자인 가이드 (라이브 카탈로그)",[131,1965,295],{},[131,1967,484],{},[131,1969,1970],{},"2026.05.19",[131,1972,1970],{},[107,1974,1975,1978,1984,1986,1988,1990],{},[131,1976,1977],{},"5-1-5",[131,1979,1980,1983],{},[21,1981,1982],{},"관리자단 부트스트랩 + 셸 (LNB + TopBar)"," — Nuxt 3 + Nuxt UI v3 + LNB 256px·8그룹 + TopBar 64px",[131,1985,295],{},[131,1987,484],{},[131,1989,1931],{},[131,1991,1931],{},[107,1993,1994,1997,2002,2004,2006,2008],{},[131,1995,1996],{},"5-1-6",[131,1998,1999],{},[21,2000,2001],{},"관리자단 디자인 가이드",[131,2003,295],{},[131,2005,484],{},[131,2007,1931],{},[131,2009,1931],{},[107,2011,2012,2015,2021,2023,2025,2027],{},[131,2013,2014],{},"5-1-7",[131,2016,2017,2020],{},[21,2018,2019],{},"관리자단 페이지 기획 MD (33종)"," — P0 14 \u002F P1 13 \u002F P2 5 — 8 그룹",[131,2022,295],{},[131,2024,484],{},[131,2026,1931],{},[131,2028,1931],{},[252,2030,2032],{"id":2031},"api-서버-16","API 서버 (16)",[101,2034,2035,2051],{},[104,2036,2037],{},[107,2038,2039,2041,2043,2045,2047,2049],{},[110,2040,264],{},[110,2042,267],{},[110,2044,270],{},[110,2046,273],{},[110,2048,276],{},[110,2050,279],{},[126,2052,2053,2073,2092,2111,2130,2149,2168,2187,2206,2225,2245,2265,2285,2304,2326,2346],{},[107,2054,2055,2058,2064,2066,2068,2071],{},[131,2056,2057],{},"5-2-1",[131,2059,2060,2063],{},[21,2061,2062],{},"Hono on Workers 부트스트랩 + Hyperdrive(Aurora)"," — drizzle-orm\u002Fmysql2 + \u002Fhealth\u002Fdb + 배포 #1",[131,2065,295],{},[131,2067,484],{},[131,2069,2070],{},"2026.05.26",[131,2072,2070],{},[107,2074,2075,2078,2084,2086,2088,2090],{},[131,2076,2077],{},"5-2-2",[131,2079,2080,2083],{},[21,2081,2082],{},"DB 마이그레이션 — 49 테이블 + 파티션 5종"," — 0000_initial.sql 적용 (49 + 75 파티션)",[131,2085,295],{},[131,2087,484],{},[131,2089,2070],{},[131,2091,2070],{},[107,2093,2094,2097,2103,2105,2107,2109],{},[131,2095,2096],{},"5-2-3",[131,2098,2099,2102],{},[21,2100,2101],{},"기초 도메인 CRUD (14 도메인)"," — \u002Fme \u002Fcontacts \u002Fcontact-groups \u002Fsender-* 등 + errors\u002Fpagination\u002Fauth\u002FDrizzle",[131,2104,295],{},[131,2106,484],{},[131,2108,2070],{},[131,2110,2070],{},[107,2112,2113,2116,2122,2124,2126,2128],{},[131,2114,2115],{},"5-2-4",[131,2117,2118,2121],{},[21,2119,2120],{},"OpenAPI 문서 (Scalar UI)"," — paths 37 \u002F schemas 45+, 루트 \u002F → \u002Fdoc 302",[131,2123,295],{},[131,2125,484],{},[131,2127,1931],{},[131,2129,1931],{},[107,2131,2132,2135,2141,2143,2145,2147],{},[131,2133,2134],{},"5-2-5",[131,2136,2137,2140],{},[21,2138,2139],{},"인증 — signup\u002Flogin\u002FJWT\u002FPBKDF2"," — Phase 1·2·3 + JWT_SECRET secret",[131,2142,295],{},[131,2144,484],{},[131,2146,2070],{},[131,2148,2070],{},[107,2150,2151,2154,2160,2162,2164,2166],{},[131,2152,2153],{},"5-2-6",[131,2155,2156,2159],{},[21,2157,2158],{},"발송 producer — 5채널 (SMS·Email·Kakao·Push·RCS)"," — 발신정보 검증·옵트아웃·크레딧 hold·트랜잭션 + 채널 branching generic화",[131,2161,295],{},[131,2163,484],{},[131,2165,1931],{},[131,2167,1931],{},[107,2169,2170,2173,2179,2181,2183,2185],{},[131,2171,2172],{},"5-2-7",[131,2174,2175,2178],{},[21,2176,2177],{},"멱등성 — TB_IDEMPOTENCY + INSERT-then-conflict"," — 0001_idempotency.sql race-free",[131,2180,295],{},[131,2182,484],{},[131,2184,1931],{},[131,2186,1931],{},[107,2188,2189,2192,2198,2200,2202,2204],{},[131,2190,2191],{},"5-2-8",[131,2193,2194,2197],{},[21,2195,2196],{},"NHN 어댑터 — 5채널 (mock\u002Freal)"," — src\u002Fadapters\u002Fnhn\u002F{sms,email,kakao,push,rcs}.ts",[131,2199,295],{},[131,2201,484],{},[131,2203,1931],{},[131,2205,1931],{},[107,2207,2208,2211,2217,2219,2221,2223],{},[131,2209,2210],{},"5-2-9",[131,2212,2213,2216],{},[21,2214,2215],{},"Cloudflare Queues + Consumer Worker"," — malgn-noti-dispatch + dispatch_state 천이",[131,2218,295],{},[131,2220,484],{},[131,2222,1931],{},[131,2224,1931],{},[107,2226,2227,2230,2236,2238,2240,2243],{},[131,2228,2229],{},"5-2-10",[131,2231,2232,2235],{},[21,2233,2234],{},"NHN Webhook 핸들러 (SMS · RCS)"," — HMAC-SHA256 + dedup_key. Email\u002FKakao\u002FPush 미.",[131,2237,338],{},[131,2239,484],{},[131,2241,2242],{},"2026.06.09",[131,2244,322],{},[107,2246,2247,2250,2256,2258,2260,2263],{},[131,2248,2249],{},"5-2-11",[131,2251,2252,2255],{},[21,2253,2254],{},"Export 잡 (다운로드 요청)"," — TB_EXPORT_JOB ✅ DDL 적용 + \u002Fexport-jobs CRUD ✅ 라이브 검증 (POST 201, GET 200). 처리 worker + R2 미",[131,2257,338],{},[131,2259,484],{},[131,2261,2262],{},"2026.06.17",[131,2264,322],{},[107,2266,2267,2270,2276,2278,2280,2283],{},[131,2268,2269],{},"5-2-12",[131,2271,2272,2275],{},[21,2273,2274],{},"Flow 정의 (복합 발송)"," — TB_FLOW_DEFINITION\u002FRUN\u002FSTEP_RUN ✅ DDL 적용 (FK 6) + \u002Fflow-definitions CRUD ✅ 라이브 검증. 실행 엔진 미",[131,2277,338],{},[131,2279,484],{},[131,2281,2282],{},"2026.06.22",[131,2284,322],{},[107,2286,2287,2290,2295,2297,2299,2302],{},[131,2288,2289],{},"5-2-13",[131,2291,2292],{},[21,2293,2294],{},"캠페인 API (스케줄러·시뮬레이션·테스트)",[131,2296,382],{},[131,2298,484],{},[131,2300,2301],{},"2026.06.25",[131,2303,322],{},[107,2305,2306,2309,2318,2320,2322,2324],{},[131,2307,2308],{},"5-2-14",[131,2310,2311,89,2314,2317],{},[21,2312,2313],{},"PG(결제) 어댑터 + 카드 등록·결제·취소",[21,2315,2316],{},"토스(TossPayments) 확정"," (2026-06-04). 어댑터 src\u002Fadapters\u002Fpg\u002Ftoss.ts 신규 작성 + secret TOSS_CLIENT_KEY\u002FTOSS_SECRET_KEY + 콜백 webhook 예정.",[131,2319,382],{},[131,2321,484],{},[131,2323,1590],{},[131,2325,322],{},[107,2327,2328,2331,2337,2339,2341,2344],{},[131,2329,2330],{},"5-2-15",[131,2332,2333,2336],{},[21,2334,2335],{},"AI 템플릿 게이트웨이 (LLM)"," — 제공자 미정",[131,2338,382],{},[131,2340,484],{},[131,2342,2343],{},"2026.07.01",[131,2345,322],{},[107,2347,2348,2351,2357,2359,2361,2363],{},[131,2349,2350],{},"5-2-16",[131,2352,2353,2356],{},[21,2354,2355],{},"NHN 실 모드 전환 + envelope 암호화"," — Notification Hub OAuth(client_credentials + Bearer 토큰) 어댑터 재작성 완료. SMS·Email 라우트 활성화. envelope 암호화·테넌트별 자격증명은 후속.",[131,2358,338],{},[131,2360,484],{},[131,2362,1533],{},[131,2364,322],{},[252,2366,2368],{"id":2367},"api-엔드포인트-5","API 엔드포인트 (5)",[101,2370,2371,2387],{},[104,2372,2373],{},[107,2374,2375,2377,2379,2381,2383,2385],{},[110,2376,264],{},[110,2378,267],{},[110,2380,270],{},[110,2382,273],{},[110,2384,276],{},[110,2386,279],{},[126,2388,2389,2409,2429,2449,2468],{},[107,2390,2391,2394,2400,2402,2404,2407],{},[131,2392,2393],{},"5-2-17",[131,2395,2396,2399],{},[21,2397,2398],{},"계약·서류 R2 라우트 (\u002Fcontracts\u002F*) + FILES 바인딩"," — 6\u002F2 §11. 5 라우트(list\u002Fsign\u002Ffiles list\u002Fupload\u002Fdownload\u002Fdelete) + R2 bucket malgn-noti-files + TB_CONTRACT\u002FTB_CONTRACT_FILE schema.ts + signup auto-create + reviewing 자동 전이 + lazy backfill (6\u002F2 §11·§12·§13).",[131,2401,295],{},[131,2403,484],{},[131,2405,2406],{},"2026.06.02",[131,2408,2406],{},[107,2410,2411,2414,2420,2422,2424,2427],{},[131,2412,2413],{},"5-2-18",[131,2415,2416,2419],{},[21,2417,2418],{},"NICE 통합인증 인프라"," — 6\u002F1 §5 + 6\u002F2 §16 + 6\u002F4 §1. mock 모드(자격증명 등록 후 콘솔 IP 정책 1007 미해결로 mock 유지). Workers outbound IPv6 진단 완료.",[131,2421,295],{},[131,2423,484],{},[131,2425,2426],{},"2026.06.01",[131,2428,2426],{},[107,2430,2431,2434,2440,2442,2444,2447],{},[131,2432,2433],{},"5-2-19",[131,2435,2436,2439],{},[21,2437,2438],{},"WBS 정본 R2 저장 + GET\u002FPATCH 라우트"," — 6\u002F4 §5. DB 미사용. R2 단일 JSON 객체(wbs\u002Fwbs.json, FILES 바인딩). 시드 142 task. GET 공개 + PATCH 인증 2 라우트. last-write-wins.",[131,2441,295],{},[131,2443,484],{},[131,2445,2446],{},"2026.06.04",[131,2448,2446],{},[107,2450,2451,2454,2460,2462,2464,2466],{},[131,2452,2453],{},"5-2-20",[131,2455,2456,2459],{},[21,2457,2458],{},"POST \u002Fme\u002Femail-change — 서비스 담당자 이메일 변경"," — 6\u002F4. 비밀번호 + OTP(change_email) + email-only UPDATE (loginid 가입 시 식별자로 고정 유지). 라이브 e2e 5 시나리오 통과.",[131,2461,295],{},[131,2463,484],{},[131,2465,2446],{},[131,2467,2446],{},[107,2469,2470,2473,2479,2481,2483,2485],{},[131,2471,2472],{},"5-2-21",[131,2474,2475,2478],{},[21,2476,2477],{},"NHN Notification Hub 어댑터 신규(OAuth + Bearer)"," — 6\u002F4 §6. adapters\u002Fnhn\u002Foauth.ts(토큰 발급+캐시) + sms.ts\u002Femail.ts 재작성(POST \u002Fmessage\u002Fv1.0\u002F{SMS|EMAIL}\u002Ffree-form-messages\u002F{purpose}). contactType=PHONE_NUMBER\u002FEMAIL_ADDRESS, X-NC-APP-KEY + X-NHN-Authorization. NhnCredentials 확장(userAccessKey\u002FsecretAccessKey + legacy secretKey 옵셔널).",[131,2480,295],{},[131,2482,484],{},[131,2484,2446],{},[131,2486,2446],{},[252,2488,2490],{"id":2489},"사용자단-화면-ui-목업-15","사용자단 화면 UI (목업) (15)",[101,2492,2493,2509],{},[104,2494,2495],{},[107,2496,2497,2499,2501,2503,2505,2507],{},[110,2498,264],{},[110,2500,267],{},[110,2502,270],{},[110,2504,273],{},[110,2506,276],{},[110,2508,279],{},[126,2510,2511,2531,2550,2570,2589,2608,2627,2646,2665,2684,2703,2722,2741,2759,2779],{},[107,2512,2513,2516,2522,2524,2526,2529],{},[131,2514,2515],{},"5-3-1",[131,2517,2518,2521],{},[21,2519,2520],{},"인증·계정 — 로그인 \u002F 회원가입 5단계 \u002F 비번 재설정 \u002F 보안 인증"," — \u002Flogin · \u002Flogin\u002Fsecurity · \u002Freset-password · \u002Freset-password\u002Fnew · \u002Fsignup",[131,2523,295],{},[131,2525,484],{},[131,2527,2528],{},"2026.05.20",[131,2530,2528],{},[107,2532,2533,2536,2542,2544,2546,2548],{},[131,2534,2535],{},"5-3-2",[131,2537,2538,2541],{},[21,2539,2540],{},"발송 6채널 (SMS\u002FRCS\u002FKakao\u002FEmail\u002FPush\u002FFlow)"," — \u002Fsend\u002F* + PU 풀세트(수신자·주소록·광고수신·컨펌·초기화)",[131,2543,295],{},[131,2545,484],{},[131,2547,2528],{},[131,2549,2528],{},[107,2551,2552,2555,2561,2563,2565,2568],{},[131,2553,2554],{},"5-3-3",[131,2556,2557,2560],{},[21,2558,2559],{},"이력 \u002F 통계 — 5채널 + 통계 대시보드"," — \u002Fhistory\u002F* + 비동기 다운로드 요청 패턴",[131,2562,295],{},[131,2564,484],{},[131,2566,2567],{},"2026.05.21",[131,2569,2567],{},[107,2571,2572,2575,2581,2583,2585,2587],{},[131,2573,2574],{},"5-3-4",[131,2576,2577,2580],{},[21,2578,2579],{},"주소록 — 연락처 \u002F 그룹 \u002F 수신거부"," — \u002Fcontacts\u002F{list,groups,optout}",[131,2582,295],{},[131,2584,484],{},[131,2586,2567],{},[131,2588,2567],{},[107,2590,2591,2594,2600,2602,2604,2606],{},[131,2592,2593],{},"5-3-5",[131,2595,2596,2599],{},[21,2597,2598],{},"발신 정보 6종"," — \u002Fsender\u002F{numbers,brands,domains,push-cert,profiles,optout-080} + 등록 마법사",[131,2601,295],{},[131,2603,484],{},[131,2605,1437],{},[131,2607,1437],{},[107,2609,2610,2613,2619,2621,2623,2625],{},[131,2611,2612],{},"5-3-6",[131,2614,2615,2618],{},[21,2616,2617],{},"템플릿 관리 — 5채널 + 발송 상세 설정"," — \u002Fmanage\u002F{sms,rcs,kakao,email,push,settings}",[131,2620,295],{},[131,2622,484],{},[131,2624,1437],{},[131,2626,1437],{},[107,2628,2629,2632,2638,2640,2642,2644],{},[131,2630,2631],{},"5-3-7",[131,2633,2634,2637],{},[21,2635,2636],{},"캠페인 — 본안 + 변형(v3)"," — \u002Fcampaign · \u002Fcampaign3",[131,2639,295],{},[131,2641,484],{},[131,2643,1437],{},[131,2645,1437],{},[107,2647,2648,2651,2657,2659,2661,2663],{},[131,2649,2650],{},"5-3-8",[131,2652,2653,2656],{},[21,2654,2655],{},"크레딧 \u002F 결제 — 충전·결과·내역·영수증·카드 관리"," — \u002Fcharge · \u002Fcharge\u002Fresult · \u002Faccount\u002F{credit,cards}",[131,2658,295],{},[131,2660,484],{},[131,2662,1437],{},[131,2664,1437],{},[107,2666,2667,2670,2676,2678,2680,2682],{},[131,2668,2669],{},"5-3-9",[131,2671,2672,2675],{},[21,2673,2674],{},"문의 — 작성 \u002F 완료 \u002F 내 문의 \u002F 상세"," — \u002Finquiry · \u002Finquiry\u002Fcomplete · \u002Faccount\u002Finquiries(\u002Fdetail)",[131,2677,295],{},[131,2679,484],{},[131,2681,1437],{},[131,2683,1437],{},[107,2685,2686,2689,2695,2697,2699,2701],{},[131,2687,2688],{},"5-3-10",[131,2690,2691,2694],{},[21,2692,2693],{},"나의 페이지 — 9 라우트"," — AppMyPageShell + \u002Faccount\u002F{settings,cards,password,security,multi,contract,credit,billing,inquiries}",[131,2696,295],{},[131,2698,484],{},[131,2700,1437],{},[131,2702,1437],{},[107,2704,2705,2708,2714,2716,2718,2720],{},[131,2706,2707],{},"5-3-11",[131,2709,2710,2713],{},[21,2711,2712],{},"메시지 관리 랜딩페이지"," — 목록 · 기본형\u002F확장형 등록 폼 · 미리보기",[131,2715,295],{},[131,2717,484],{},[131,2719,1437],{},[131,2721,1437],{},[107,2723,2724,2727,2733,2735,2737,2739],{},[131,2725,2726],{},"5-3-12",[131,2728,2729,2732],{},[21,2730,2731],{},"공개 랜딩페이지 + 운영 가이드"," — \u002F (히어로·5채널·장점·단가 비교·CTA) + \u002Fhelp",[131,2734,295],{},[131,2736,484],{},[131,2738,1437],{},[131,2740,1437],{},[107,2742,2743,2746,2751,2753,2755,2757],{},[131,2744,2745],{},"5-3-13",[131,2747,2748],{},[21,2749,2750],{},"디자인 가이드 (라이브 카탈로그)",[131,2752,295],{},[131,2754,484],{},[131,2756,1970],{},[131,2758,1970],{},[107,2760,2761,2764,2770,2772,2774,2777],{},[131,2762,2763],{},"5-3-14",[131,2765,2766,2769],{},[21,2767,2768],{},"시스템 페이지 — 404 \u002F system error"," — 단독 일부 라이브. 점검 \u002F 네트워크 \u002F 인증 메일 템플릿 미",[131,2771,338],{},[131,2773,484],{},[131,2775,2776],{},"2026.06.11",[131,2778,322],{},[107,2780,2781,2784,2790,2792,2794,2796],{},[131,2782,2783],{},"5-3-15",[131,2785,2786,2789],{},[21,2787,2788],{},"\u002Fwbs 페이지 — R2 정본 비동기 로드 + 인라인 편집 모달"," — 6\u002F4 §5. 임베디드 STAGES 제거 → top-level await api(\u002Fwbs). AppModal 편집 다이얼로그(owner·note·href·targetDate·completionDate). 비로그인 읽기 전용 + \"로그인하면 편집 가능\" 힌트.",[131,2791,295],{},[131,2793,484],{},[131,2795,2446],{},[131,2797,2446],{},[252,2799,2801],{"id":2800},"사용자단-api-연동-21","사용자단 ↔ API 연동 (21)",[101,2803,2804,2820],{},[104,2805,2806],{},[107,2807,2808,2810,2812,2814,2816,2818],{},[110,2809,264],{},[110,2811,267],{},[110,2813,270],{},[110,2815,273],{},[110,2817,276],{},[110,2819,279],{},[126,2821,2822,2841,2860,2880,2900,2919,2938,2958,2977,2996,3015,3034,3053,3072,3092,3112,3130,3149,3167,3186,3205],{},[107,2823,2824,2827,2833,2835,2837,2839],{},[131,2825,2826],{},"5-3C-1",[131,2828,2829,2832],{},[21,2830,2831],{},"인증·계정 (\u002Fauth\u002Fsignup·\u002Fauth\u002Flogin·\u002Fme)"," — 6\u002F1 §4. JWT 쿠키 + 가드 미들웨어 + 클라이언트 부트스트랩 플러그인",[131,2834,295],{},[131,2836,484],{},[131,2838,2426],{},[131,2840,2426],{},[107,2842,2843,2846,2852,2854,2856,2858],{},[131,2844,2845],{},"5-3C-1a",[131,2847,2848,2851],{},[21,2849,2850],{},"이메일 OTP (\u002Fauth\u002Femail-code\u002Fsend·\u002Fverify)"," — 6\u002F1 §5. signup.vue Step 3에서 실 API 호출 + mockCode 개발 편의",[131,2853,295],{},[131,2855,484],{},[131,2857,2426],{},[131,2859,2426],{},[107,2861,2862,2865,2871,2873,2875,2878],{},[131,2863,2864],{},"5-3C-2",[131,2866,2867,2870],{},[21,2868,2869],{},"로그아웃 — GNB 실 연결 (P0)"," — useAuthStore().logout() 호출로 데모 토글 교체",[131,2872,382],{},[131,2874,484],{},[131,2876,2877],{},"2026.06.05",[131,2879,322],{},[107,2881,2882,2885,2891,2893,2895,2898],{},[131,2883,2884],{},"5-3C-3",[131,2886,2887,2890],{},[21,2888,2889],{},"비밀번호 재설정 — OTP 인프라 재활용 (P0)"," — purpose='reset_password' + POST \u002Fauth\u002Fpassword\u002Freset 신설",[131,2892,382],{},[131,2894,484],{},[131,2896,2897],{},"2026.06.10",[131,2899,322],{},[107,2901,2902,2905,2911,2913,2915,2917],{},[131,2903,2904],{},"5-3C-4",[131,2906,2907,2910],{},[21,2908,2909],{},"POST \u002Fauth\u002Flogin-by-email — companyId UX 개선 (P0)"," — 6\u002F2 §7. 로그인 폼에서 고객사 ID 필드 제거. 같은 이메일이 여러 회사면 회사 선택 UI 노출",[131,2912,295],{},[131,2914,484],{},[131,2916,2406],{},[131,2918,2406],{},[107,2920,2921,2924,2930,2932,2934,2936],{},[131,2922,2923],{},"5-3C-5",[131,2925,2926,2929],{},[21,2927,2928],{},"약관 동의 적재 (POST \u002Fauth\u002Fagree-terms) (P1)"," — TB_TERMS_AGREEMENT 적재",[131,2931,382],{},[131,2933,484],{},[131,2935,2877],{},[131,2937,322],{},[107,2939,2940,2943,2949,2951,2953,2956],{},[131,2941,2942],{},"5-3C-6",[131,2944,2945,2948],{},[21,2946,2947],{},"companyType 전달·저장 + 화면 분기 (P1)"," — 6\u002F2 §7. TB_COMPANY.company_type 추가 + signup에서 전달 + \u002Fme 응답 노출 + Member 패널 사업자등록증 변경 버튼 조건부 노출. 개인 유형 다른 화면(LNB·계약\u002F멀티 미노출)은 후속",[131,2950,338],{},[131,2952,484],{},[131,2954,2955],{},"2026.06.08",[131,2957,2406],{},[107,2959,2960,2963,2969,2971,2973,2975],{},[131,2961,2962],{},"5-3C-17",[131,2964,2965,2968],{},[21,2966,2967],{},"사업자등록증 심사 승인 게이트 (정책)"," — 6\u002F2 §7. TB_COMPANY.approval_state 신규 + signup 자동 분기(corp\u002Fsole pending, personal approved) + PATCH \u002Fme·\u002Fme\u002Fcompany 차단 403 + 프런트 배너·입력 disabled. 운영자단 승인 화면 + 다른 도메인 라우트 차단은 후속",[131,2970,295],{},[131,2972,484],{},[131,2974,2406],{},[131,2976,2406],{},[107,2978,2979,2982,2988,2990,2992,2994],{},[131,2980,2981],{},"5-3C-7",[131,2983,2984,2987],{},[21,2985,2986],{},"PATCH \u002Fme + \u002Faccount\u002Fsettings"," — 6\u002F2 §6 + 6\u002F4 (PATCH \u002Fme + \u002Fme\u002Fcompany + \u002Fme\u002Femail-change). 서비스 담당자 이메일 변경(loginid 유지·email만)·결제 이메일 변경·광고수신 토글 모두 실 API. 비밀번호 변경은 5-3C-8 별도.",[131,2989,295],{},[131,2991,484],{},[131,2993,2446],{},[131,2995,2446],{},[107,2997,2998,3001,3006,3008,3010,3013],{},[131,2999,3000],{},"5-3C-8",[131,3002,3003],{},[21,3004,3005],{},"POST \u002Fauth\u002Fpassword + \u002Faccount\u002Fpassword (P2)",[131,3007,382],{},[131,3009,484],{},[131,3011,3012],{},"2026.06.23",[131,3014,322],{},[107,3016,3017,3020,3026,3028,3030,3032],{},[131,3018,3019],{},"5-3C-9",[131,3021,3022,3025],{},[21,3023,3024],{},"\u002Faccount\u002Fsecurity (2FA) + PATCH \u002Fme\u002Fsecurity (P2)"," — TB_VERIFICATION 재사용",[131,3027,382],{},[131,3029,484],{},[131,3031,1590],{},[131,3033,322],{},[107,3035,3036,3039,3044,3046,3048,3051],{},[131,3037,3038],{},"5-3C-10",[131,3040,3041],{},[21,3042,3043],{},"\u002Faccount\u002Fmulti + \u002Fmanager-invites (P2)",[131,3045,382],{},[131,3047,484],{},[131,3049,3050],{},"2026.06.26",[131,3052,322],{},[107,3054,3055,3058,3064,3066,3068,3070],{},[131,3056,3057],{},"5-3C-11",[131,3059,3060,3063],{},[21,3061,3062],{},"\u002Faccount\u002Fcontract + R2 업로드"," — 6\u002F2 §11~§15. \u002Fcontracts\u002F* 5 라우트(list\u002Fsign\u002Ffiles list\u002Fupload\u002Fdownload\u002Fdelete) + R2 bucket malgn-noti-files + 미리보기·삭제·휴대폰 본인인증 서명 + 사업자등록증 자동 reviewing 전이 + lazy backfill + 파일 행 상태 배지. 운영자 승인 화면만 미",[131,3065,295],{},[131,3067,484],{},[131,3069,2406],{},[131,3071,2406],{},[107,3073,3074,3077,3083,3085,3087,3090],{},[131,3075,3076],{},"5-3C-12",[131,3078,3079,3082],{},[21,3080,3081],{},"발송 6채널 — UI에 실 API 호출 (Idempotency-Key 헤더)"," — NHN Notification Hub 자격증명(User Access Key) 수령 + 어댑터 OAuth 재작성 필요 (6\u002F2 §16)",[131,3084,382],{},[131,3086,484],{},[131,3088,3089],{},"2026.06.15",[131,3091,322],{},[107,3093,3094,3097,3103,3105,3107,3110],{},[131,3095,3096],{},"5-3C-13",[131,3098,3099,3102],{},[21,3100,3101],{},"이력\u002F통계 — 목록·통계 라우트 연동"," — API 일부 미 — 5-2 동시 진행",[131,3104,382],{},[131,3106,484],{},[131,3108,3109],{},"2026.06.18",[131,3111,322],{},[107,3113,3114,3117,3122,3124,3126,3128],{},[131,3115,3116],{},"5-3C-14",[131,3118,3119],{},[21,3120,3121],{},"주소록·발신정보·템플릿 — CRUD 연동 (API ✅)",[131,3123,382],{},[131,3125,484],{},[131,3127,1570],{},[131,3129,322],{},[107,3131,3132,3135,3140,3142,3144,3147],{},[131,3133,3134],{},"5-3C-15",[131,3136,3137],{},[21,3138,3139],{},"크레딧·결제 — PG 어댑터 미정 (블로커)",[131,3141,382],{},[131,3143,484],{},[131,3145,3146],{},"2026.06.27",[131,3148,322],{},[107,3150,3151,3154,3159,3161,3163,3165],{},[131,3152,3153],{},"5-3C-16",[131,3155,3156],{},[21,3157,3158],{},"문의 — \u002Finquiries 연동",[131,3160,382],{},[131,3162,484],{},[131,3164,1570],{},[131,3166,322],{},[107,3168,3169,3172,3178,3180,3182,3184],{},[131,3170,3171],{},"5-3C-18",[131,3173,3174,3177],{},[21,3175,3176],{},"사업자등록증 첨부 시 reviewing 자동 전이 + 파일 행 배지 + 반려 시 삭제"," — 6\u002F2 §12·§14. approval_state enum 4단계 확장(pending→reviewing→approved\u002Frejected) + POST \u002Fcontracts\u002Ffiles kind=biz 후 pending\u002Frejected→reviewing UPDATE + 파일 행 상태 배지(reviewing=info·approved=success·rejected=danger) + rejected 상태에서만 삭제 버튼",[131,3179,295],{},[131,3181,484],{},[131,3183,2406],{},[131,3185,2406],{},[107,3187,3188,3191,3197,3199,3201,3203],{},[131,3189,3190],{},"5-3C-19",[131,3192,3193,3196],{},[21,3194,3195],{},"계약서 서명 다이얼로그 — 휴대폰 본인인증 sub-step"," — 6\u002F2 §15. phone-code purpose=contract_sign 추가 + 다이얼로그 STEP 3에 본인인증 카드(휴대폰 마스킹 + 발송 + 6자리 확인) + 통과 시 서명 영역 노출 + 공인인증서 탭 제거 + dialog open 시 fetchMe 강제 hydrate",[131,3198,295],{},[131,3200,484],{},[131,3202,2406],{},[131,3204,2406],{},[107,3206,3207,3210,3216,3218,3220,3222],{},[131,3208,3209],{},"5-3C-20",[131,3211,3212,3215],{},[21,3213,3214],{},"서비스 담당자 이메일 변경 — 실 OTP API 연동"," — 6\u002F4. AppEmailChangeDialog sendCode\u002FconfirmCode를 \u002Fauth\u002Femail-code\u002F{send,verify} (purpose=change_email)로 교체. confirm payload={newEmail,code,password}. auth store changeEmail() → POST \u002Fme\u002Femail-change. 결제 이메일 변경은 기존 흐름 유지.",[131,3217,295],{},[131,3219,484],{},[131,3221,2446],{},[131,3223,2446],{},[252,3225,3227],{"id":3226},"관리자단-화면-16","관리자단 화면 (16)",[101,3229,3230,3246],{},[104,3231,3232],{},[107,3233,3234,3236,3238,3240,3242,3244],{},[110,3235,264],{},[110,3237,267],{},[110,3239,270],{},[110,3241,273],{},[110,3243,276],{},[110,3245,279],{},[126,3247,3248,3267,3286,3304,3323,3341,3359,3378,3397,3416,3434,3452,3471,3489,3508,3527],{},[107,3249,3250,3253,3259,3261,3263,3265],{},[131,3251,3252],{},"5-4-1",[131,3254,3255,3258],{},[21,3256,3257],{},"셸 + LNB(8 그룹) + TopBar + 디자인 가이드"," — 부트스트랩 · 라이브",[131,3260,295],{},[131,3262,484],{},[131,3264,1931],{},[131,3266,1931],{},[107,3268,3269,3272,3278,3280,3282,3284],{},[131,3270,3271],{},"5-4-2",[131,3273,3274,3277],{},[21,3275,3276],{},"페이지 기획 MD (33종)"," — P0 14 \u002F P1 13 \u002F P2 5",[131,3279,295],{},[131,3281,484],{},[131,3283,1931],{},[131,3285,1931],{},[107,3287,3288,3291,3296,3298,3300,3302],{},[131,3289,3290],{},"5-4-3",[131,3292,3293],{},[21,3294,3295],{},"회원 · 고객사 관리 (P0)",[131,3297,382],{},[131,3299,484],{},[131,3301,3146],{},[131,3303,322],{},[107,3305,3306,3309,3314,3316,3318,3321],{},[131,3307,3308],{},"5-4-4",[131,3310,3311],{},[21,3312,3313],{},"시스템 관리 (P0) — 운영자 \u002F RBAC \u002F 감사 로그",[131,3315,382],{},[131,3317,484],{},[131,3319,3320],{},"2026.06.29",[131,3322,322],{},[107,3324,3325,3328,3333,3335,3337,3339],{},[131,3326,3327],{},"5-4-5",[131,3329,3330],{},[21,3331,3332],{},"요금 · 단가 관리 (P0)",[131,3334,382],{},[131,3336,484],{},[131,3338,3146],{},[131,3340,322],{},[107,3342,3343,3346,3351,3353,3355,3357],{},[131,3344,3345],{},"5-4-6",[131,3347,3348],{},[21,3349,3350],{},"고객지원 (P0)",[131,3352,382],{},[131,3354,484],{},[131,3356,3320],{},[131,3358,322],{},[107,3360,3361,3364,3369,3371,3373,3376],{},[131,3362,3363],{},"5-4-7",[131,3365,3366],{},[21,3367,3368],{},"발송 운영 모니터링 (P1)",[131,3370,382],{},[131,3372,484],{},[131,3374,3375],{},"2026.07.02",[131,3377,322],{},[107,3379,3380,3383,3388,3390,3392,3395],{},[131,3381,3382],{},"5-4-8",[131,3384,3385],{},[21,3386,3387],{},"발신 정보 검수 (P0)",[131,3389,382],{},[131,3391,484],{},[131,3393,3394],{},"2026.06.28",[131,3396,322],{},[107,3398,3399,3402,3407,3409,3411,3414],{},[131,3400,3401],{},"5-4-9",[131,3403,3404],{},[21,3405,3406],{},"결제 · 크레딧 + 고객사 상세 결제 탭 (P0)",[131,3408,382],{},[131,3410,484],{},[131,3412,3413],{},"2026.06.30",[131,3415,322],{},[107,3417,3418,3421,3426,3428,3430,3432],{},[131,3419,3420],{},"5-4-10",[131,3422,3423],{},[21,3424,3425],{},"템플릿 검수 · 관리 (P0)",[131,3427,382],{},[131,3429,484],{},[131,3431,2343],{},[131,3433,322],{},[107,3435,3436,3439,3444,3446,3448,3450],{},[131,3437,3438],{},"5-4-11",[131,3440,3441],{},[21,3442,3443],{},"수신거부 (운영) (P1)",[131,3445,382],{},[131,3447,484],{},[131,3449,2343],{},[131,3451,322],{},[107,3453,3454,3457,3462,3464,3466,3469],{},[131,3455,3456],{},"5-4-12",[131,3458,3459],{},[21,3460,3461],{},"통계 · 리포트 + 대시보드 (P2)",[131,3463,382],{},[131,3465,484],{},[131,3467,3468],{},"2026.07.03",[131,3470,322],{},[107,3472,3473,3476,3481,3483,3485,3487],{},[131,3474,3475],{},"5-4-13",[131,3477,3478],{},[21,3479,3480],{},"콘텐츠 · 사이트 + 시스템 관리 + API 관리 (P2)",[131,3482,382],{},[131,3484,484],{},[131,3486,3468],{},[131,3488,322],{},[107,3490,3491,3494,3500,3502,3504,3506],{},[131,3492,3493],{},"5-4-14",[131,3495,3496,3499],{},[21,3497,3498],{},"핸드오프 정본 17 페이지 풀세트 (화면만, API 연동 후속)"," — 6\u002F4 §3. handoff_noti_admin (3,129줄 jsx) → Vue 1:1 포팅. 셸 완전 재정비 + 공유 컴포넌트 14종 + 차트 4종 + 17 페이지(대시보드·고객사·고객사 상세·계정·모니터링·발신번호·발신프로필·템플릿검수·결제·채널단가·충전쿠폰·1:1문의·FAQ·공지·통계·운영자·권한그룹·API). 18 라우트 라이브 200.",[131,3501,295],{},[131,3503,484],{},[131,3505,2446],{},[131,3507,2446],{},[107,3509,3510,3513,3519,3521,3523,3525],{},[131,3511,3512],{},"5-4-15",[131,3514,3515,3518],{},[21,3516,3517],{},"페이지 진척 상태 라벨 (dev=screen\u002Fpartial\u002Flive)"," — 6\u002F4. AppPageHeader prop dev 3단계. 화면(neutral·flask)·일부 연동(warning·construction)·연동(미표시). 17 페이지 모두 dev=\"screen\"으로 명시.",[131,3520,295],{},[131,3522,484],{},[131,3524,2446],{},[131,3526,2446],{},[107,3528,3529,3532,3538,3540,3542,3544],{},[131,3530,3531],{},"5-4-16",[131,3533,3534,3537],{},[21,3535,3536],{},"로고\u002F브랜드 — 사용자단 로고로 통일 + \"관리자\" 식별 태그"," — 6\u002F4. 기존 파랑 그라데이션 박스 폐기 → AppLogoMark(말풍선+스파클) + \"맑은 message\" + primary-50 배경 \"관리자\" 배지.",[131,3539,295],{},[131,3541,484],{},[131,3543,2446],{},[131,3545,2446],{},[252,3547,3549],{"id":3548},"통합-배포-12","통합 · 배포 (12)",[101,3551,3552,3568],{},[104,3553,3554],{},[107,3555,3556,3558,3560,3562,3564,3566],{},[110,3557,264],{},[110,3559,267],{},[110,3561,270],{},[110,3563,273],{},[110,3565,276],{},[110,3567,279],{},[126,3569,3570,3589,3608,3627,3646,3668,3687,3706,3724,3742,3761,3785],{},[107,3571,3572,3575,3581,3583,3585,3587],{},[131,3573,3574],{},"5-5-1",[131,3576,3577,3580],{},[21,3578,3579],{},"사용자단 Cloudflare Pages 배포 #1~#69 + alias 다수"," — 매 마일스톤 직후 배포 (6\u002F4 누적 #80+ alias 다수)",[131,3582,338],{},[131,3584,484],{},[131,3586,322],{},[131,3588,322],{},[107,3590,3591,3594,3600,3602,3604,3606],{},[131,3592,3593],{},"5-5-2",[131,3595,3596,3599],{},[21,3597,3598],{},"관리자단 Cloudflare Pages 첫 Nuxt 배포"," — 정적 placeholder → 실 Nuxt 앱",[131,3601,295],{},[131,3603,484],{},[131,3605,1931],{},[131,3607,1931],{},[107,3609,3610,3613,3619,3621,3623,3625],{},[131,3611,3612],{},"5-5-3",[131,3614,3615,3618],{},[21,3616,3617],{},"API Workers 배포 #1~#19"," — 6\u002F4 최신 Version 1ca0446e-ed3f-4079-be5f-3407f4550ba7 (#25+)",[131,3620,338],{},[131,3622,484],{},[131,3624,322],{},[131,3626,322],{},[107,3628,3629,3632,3638,3640,3642,3644],{},[131,3630,3631],{},"5-5-4",[131,3633,3634,3637],{},[21,3635,3636],{},"DDL — 0001~0005 라이브 적용"," — 0001 idempotency \u002F 0002 export_flow \u002F 0003 loginid global unique \u002F 0004 nice_auth \u002F 0005 company_approval. TB_CONTRACT·TB_CONTRACT_FILE은 6\u002F2 §11에서 schema.ts 정의(라이브에 이미 존재)",[131,3639,295],{},[131,3641,484],{},[131,3643,2406],{},[131,3645,2406],{},[107,3647,3648,3651,3660,3662,3664,3666],{},[131,3649,3650],{},"5-5-5",[131,3652,3653,89,3656,3659],{},[21,3654,3655],{},"NHN Notification Hub 자격증명 + 어댑터 재작성",[21,3657,3658],{},"6\u002F4: SMS·Email 어댑터 Notification Hub로 재작성 완료"," + Email real 발송 검증 통과. SMS는 NHN 콘솔 발신번호 등록 + SMS_FROM secret 대기. push\u002Frcs\u002Fkakao 어댑터 마이그레이션 후속.",[131,3661,338],{},[131,3663,484],{},[131,3665,1570],{},[131,3667,322],{},[107,3669,3670,3673,3679,3681,3683,3685],{},[131,3671,3672],{},"5-5-6",[131,3674,3675,3678],{},[21,3676,3677],{},"NICE 통합인증 실 모드 전환"," — 6\u002F4 재시도 → 여전히 1007 (Workers outbound IPv6 vs NICE 콘솔 IPv4 등록). 사용자 콘솔 IP 정책 해결 대기.",[131,3680,382],{},[131,3682,484],{},[131,3684,3468],{},[131,3686,322],{},[107,3688,3689,3692,3698,3700,3702,3704],{},[131,3690,3691],{},"5-5-7",[131,3693,3694,3697],{},[21,3695,3696],{},"R2 bucket malgn-noti-files + FILES 바인딩"," — 6\u002F2 §11. 사업자등록증·대부업등록증·보험증권 첨부용",[131,3699,295],{},[131,3701,484],{},[131,3703,2406],{},[131,3705,2406],{},[107,3707,3708,3711,3716,3718,3720,3722],{},[131,3709,3710],{},"5-5-8",[131,3712,3713],{},[21,3714,3715],{},"PG 카드 결제 연동",[131,3717,382],{},[131,3719,484],{},[131,3721,3050],{},[131,3723,322],{},[107,3725,3726,3729,3734,3736,3738,3740],{},[131,3727,3728],{},"5-5-9",[131,3730,3731],{},[21,3732,3733],{},"AI 템플릿 게이트웨이 연동",[131,3735,382],{},[131,3737,484],{},[131,3739,3375],{},[131,3741,322],{},[107,3743,3744,3747,3753,3755,3757,3759],{},[131,3745,3746],{},"5-5-10",[131,3748,3749,3752],{},[21,3750,3751],{},"Hyperdrive Cloudflare Tunnel(Access) 전환"," — 6\u002F4 §2. id a2ba... → 439b... 신규 origin malgn-dev-db.apiserver.kr + access_client_id. Aurora SG egress IP 화이트리스트 운영 부담 해소. 정본 3개(API CLAUDE.md §3·§8·§12, SCALABILITY.md §6 신규 절, MIGRATION.md §1) 동기화. 라이브 검증 통과.",[131,3754,295],{},[131,3756,484],{},[131,3758,2446],{},[131,3760,2446],{},[107,3762,3763,3766,3777,3779,3781,3783],{},[131,3764,3765],{},"5-5-11",[131,3767,3768,3771,3772,3776],{},[21,3769,3770],{},"NHN Email 실 발송 활성화"," — 6\u002F4. ",[26,3773,3775],{"href":3774},"mailto:message@malgnsoft.com","message@malgnsoft.com"," 발신 도메인 NHN Notification Hub 콘솔 등록 + EMAIL_FROM\u002FEMAIL_FROM_NAME secret 등록. NHN 직접 호출 SUCCESS·messageId 발급 확인.",[131,3778,295],{},[131,3780,484],{},[131,3782,2446],{},[131,3784,2446],{},[107,3786,3787,3790,3796,3798,3800,3803],{},[131,3788,3789],{},"5-5-12",[131,3791,3792,3795],{},[21,3793,3794],{},"NHN SMS 실 발송 활성화"," — 어댑터·인증·페이로드 검증 완료. NHN 콘솔 발신번호 등록 + SMS_FROM secret 설정 + 라이브 e2e 1건 대기.",[131,3797,382],{},[131,3799,484],{},[131,3801,3802],{},"2026.06.13",[131,3804,322],{},[73,3806],{},[76,3808,3810],{"id":3809},"부록-a-step-5-단순화-카테고리-재정의","부록 A. Step 5 단순화 카테고리 (재정의)",[15,3812,3813],{},[18,3814,3815,3816,3819,3820,3823],{},"위 \"단계별 상세\"의 Step 5(서비스 개발) 세부 항목을 ",[21,3817,3818],{},"큰 카테고리로 묶어 단순화","한 뷰. 향후 ",[21,3821,3822],{},"간트 WBS","로 펼칠 때의 묶음 기준. 괄호 안은 기존 task id.",[237,3825,3827],{"id":3826},"a-1-api-백엔드","A-1. API 백엔드 — 🟡",[18,3829,3830,3831,3833,3834,3837],{},"기존 ",[32,3832,716],{},"(16) + ",[32,3835,3836],{},"API 엔드포인트","(5) → 6개 카테고리 통합.",[101,3839,3840,3855],{},[104,3841,3842],{},[107,3843,3844,3847,3850,3852],{},[110,3845,3846],{},"#",[110,3848,3849],{},"카테고리",[110,3851,270],{},[110,3853,3854],{},"포함 (기존 id)",[126,3856,3857,3873,3888,3904,3919,3935],{},[107,3858,3859,3862,3867,3870],{},[131,3860,3861],{},"1.1",[131,3863,3864],{},[21,3865,3866],{},"기반 인프라",[131,3868,3869],{},"✅",[131,3871,3872],{},"Workers+Hyperdrive(5-2-1) · DB 49테이블·파티션(5-2-2) · 기초 CRUD 14도메인(5-2-3) · OpenAPI\u002FScalar(5-2-4)",[107,3874,3875,3878,3883,3885],{},[131,3876,3877],{},"1.2",[131,3879,3880],{},[21,3881,3882],{},"인증·계정·문서",[131,3884,3869],{},[131,3886,3887],{},"signup\u002Flogin\u002FJWT\u002FPBKDF2(5-2-5) · NICE 통합인증(5-2-18) · 계약·서류 R2(5-2-17) · WBS R2(5-2-19) · 이메일 변경(5-2-20)",[107,3889,3890,3893,3898,3901],{},[131,3891,3892],{},"1.3",[131,3894,3895],{},[21,3896,3897],{},"발송 엔진",[131,3899,3900],{},"🟡",[131,3902,3903],{},"producer 5채널(5-2-6) · 멱등성(5-2-7) · NHN 어댑터(5-2-8) · Queues+Consumer(5-2-9) · NHN Hub OAuth(5-2-21) · Webhook(5-2-10) · 실모드 전환·암호화(5-2-16)",[107,3905,3906,3909,3914,3916],{},[131,3907,3908],{},"1.4",[131,3910,3911],{},[21,3912,3913],{},"발송 확장",[131,3915,3900],{},[131,3917,3918],{},"Export 다운로드 잡(5-2-11) · Flow 복합발송(5-2-12) · 캠페인(스케줄·시뮬·테스트)(5-2-13)",[107,3920,3921,3924,3929,3932],{},[131,3922,3923],{},"1.5",[131,3925,3926],{},[21,3927,3928],{},"결제·크레딧",[131,3930,3931],{},"⚪",[131,3933,3934],{},"PG 어댑터 + 카드 등록·결제·취소(5-2-14)",[107,3936,3937,3940,3945,3947],{},[131,3938,3939],{},"1.6",[131,3941,3942],{},[21,3943,3944],{},"AI 템플릿",[131,3946,3931],{},[131,3948,3949],{},"LLM 게이트웨이(5-2-15)",[237,3951,3953],{"id":3952},"a-2-사용자단-api-연동","A-2. 사용자단 ↔ API 연동 — 🟡",[101,3955,3956,3968],{},[104,3957,3958],{},[107,3959,3960,3962,3964,3966],{},[110,3961,3846],{},[110,3963,3849],{},[110,3965,270],{},[110,3967,3854],{},[126,3969,3970,3993,4008,4023,4038,4057],{},[107,3971,3972,3975,3980,3982],{},[131,3973,3974],{},"2.1",[131,3976,3977],{},[21,3978,3979],{},"인증·계정",[131,3981,3900],{},[131,3983,3984,3985,3988,3989,3992],{},"로그인·회원가입·",[32,3986,3987],{},"\u002Fme","(5-3C-1) · 이메일 OTP(5-3C-1a) · login-by-email(5-3C-4) · ",[32,3990,3991],{},"\u002Faccount\u002Fsettings"," PATCH(5-3C-7) · 로그아웃(5-3C-2) · 비번 재설정(5-3C-3) · 약관 동의(5-3C-5) · companyType 분기(5-3C-6) · 비번 변경(5-3C-8) · 2FA(5-3C-9) · 멀티계정·초대(5-3C-10)",[107,3994,3995,3998,4003,4005],{},[131,3996,3997],{},"2.2",[131,3999,4000],{},[21,4001,4002],{},"계약·승인",[131,4004,3869],{},[131,4006,4007],{},"승인 게이트(5-3C-17) · 계약·R2 업로드(5-3C-11) · reviewing 자동전이·배지(5-3C-18) · 계약서 서명(5-3C-19) · 담당자 이메일 변경(5-3C-20)",[107,4009,4010,4013,4018,4020],{},[131,4011,4012],{},"2.3",[131,4014,4015],{},[21,4016,4017],{},"발송·이력·통계",[131,4019,3931],{},[131,4021,4022],{},"발송 6채널 실 API(5-3C-12) · 이력\u002F통계 연동(5-3C-13)",[107,4024,4025,4028,4033,4035],{},[131,4026,4027],{},"2.4",[131,4029,4030],{},[21,4031,4032],{},"데이터 관리",[131,4034,3931],{},[131,4036,4037],{},"주소록·발신정보·템플릿 CRUD 연동(5-3C-14)",[107,4039,4040,4043,4048,4050],{},[131,4041,4042],{},"2.5",[131,4044,4045],{},[21,4046,4047],{},"크레딧·결제",[131,4049,3931],{},[131,4051,4052,4053,4056],{},"PG 연동 — ",[21,4054,4055],{},"블로커","(5-3C-15)",[107,4058,4059,4062,4067,4069],{},[131,4060,4061],{},"2.6",[131,4063,4064],{},[21,4065,4066],{},"문의",[131,4068,3931],{},[131,4070,4071,4074],{},[32,4072,4073],{},"\u002Finquiries"," 연동(5-3C-16)",[237,4076,4078],{"id":4077},"a-3-관리자단-화면-화면-골격-실-연동","A-3. 관리자단 화면 — ⚪ (화면 골격 ✅ \u002F 실 연동 ⚪)",[101,4080,4081,4093],{},[104,4082,4083],{},[107,4084,4085,4087,4089,4091],{},[110,4086,3846],{},[110,4088,3849],{},[110,4090,270],{},[110,4092,3854],{},[126,4094,4095,4110,4125,4140,4155,4169],{},[107,4096,4097,4100,4105,4107],{},[131,4098,4099],{},"3.1",[131,4101,4102],{},[21,4103,4104],{},"기반·셸·핸드오프",[131,4106,3869],{},[131,4108,4109],{},"셸 LNB+TopBar+디자인가이드(5-4-1) · 기획 MD 33종(5-4-2) · 핸드오프 17페이지(5-4-14) · 진척 라벨(5-4-15) · 로고\u002F브랜드(5-4-16)",[107,4111,4112,4115,4120,4122],{},[131,4113,4114],{},"3.2",[131,4116,4117],{},[21,4118,4119],{},"회원·고객사",[131,4121,3931],{},[131,4123,4124],{},"회원·고객사 관리 + 상세(5-4-3)",[107,4126,4127,4130,4135,4137],{},[131,4128,4129],{},"3.3",[131,4131,4132],{},[21,4133,4134],{},"운영·검수",[131,4136,3931],{},[131,4138,4139],{},"발송 모니터링(5-4-7) · 발신정보 검수(5-4-8) · 템플릿 검수(5-4-10) · 수신거부 운영(5-4-11)",[107,4141,4142,4145,4150,4152],{},[131,4143,4144],{},"3.4",[131,4146,4147],{},[21,4148,4149],{},"요금·결제",[131,4151,3931],{},[131,4153,4154],{},"요금·단가(5-4-5) · 결제·크레딧 + 결제 탭(5-4-9)",[107,4156,4157,4160,4164,4166],{},[131,4158,4159],{},"3.5",[131,4161,4162],{},[21,4163,1506],{},[131,4165,3931],{},[131,4167,4168],{},"1:1 문의·FAQ·공지(5-4-6)",[107,4170,4171,4174,4179,4181],{},[131,4172,4173],{},"3.6",[131,4175,4176],{},[21,4177,4178],{},"시스템·통계",[131,4180,3931],{},[131,4182,4183],{},"운영자·RBAC·감사로그(5-4-4) · 통계·리포트(5-4-12) · 콘텐츠·사이트·API(5-4-13)",[73,4185],{},[76,4187,4189],{"id":4188},"다음-단계","다음 단계",[81,4191,4192,4198,4204],{},[84,4193,4194,4197],{},[21,4195,4196],{},"간트 WBS 신규 작성"," — 부록 A 카테고리를 작업 단위로 펼쳐 시작·종료·의존성·담당을 간트로 구성(별도).",[84,4199,4200,4201,4203],{},"본 문서는 현황판 데이터(",[32,4202,54],{},")가 갱신될 때 함께 현행화(맨 위 날짜 갱신). 세부 진척의 일차 정본은 현황판\u002F간트 WBS.",[84,4205,4206],{},"우선순위(공통): 발송·이력 실 연동(2.3) · 관리자단 회원·고객사(3.2) · 결제 트랙(PG 선정 → 1.5\u002F2.5\u002F3.4 동시 해소).",{"title":4208,"searchDepth":4209,"depth":4209,"links":4210},"",3,[4211,4213,4251,4256],{"id":78,"depth":4212,"text":79},2,{"id":234,"depth":4212,"text":235,"children":4214},[4215,4224,4233,4239,4241],{"id":239,"depth":4209,"text":4216,"children":4217},"🎯 Step 1 · 프로젝트 준비  step-1",[4218,4220,4221,4222,4223],{"id":254,"depth":4219,"text":255},4,{"id":347,"depth":4219,"text":348},{"id":447,"depth":4219,"text":448},{"id":531,"depth":4219,"text":532},{"id":611,"depth":4219,"text":612},{"id":729,"depth":4209,"text":4225,"children":4226},"📐 Step 2 · 주요 서비스 정책 이슈 정리  step-2",[4227,4228,4229,4230,4231,4232],{"id":743,"depth":4219,"text":744},{"id":859,"depth":4219,"text":860},{"id":921,"depth":4219,"text":922},{"id":964,"depth":4219,"text":965},{"id":1102,"depth":4219,"text":1103},{"id":1221,"depth":4219,"text":1222},{"id":1304,"depth":4209,"text":4234,"children":4235},"📋 Step 3 · 서비스 기획 (화면설계)  step-3",[4236,4237,4238],{"id":1318,"depth":4219,"text":1319},{"id":1399,"depth":4219,"text":1400},{"id":1613,"depth":4219,"text":1614},{"id":1783,"depth":4209,"text":4240},"🎨 Step 4 · 디자인 \u002F 퍼블리싱  step-4",{"id":1858,"depth":4209,"text":4242,"children":4243},"🛠️ Step 5 · 서비스 개발  step-5",[4244,4245,4246,4247,4248,4249,4250],{"id":1872,"depth":4219,"text":1873},{"id":2031,"depth":4219,"text":2032},{"id":2367,"depth":4219,"text":2368},{"id":2489,"depth":4219,"text":2490},{"id":2800,"depth":4219,"text":2801},{"id":3226,"depth":4219,"text":3227},{"id":3548,"depth":4219,"text":3549},{"id":3809,"depth":4212,"text":3810,"children":4252},[4253,4254,4255],{"id":3826,"depth":4209,"text":3827},{"id":3952,"depth":4209,"text":3953},{"id":4077,"depth":4209,"text":4078},{"id":4188,"depth":4212,"text":4189},"md",{},true,{"title":6,"description":4208},"BOARD","xCbohy5xT-1H0ylMbjvCbznnhStY0NYkDSrkx0DCl34",{"id":4264,"title":4265,"body":4266,"description":4208,"extension":4257,"meta":7643,"navigation":4259,"path":7644,"seo":7645,"stem":7646,"__hash__":7647},"docs\u002FDESIGN.md","디자인 가이드 — 맑은 메시징 (사용자단)",{"type":8,"value":4267,"toc":7577},[4268,4271,4309,4311,4315,4590,4610,4612,4616,4733,4737,4750,4760,4762,4766,4783,4789,5003,5007,5065,5081,5089,5096,5211,5215,5244,5246,5250,5259,5291,5295,5418,5443,5445,5449,5453,5459,5463,5521,5528,5534,5541,5543,5547,5551,5557,5579,5586,5592,5596,5607,5611,5622,5626,5645,5651,5663,5667,5676,5682,5700,5704,5714,5720,5745,5749,5841,5849,5851,5855,5864,5870,5992,6011,6017,6041,6048,6086,6090,6126,6132,6153,6157,6177,6183,6191,6205,6287,6294,6314,6359,6363,6391,6396,6400,6418,6424,6456,6460,6474,6480,6502,6505,6600,6604,6613,6617,6634,6638,6664,6666,6670,6673,6719,6721,6725,6811,6826,6828,6832,6875,6877,6881,6981,6983,6987,7049,7051,7055,7064,7068,7074,7105,7109,7285,7295,7299,7333,7352,7372,7391,7418,7435,7453,7455,7459,7508,7510,7514],[11,4269,4265],{"id":4270},"디자인-가이드-맑은-메시징-사용자단",[15,4272,4273,4290,4302],{},[18,4274,4275,47,4278,4281,4282,4285,4286,4289],{},[21,4276,4277],{},"정본(SoT)",[32,4279,4280],{},"design_handoff_malgn_noti"," 핸드오프 — Relay-inspired Design System ",[21,4283,4284],{},"v1.0",".\n",[21,4287,4288],{},"\"Less chrome, more clarity.\""," — Linear · Vercel · Nuxt UI 영감의 저밀도 라이트 모드. 색은 의미가 있을 때만, 라인은 1px, 숫자는 모노스페이스.",[18,4291,4292,4293,4297,4298],{},"코딩 컨벤션 → ",[26,4294,4296],{"href":4295},".\u002FFRONTEND","FRONTEND.md"," · 기술 스택 → ",[26,4299,4301],{"href":4300},".\u002FSTACK","STACK.md",[18,4303,4304,4305,4308],{},"⚠️ 2026-05-18: 기존 malgn-notifications 시안 기반 디자인(indigo\u002Fsky\u002FNoto Sans KR)에서 ",[21,4306,4307],{},"이 핸드오프 디자인으로 전면 피벗",". 시안은 IA(페이지 목록\u002F라우트)에 한해 참조하되, 디자인 언어의 정본은 이 문서다.",[73,4310],{},[76,4312,4314],{"id":4313},"_0-적용-현황","0. 적용 현황",[101,4316,4317,4329],{},[104,4318,4319],{},[107,4320,4321,4324,4326],{},[110,4322,4323],{},"레이어",[110,4325,270],{},[110,4327,4328],{},"위치",[126,4330,4331,4353,4375,4393,4406,4429,4442,4459,4487,4522,4556,4576],{},[107,4332,4333,4344,4347],{},[131,4334,4335,4336,4339,4340,4343],{},"디자인 토큰 (",[32,4337,4338],{},":root"," + ",[32,4341,4342],{},"@theme",")",[131,4345,4346],{},"✅ 적용",[131,4348,4349],{},[26,4350,4352],{"href":4351},"..\u002Fapp\u002Fassets\u002Fcss\u002Fmain.css","app\u002Fassets\u002Fcss\u002Fmain.css",[107,4354,4355,4369,4371],{},[131,4356,4357,4358,4361,4362,4361,4365,4368],{},"컴포넌트 CSS (",[32,4359,4360],{},".card","\u002F",[32,4363,4364],{},".btn",[32,4366,4367],{},".table","…)",[131,4370,4346],{},[131,4372,4373],{},[26,4374,4352],{"href":4351},[107,4376,4377,4385,4387],{},[131,4378,4379,4380,4361,4383,4343],{},"Nuxt UI 색상 매핑 (",[32,4381,4382],{},"zinc",[32,4384,4382],{},[131,4386,4346],{},[131,4388,4389],{},[26,4390,4392],{"href":4391},"..\u002Fapp\u002Fapp.config.ts","app\u002Fapp.config.ts",[107,4394,4395,4398,4400],{},[131,4396,4397],{},"폰트 (Inter\u002FJetBrains Mono\u002FPretendard)",[131,4399,4346],{},[131,4401,4402],{},[26,4403,4405],{"href":4404},"..\u002Fnuxt.config.ts","nuxt.config.ts",[107,4407,4408,4411,4414],{},[131,4409,4410],{},"셸 (AppGnb\u002FAppFooter\u002Flayouts)",[131,4412,4413],{},"✅ Phase 2a 적용",[131,4415,4416,4420,4421,4420,4425],{},[26,4417,4419],{"href":4418},"..\u002Fapp\u002Fcomponents\u002FAppGnb.vue","components\u002FAppGnb.vue"," · ",[26,4422,4424],{"href":4423},"..\u002Fapp\u002Fcomponents\u002FAppFooter.vue","AppFooter.vue",[26,4426,4428],{"href":4427},"..\u002Fapp\u002Flayouts\u002F","layouts\u002F",[107,4430,4431,4434,4436],{},[131,4432,4433],{},"홈 대시보드 (레퍼런스)",[131,4435,4413],{},[131,4437,4438],{},[26,4439,4441],{"href":4440},"..\u002Fapp\u002Fpages\u002Fhome.vue","pages\u002Fhome.vue",[107,4443,4444,4447,4449],{},[131,4445,4446],{},"디자인 가이드 페이지 (라이브 카탈로그)",[131,4448,4346],{},[131,4450,4451,89,4455,4458],{},[26,4452,4454],{"href":4453},"..\u002Fapp\u002Fpages\u002Fguide.vue","pages\u002Fguide.vue",[32,4456,4457],{},"\u002Fguide",", 18섹션 sticky nav",[107,4460,4461,4464,4467],{},[131,4462,4463],{},"발송 공용 컴포넌트군 + SMS 발송 (레퍼런스)",[131,4465,4466],{},"✅ Phase 2b-1 적용",[131,4468,4469,4473,4474,4361,4477,4361,4480,4361,4483,4486],{},[26,4470,4472],{"href":4471},"..\u002Fapp\u002Fpages\u002Fsend\u002Fsms.vue","pages\u002Fsend\u002Fsms.vue",", ",[32,4475,4476],{},"AppModal",[32,4478,4479],{},"AppSendFormCard",[32,4481,4482],{},"AppRecipientCard",[32,4484,4485],{},"AppSmsTemplateDialog"," 등",[107,4488,4489,4492,4495],{},[131,4490,4491],{},"알림톡·RCS·이메일·PUSH·Flow 발송 + 채널 프리뷰",[131,4493,4494],{},"✅ Phase 2b-2 적용",[131,4496,4497,4473,4501,4361,4504,4361,4507,4361,4510,4361,4513,4361,4516,4361,4519],{},[26,4498,4500],{"href":4499},"..\u002Fapp\u002Fpages\u002Fsend\u002F","app\u002Fpages\u002Fsend\u002F",[32,4502,4503],{},"AppKakaoPreview",[32,4505,4506],{},"AppRcsPreview",[32,4508,4509],{},"AppEmailPreview",[32,4511,4512],{},"AppPushPreview",[32,4514,4515],{},"AppSegmented",[32,4517,4518],{},"AppKakaoTemplateDialog",[32,4520,4521],{},"AppTemplateVariableTextarea",[107,4523,4524,4527,4530],{},[131,4525,4526],{},"발송조회(5채널)·통계·주소록·충전·로그인·회원가입",[131,4528,4529],{},"✅ Phase 2b-3 적용",[131,4531,4532,4536,4537,4536,4541,4536,4545,4536,4549,4473,4553],{},[26,4533,4535],{"href":4534},"..\u002Fapp\u002Fpages\u002Fhistory\u002F","pages\u002Fhistory\u002F","·",[26,4538,4540],{"href":4539},"..\u002Fapp\u002Fpages\u002Fcontacts\u002Flist.vue","contacts\u002Flist",[26,4542,4544],{"href":4543},"..\u002Fapp\u002Fpages\u002Fcharge\u002F","charge\u002F",[26,4546,4548],{"href":4547},"..\u002Fapp\u002Fpages\u002Flogin\u002F","login\u002F",[26,4550,4552],{"href":4551},"..\u002Fapp\u002Fpages\u002Fsignup.vue","signup",[32,4554,4555],{},"AppHistoryView",[107,4557,4558,4561,4564],{},[131,4559,4560],{},"발송 6채널 UX 2차 폴리시 — 3-카드 재배치·채널 템플릿\u002F부가 다이얼로그·플로우 관리",[131,4562,4563],{},"✅ 적용 (2026-05-20)",[131,4565,4566,4473,4568,4536,4571,4536,4574,4486],{},[26,4567,4500],{"href":4499},[32,4569,4570],{},"AppPush*Dialog",[32,4572,4573],{},"AppFlow*Dialog",[32,4575,4509],{},[107,4577,4578,4581,4584],{},[131,4579,4580],{},"발신정보·메시지관리·캠페인·계정\u002F문의 등 잔여",[131,4582,4583],{},"⏳ 핸드오프 시안 없음 — 별도 협의",[131,4585,4586],{},[26,4587,4589],{"href":4588},"..\u002Fapp\u002Fpages\u002F","app\u002Fpages\u002F",[15,4591,4592],{},[18,4593,4594,4595,4598,4599,4361,4601,4361,4603,4605,4606,4609],{},"Phase 1(토큰·폰트·문서)·Phase 2(셸·홈·발송 6채널·조회\u002F통계\u002F주소록\u002F충전\u002F인증)는 모두 적용 완료. 모든 ",[32,4596,4597],{},"App*"," 컴포넌트가 ink\u002Faccent 신규 팔레트와 ",[32,4600,4360],{},[32,4602,4364],{},[32,4604,4367],{}," 클래스를 직접 사용한다(구 ",[32,4607,4608],{},"--gray-*"," 별칭 의존 없음).",[73,4611],{},[76,4613,4615],{"id":4614},"_1-디자인-원칙-7","1. 디자인 원칙 (7)",[101,4617,4618,4630],{},[104,4619,4620],{},[107,4621,4622,4624,4627],{},[110,4623,3846],{},[110,4625,4626],{},"원칙",[110,4628,4629],{},"의미",[126,4631,4632,4645,4658,4674,4687,4704,4720],{},[107,4633,4634,4637,4642],{},[131,4635,4636],{},"1",[131,4638,4639],{},[21,4640,4641],{},"Calm by default",[131,4643,4644],{},"채도 높은 색은 상태(error\u002Fsuccess)에만. 평상시 화면은 무채색.",[107,4646,4647,4650,4655],{},[131,4648,4649],{},"2",[131,4651,4652],{},[21,4653,4654],{},"Information first, decoration last",[131,4656,4657],{},"카드 그라데이션·아이콘 장식 금지. 데이터에 시선이 먼저.",[107,4659,4660,4663,4668],{},[131,4661,4662],{},"3",[131,4664,4665],{},[21,4666,4667],{},"Single accent, used sparingly",[131,4669,4670,4673],{},[32,4671,4672],{},"#00DC82"," 한 가지 액센트. 페이지당 4–6회 이내.",[107,4675,4676,4679,4684],{},[131,4677,4678],{},"4",[131,4680,4681],{},[21,4682,4683],{},"Typographic hierarchy over boxes",[131,4685,4686],{},"박스를 여러 겹 쌓기보다 폰트로 위계.",[107,4688,4689,4692,4697],{},[131,4690,4691],{},"5",[131,4693,4694],{},[21,4695,4696],{},"Tabular numbers everywhere",[131,4698,4699,4700,4703],{},"모든 수치는 JetBrains Mono + ",[32,4701,4702],{},"tabular-nums",".",[107,4705,4706,4709,4714],{},[131,4707,4708],{},"6",[131,4710,4711],{},[21,4712,4713],{},"Hairline borders > shadows",[131,4715,4716,4719],{},[32,4717,4718],{},"1px solid #ececec","가 기본. 그림자는 floating UI에만.",[107,4721,4722,4725,4730],{},[131,4723,4724],{},"7",[131,4726,4727],{},[21,4728,4729],{},"AI is a quiet partner",[131,4731,4732],{},"AI 요소는 ✨ + 그린 한 가지로만. 보라색·무지개 절대 금지.",[237,4734,4736],{"id":4735},"do-dont","Do \u002F Don't",[18,4738,4739,4742,4743,4745,4746,4749],{},[21,4740,4741],{},"✅ Do"," — 1px hairline으로 카드 분리 · 숫자는 모노+",[32,4744,4702],{}," · 액센트는 의미 있는 한 곳에만(AI\u002FLive\u002F+delta\u002FPrimary CTA) · AI 표식엔 항상 ",[32,4747,4748],{},"Sparkles","+그린 · 빈 공간을 두려워하지 않기 · 텍스트 톤 한 화면 3단계만(Primary\u002FSecondary\u002FMuted).",[18,4751,4752,4755,4756,4759],{},[21,4753,4754],{},"❌ Don't"," — 카드에 drop shadow · 컬러풀(다색) 아이콘 · 보라\u002F무지개 AI · 12px 미만 본문 · ",[32,4757,4758],{},"ink-300"," 본문 텍스트(축 라벨 한정) · 화면당 액센트 6회 초과 · 컨테이너 radius 12px 이상 · 단순 강조 텍스트에 액센트 · 헤더 등 큰 영역에 액센트 채움.",[73,4761],{},[76,4763,4765],{"id":4764},"_2-컬러-시스템","2. 컬러 시스템",[18,4767,4768,4769,51,4772,4774,4775,4778,4779,4782],{},"토큰 정본 = ",[26,4770,4771],{"href":4351},"main.css",[32,4773,4338],{},". 색은 ",[21,4776,4777],{},"항상 CSS 변수","로. ",[32,4780,4781],{},"bg-[#xxxxxx]"," 임의값 금지.",[237,4784,4786,4787,4343],{"id":4785},"_21-ink-scale-무채색-11단-tailwind-zinc","2.1 Ink Scale (무채색 11단 ≈ Tailwind ",[32,4788,4382],{},[101,4790,4791,4804],{},[104,4792,4793],{},[107,4794,4795,4798,4801],{},[110,4796,4797],{},"토큰",[110,4799,4800],{},"값",[110,4802,4803],{},"용도",[126,4805,4806,4821,4836,4851,4866,4881,4896,4911,4926,4941,4956,4971,4988],{},[107,4807,4808,4813,4818],{},[131,4809,4810],{},[32,4811,4812],{},"--ink-900",[131,4814,4815],{},[32,4816,4817],{},"#0a0a0a",[131,4819,4820],{},"Primary 텍스트, Primary 버튼 BG",[107,4822,4823,4828,4833],{},[131,4824,4825],{},[32,4826,4827],{},"--ink-800",[131,4829,4830],{},[32,4831,4832],{},"#18181b",[131,4834,4835],{},"Hover(primary), 강조 텍스트",[107,4837,4838,4843,4848],{},[131,4839,4840],{},[32,4841,4842],{},"--ink-700",[131,4844,4845],{},[32,4846,4847],{},"#27272a",[131,4849,4850],{},"라이트 표면 본문",[107,4852,4853,4858,4863],{},[131,4854,4855],{},[32,4856,4857],{},"--ink-600",[131,4859,4860],{},[32,4861,4862],{},"#3f3f46",[131,4864,4865],{},"보조 텍스트",[107,4867,4868,4873,4878],{},[131,4869,4870],{},[32,4871,4872],{},"--ink-500",[131,4874,4875],{},[32,4876,4877],{},"#52525b",[131,4879,4880],{},"3차 텍스트, 라벨",[107,4882,4883,4888,4893],{},[131,4884,4885],{},[32,4886,4887],{},"--ink-400",[131,4889,4890],{},[32,4891,4892],{},"#71717a",[131,4894,4895],{},"muted, placeholder, 아이콘 기본",[107,4897,4898,4903,4908],{},[131,4899,4900],{},[32,4901,4902],{},"--ink-300",[131,4904,4905],{},[32,4906,4907],{},"#a1a1aa",[131,4909,4910],{},"disabled 텍스트, 축 라벨",[107,4912,4913,4918,4923],{},[131,4914,4915],{},[32,4916,4917],{},"--ink-200",[131,4919,4920],{},[32,4921,4922],{},"#d4d4d8",[131,4924,4925],{},"disabled BG, hover border",[107,4927,4928,4933,4938],{},[131,4929,4930],{},[32,4931,4932],{},"--ink-100",[131,4934,4935],{},[32,4936,4937],{},"#e4e4e7",[131,4939,4940],{},"progress track, 옅은 divider",[107,4942,4943,4948,4953],{},[131,4944,4945],{},[32,4946,4947],{},"--ink-50",[131,4949,4950],{},[32,4951,4952],{},"#f4f4f5",[131,4954,4955],{},"subtle BG (segmented\u002Fhover)",[107,4957,4958,4963,4968],{},[131,4959,4960],{},[32,4961,4962],{},"--paper",[131,4964,4965],{},[32,4966,4967],{},"#fafaf9",[131,4969,4970],{},"앱 배경",[107,4972,4973,4978,4983],{},[131,4974,4975],{},[32,4976,4977],{},"--line",[131,4979,4980],{},[32,4981,4982],{},"#ececec",[131,4984,4985],{},[21,4986,4987],{},"hairline border (1px)",[107,4989,4990,4995,5000],{},[131,4991,4992],{},[32,4993,4994],{},"--white",[131,4996,4997],{},[32,4998,4999],{},"#ffffff",[131,5001,5002],{},"표면 (카드·탑바·입력)",[237,5004,5006],{"id":5005},"_22-single-accent-green","2.2 Single Accent — Green",[101,5008,5009,5019],{},[104,5010,5011],{},[107,5012,5013,5015,5017],{},[110,5014,4797],{},[110,5016,4800],{},[110,5018,4803],{},[126,5020,5021,5035,5050],{},[107,5022,5023,5028,5032],{},[131,5024,5025],{},[32,5026,5027],{},"--accent",[131,5029,5030],{},[32,5031,4672],{},[131,5033,5034],{},"AI 표식 · Live · +delta · Primary CTA",[107,5036,5037,5042,5047],{},[131,5038,5039],{},[32,5040,5041],{},"--accent-soft",[131,5043,5044],{},[32,5045,5046],{},"#e6fbf2",[131,5048,5049],{},"pill\u002F배경",[107,5051,5052,5057,5062],{},[131,5053,5054],{},[32,5055,5056],{},"--accent-ink",[131,5058,5059],{},[32,5060,5061],{},"#007a48",[131,5063,5064],{},"라이트 위 액센트 텍스트(대비 보강)",[18,5066,5067,5069,5070,5073,5074,4361,5077,5080],{},[32,5068,4342],{},"에 ",[32,5071,5072],{},"--color-accent","로도 노출되어 ",[32,5075,5076],{},"bg-accent",[32,5078,5079],{},"text-accent"," 유틸 사용 가능.",[15,5082,5083],{},[18,5084,5085,5088],{},[21,5086,5087],{},"액센트 사용",": ✅ AI UI · open\u002Fonline\u002Flive 상태 · 긍정 변동률 · Primary CTA(페이지당 ≤1). ❌ 단순 강조 텍스트 색 · 큰 영역 채움(헤더 배경 등).",[237,5090,5092,5093,4343],{"id":5091},"_23-semantic-status-soft-bg-line-border-ink-text","2.3 Semantic \u002F Status (",[32,5094,5095],{},"*-soft BG + *-line border + *-ink text",[101,5097,5098,5114],{},[104,5099,5100],{},[107,5101,5102,5105,5108,5111],{},[110,5103,5104],{},"Status",[110,5106,5107],{},"soft",[110,5109,5110],{},"line",[110,5112,5113],{},"ink",[126,5115,5116,5134,5154,5174,5194],{},[107,5117,5118,5121,5125,5130],{},[131,5119,5120],{},"Success",[131,5122,5123],{},[32,5124,5046],{},[131,5126,5127],{},[32,5128,5129],{},"rgba(0,220,130,.3)",[131,5131,5132],{},[32,5133,5061],{},[107,5135,5136,5139,5144,5149],{},[131,5137,5138],{},"Warning",[131,5140,5141],{},[32,5142,5143],{},"#fffbeb",[131,5145,5146],{},[32,5147,5148],{},"#fde68a",[131,5150,5151],{},[32,5152,5153],{},"#b45309",[107,5155,5156,5159,5164,5169],{},[131,5157,5158],{},"Danger",[131,5160,5161],{},[32,5162,5163],{},"#fff1f2",[131,5165,5166],{},[32,5167,5168],{},"#fecdd3",[131,5170,5171],{},[32,5172,5173],{},"#be123c",[107,5175,5176,5179,5184,5189],{},[131,5177,5178],{},"Info",[131,5180,5181],{},[32,5182,5183],{},"#eff6ff",[131,5185,5186],{},[32,5187,5188],{},"#bfdbfe",[131,5190,5191],{},[32,5192,5193],{},"#1d4ed8",[107,5195,5196,5199,5203,5207],{},[131,5197,5198],{},"Neutral",[131,5200,5201],{},[32,5202,4952],{},[131,5204,5205],{},[32,5206,4937],{},[131,5208,5209],{},[32,5210,4862],{},[237,5212,5214],{"id":5213},"_24-channel-colors-점-표시-전용","2.4 Channel Colors (점 표시 전용)",[18,5216,5217,5218,5221,5222,5225,5226,5229,5230,5233,5234,5236,5237,5239,5240,5243],{},"SMS ",[32,5219,5220],{},"#3b82f6"," · Kakao ",[32,5223,5224],{},"#fbbf24"," · RCS ",[32,5227,5228],{},"#8b5cf6"," · Email ",[32,5231,5232],{},"#f59e0b"," · Push ",[32,5235,4672],{}," · Flow ",[32,5238,4817],{},". 채널은 ",[21,5241,5242],{},"도트로만"," 구분, 큰 색 채움 금지.",[73,5245],{},[76,5247,5249],{"id":5248},"_3-타이포그래피","3. 타이포그래피",[5251,5252,5257],"pre",{"className":5253,"code":5255,"language":5256},[5254],"language-text","--font-sans:  \"Inter\", \"Pretendard Variable\", \"Pretendard\", -apple-system, \"Noto Sans KR\", sans-serif;\n--font-mono:  \"JetBrains Mono\", ui-monospace, monospace;\n--font-serif: \"Instrument Serif\", Georgia, serif;\n--font-base:  13px;\n","text",[32,5258,5255],{"__ignoreMap":4208},[81,5260,5261,5270,5279,5285],{},[84,5262,5263,5266,5267,4703],{},[21,5264,5265],{},"Inter"," — 모든 UI 텍스트. ",[32,5268,5269],{},"font-feature-settings: \"cv11\",\"ss01\",\"ss03\"",[84,5271,5272,5275,5276,5278],{},[21,5273,5274],{},"JetBrains Mono"," — 모든 숫자·ID·kbd. ",[32,5277,4702],{}," 필수.",[84,5280,5281,5284],{},[21,5282,5283],{},"Pretendard"," — 한국어 fallback (Inter 다음).",[84,5286,5287,5290],{},[21,5288,5289],{},"Instrument Serif"," — 대형 디스플레이 한정 (어드민에서 거의 미사용).",[237,5292,5294],{"id":5293},"type-scale","Type Scale",[101,5296,5297,5313],{},[104,5298,5299],{},[107,5300,5301,5304,5307,5310],{},[110,5302,5303],{},"Role",[110,5305,5306],{},"Size \u002F LH",[110,5308,5309],{},"Weight",[110,5311,5312],{},"Tracking",[126,5314,5315,5329,5342,5355,5367,5379,5392,5406],{},[107,5316,5317,5320,5323,5326],{},[131,5318,5319],{},"Display (page H1)",[131,5321,5322],{},"22 \u002F 28",[131,5324,5325],{},"600",[131,5327,5328],{},"-0.01em",[107,5330,5331,5334,5337,5339],{},[131,5332,5333],{},"Section H3 \u002F 카드 타이틀",[131,5335,5336],{},"13 \u002F 20",[131,5338,5325],{},[131,5340,5341],{},"-0.005em",[107,5343,5344,5347,5349,5352],{},[131,5345,5346],{},"Body",[131,5348,5336],{},[131,5350,5351],{},"400",[131,5353,5354],{},"0",[107,5356,5357,5360,5363,5365],{},[131,5358,5359],{},"Body large (AI 헤드라인)",[131,5361,5362],{},"15 \u002F 24",[131,5364,5351],{},[131,5366,5354],{},[107,5368,5369,5372,5375,5377],{},[131,5370,5371],{},"Label \u002F Caption",[131,5373,5374],{},"12 \u002F 16",[131,5376,5351],{},[131,5378,5354],{},[107,5380,5381,5384,5387,5389],{},[131,5382,5383],{},"Micro (eyebrow)",[131,5385,5386],{},"10–11 \u002F 14",[131,5388,5325],{},[131,5390,5391],{},"0.06em, UPPERCASE, mono",[107,5393,5394,5397,5400,5403],{},[131,5395,5396],{},"KPI Number",[131,5398,5399],{},"28 \u002F 28",[131,5401,5402],{},"500 (mono)",[131,5404,5405],{},"-0.02em",[107,5407,5408,5411,5414,5416],{},[131,5409,5410],{},"Inline mono (ID\u002F%\u002Fkbd)",[131,5412,5413],{},"11–12",[131,5415,5402],{},[131,5417,5354],{},[18,5419,5420,5423,5424,5427,5428,5431,5432,5435,5436,5439,5440],{},[21,5421,5422],{},"Color × Type",": Heading→",[32,5425,5426],{},"ink-900"," · Body→",[32,5429,5430],{},"ink-700\u002F800"," · Secondary→",[32,5433,5434],{},"ink-400~500"," · Muted→",[32,5437,5438],{},"ink-300~400",". ",[21,5441,5442],{},"한 화면 3단계 톤 초과 금지.",[73,5444],{},[76,5446,5448],{"id":5447},"_4-간격-라운드-그림자","4. 간격 · 라운드 · 그림자",[237,5450,5452],{"id":5451},"_41-spacing-tailwind-4px-base","4.1 Spacing (Tailwind 4px base)",[18,5454,5455,5458],{},[32,5456,5457],{},"1=4 · 1.5=6 · 2=8 · 2.5=10 · 3=12 · 4=16 · 5=20 · 6=24 · 8=32"," (px). 카드 패딩 16~20, 카드 간 24, Shell 패딩 32.",[237,5460,5462],{"id":5461},"_42-radius","4.2 Radius",[101,5464,5465,5475],{},[104,5466,5467],{},[107,5468,5469,5472],{},[110,5470,5471],{},"요소",[110,5473,5474],{},"radius",[126,5476,5477,5488,5499,5510],{},[107,5478,5479,5482],{},[131,5480,5481],{},"Pill \u002F Badge \u002F Button \u002F Input",[131,5483,5484,5487],{},[32,5485,5486],{},"--r-md"," 6px",[107,5489,5490,5493],{},[131,5491,5492],{},"Card \u002F Section \u002F Modal",[131,5494,5495,5498],{},[32,5496,5497],{},"--r-lg"," 8px",[107,5500,5501,5504],{},[131,5502,5503],{},"kbd \u002F 작은 요소",[131,5505,5506,5509],{},[32,5507,5508],{},"--r-sm"," 4px",[107,5511,5512,5515],{},[131,5513,5514],{},"Avatar",[131,5516,5517,5520],{},[32,5518,5519],{},"--r-full"," 9999px",[237,5522,5524,5525],{"id":5523},"_43-elevation-카드에-그림자-금지","4.3 Elevation — ",[21,5526,5527],{},"카드에 그림자 금지",[5251,5529,5532],{"className":5530,"code":5531,"language":5256},[5254],"--shadow-soft:    0 1px 2px rgba(0,0,0,.04)    \u002F* tooltip, segmented thumb *\u002F\n--shadow-popover: 0 4px 12px rgba(0,0,0,.08)   \u002F* dropdown, popover, toast *\u002F\n--shadow-modal:   0 12px 32px rgba(0,0,0,.12)  \u002F* modal *\u002F\n",[32,5533,5531],{"__ignoreMap":4208},[18,5535,5536,5537,5540],{},"카드·섹션은 항상 ",[32,5538,5539],{},"1px solid var(--line)"," hairline으로 분리. 그림자는 floating UI 전용.",[73,5542],{},[76,5544,5546],{"id":5545},"_5-레이아웃","5. 레이아웃",[237,5548,5550],{"id":5549},"_51-app-shell","5.1 App Shell",[5251,5552,5555],{"className":5553,"code":5554,"language":5256},[5254],"┌────────────────────────────────────────────────────┐\n│ Topbar (56px, sticky, bg-white\u002F80 backdrop-blur, ─b)│\n├────────────────────────────────────────────────────┤\n│ Content  (max-w 1400px · px-32 · py-32)            │\n└────────────────────────────────────────────────────┘\n",[32,5556,5554],{"__ignoreMap":4208},[81,5558,5559,5573,5576],{},[84,5560,5561,5562,4339,5565,5568,5569,5572],{},"Topbar: 56px, sticky, ",[32,5563,5564],{},"rgba(255,255,255,.8)",[32,5566,5567],{},"backdrop-blur(8px)",", 하단 1px line. ",[21,5570,5571],{},"단일 행"," GNB(유틸바 없음, 시안의 2단 GNB 폐기).",[84,5574,5575],{},"Content: max 1400px, 좌우 32px, 상하 32px.",[84,5577,5578],{},"모바일(\u003C1024px): GNB 메뉴 숨김 → 햄버거 + 좌측 Drawer(280px).",[237,5580,5582,5583,4343],{"id":5581},"_52-2-column-content-2col","5.2 2-Column (",[32,5584,5585],{},".content-2col",[18,5587,5588,5591],{},[32,5589,5590],{},"minmax(0,1fr) \u002F 320px",", gap 24px. \u003C1024px에서 1단 (aside가 위로).",[237,5593,5595],{"id":5594},"_53-density-spacious-linearvercel","5.3 Density (Spacious — Linear\u002FVercel)",[18,5597,5598,5599,5602,5603,5606],{},"테이블 row ~52px(",[32,5600,5601],{},"py-3",") · Nav item 32px · Button 28\u002F32\u002F36 (sm\u002Fmd\u002Flg) · Input 36px(",[32,5604,5605],{},"h-9",").",[237,5608,5610],{"id":5609},"_54-페이지-레이아웃-타입-a-b-c","5.4 페이지 레이아웃 타입 (A · B · C)",[18,5612,5613,5614,5617,5618,5621],{},"화면의 골격(헤더 · 내비게이션 · 콘텐츠 구성)을 ",[21,5615,5616],{},"3가지 페이지 레이아웃 타입","으로 정의한다. 새 화면은 셋 중 하나를 따른다. (§6.5의 \"테이블 스타일 A\u002FB\u002FC\"와는 ",[21,5619,5620],{},"별개 축"," — 이쪽은 페이지 전체 골격, 그쪽은 목록 테이블 패턴.)",[252,5623,5625],{"id":5624},"a-페이지-표준-페이지","A 페이지 — 표준 페이지",[18,5627,5628,5629,5632,5633,5636,5637,5640,5641,5644],{},"전체 GNB + ",[32,5630,5631],{},".page-header","(크럼 + ",[32,5634,5635],{},"\u003Ch1>"," + 선택 설명) + ",[21,5638,5639],{},"단일 콘텐츠 열",". 앱 화면 대부분이 이 타입. ",[32,5642,5643],{},"default"," 레이아웃.",[5251,5646,5649],{"className":5647,"code":5648,"language":5256},[5254],"┌────────────────────────────────────┐\n│ GNB (전체 메뉴 · 문의 · 충전 · 계정)│\n├────────────────────────────────────┤\n│ crumb \u002F h1 \u002F desc                  │\n│ ┌────────────────────────────────┐ │\n│ │ 콘텐츠 (단일 열)               │ │\n│ └────────────────────────────────┘ │\n└────────────────────────────────────┘\n",[32,5650,5648],{"__ignoreMap":4208},[81,5652,5653,5660],{},[84,5654,5655,5656,5659],{},"콘텐츠: ",[32,5657,5658],{},".app-container .page-body"," 단일 열.",[84,5661,5662],{},"레퍼런스: 발송 · 조회 · 메시지 관리 · 사이트맵 등 대부분.",[252,5664,5666],{"id":5665},"b-페이지-좌측-메뉴-페이지-lnb","B 페이지 — 좌측 메뉴 페이지 (LNB)",[18,5668,5628,5669,5672,5673,5675],{},[21,5670,5671],{},"2단 구성","(좌측 sticky 서브메뉴 + 우측 콘텐츠 패널). 하위 항목이 여러 개인 페이지 그룹에 쓴다. ",[32,5674,5643],{}," 레이아웃 + 공용 셸 컴포넌트.",[5251,5677,5680],{"className":5678,"code":5679,"language":5256},[5254],"┌────────────────────────────────────┐\n│ GNB (전체 메뉴 · 문의 · 충전 · 계정)│\n├────────────────────────────────────┤\n│ crumb \u002F h1 (그룹명)                │\n│ ┌────────┬───────────────────────┐ │\n│ │ 서브   │ 콘텐츠 패널           │ │\n│ │ 메뉴   │ (흰 카드 · 1px 보더)  │ │\n│ │(220px) │                       │ │\n│ └────────┴───────────────────────┘ │\n└────────────────────────────────────┘\n",[32,5681,5679],{"__ignoreMap":4208},[81,5683,5684,5690],{},[84,5685,5686,5687,4703],{},"좌측: sticky 서브메뉴(220px), 각 항목은 ",[21,5688,5689],{},"독립 라우트",[84,5691,5692,5693,89,5697,5606],{},"레퍼런스: 나의 페이지 (",[26,5694,5696],{"href":5695},"..\u002Fapp\u002Fcomponents\u002FAppMyPageShell.vue","AppMyPageShell.vue",[32,5698,5699],{},"\u002Faccount\u002F*",[252,5701,5703],{"id":5702},"c-페이지-독립-페이지-standalone","C 페이지 — 독립 페이지 (Standalone)",[18,5705,5706,5707,5710,5711,5644],{},"앱 GNB 없이 ",[21,5708,5709],{},"로고만 있는 sticky 상단 바"," + 콘텐츠 + 간단 푸터. 새 창으로 띄워 단독으로 보는 참고 · 문서 화면. ",[32,5712,5713],{},"blank",[5251,5715,5718],{"className":5716,"code":5717,"language":5256},[5254],"┌────────────────────────────────────┐\n│ 로고만 (56px, sticky · GNB 없음)    │\n├────────────────────────────────────┤\n│ ┌────────────────────────────────┐ │\n│ │ 콘텐츠 (자체 헤더 · 푸터)       │ │\n│ └────────────────────────────────┘ │\n└────────────────────────────────────┘\n",[32,5719,5717],{"__ignoreMap":4208},[81,5721,5722,5725,5735],{},[84,5723,5724],{},"상단: 로고 전용 바, GNB · 계정 메뉴 없음. 헤더 · 푸터를 페이지가 직접 구성.",[84,5726,5727,5730,5731,5734],{},[21,5728,5729],{},"단일 루트로 감싸"," sticky 요소가 페이지 끝까지 고정되게 한다(",[32,5732,5733],{},"#__nuxt"," 높이 100% 제약 회피).",[84,5736,5737,5738,89,5742,5606],{},"레퍼런스: 운영 가이드 (",[26,5739,5741],{"href":5740},"..\u002Fapp\u002Fpages\u002Fhelp.vue","help.vue",[32,5743,5744],{},"\u002Fhelp",[252,5746,5748],{"id":5747},"a-b-c-비교","A · B · C 비교",[101,5750,5751,5767],{},[104,5752,5753],{},[107,5754,5755,5757,5761,5764],{},[110,5756],{},[110,5758,5760],{"align":5759},"center","A 페이지",[110,5762,5763],{"align":5759},"B 페이지",[110,5765,5766],{"align":5759},"C 페이지",[126,5768,5769,5781,5793,5811,5824],{},[107,5770,5771,5774,5776,5778],{},[131,5772,5773],{},"전체 GNB",[131,5775,3869],{"align":5759},[131,5777,3869],{"align":5759},[131,5779,5780],{"align":5759},"❌ (로고만)",[107,5782,5783,5786,5789,5791],{},[131,5784,5785],{},"좌측 서브메뉴",[131,5787,5788],{"align":5759},"❌",[131,5790,3869],{"align":5759},[131,5792,5788],{"align":5759},[107,5794,5795,5798,5802,5807],{},[131,5796,5797],{},"레이아웃",[131,5799,5800],{"align":5759},[32,5801,5643],{},[131,5803,5804,5806],{"align":5759},[32,5805,5643],{}," + 셸",[131,5808,5809],{"align":5759},[32,5810,5713],{},[107,5812,5813,5815,5818,5821],{},[131,5814,4803],{},[131,5816,5817],{"align":5759},"일반 화면",[131,5819,5820],{"align":5759},"하위 메뉴가 많은 그룹",[131,5822,5823],{"align":5759},"단독 참고 · 문서",[107,5825,5826,5829,5832,5837],{},[131,5827,5828],{},"레퍼런스",[131,5830,5831],{"align":5759},"대부분 화면",[131,5833,5834],{"align":5759},[32,5835,5836],{},"AppMyPageShell",[131,5838,5839],{"align":5759},[32,5840,5744],{},[15,5842,5843],{},[18,5844,5845,5846,5848],{},"인증(로그인 · 회원가입 · 서비스 담당자 등록)과 비로그인 랜딩(",[32,5847,4361],{},")은 별도 — 중앙 정렬 카드 \u002F 마케팅 레이아웃으로, A · B · C 분류에 포함하지 않는다.",[73,5850],{},[76,5852,5854],{"id":5853},"_6-컴포넌트-패턴","6. 컴포넌트 패턴",[15,5856,5857],{},[18,5858,5859,5861,5862,4703],{},[32,5860,4597],{}," 컴포넌트는 아래 클래스\u002F토큰을 직접 사용한다(적용 완료). 클래스 정본 = ",[26,5863,4771],{"href":4351},[237,5865,5867,5868,4343],{"id":5866},"_61-버튼-btn","6.1 버튼 (",[32,5869,4364],{},[101,5871,5872,5884],{},[104,5873,5874],{},[107,5875,5876,5879,5881],{},[110,5877,5878],{},"변형",[110,5880,4803],{},[110,5882,5883],{},"시각",[126,5885,5886,5901,5920,5942,5961,5976],{},[107,5887,5888,5893,5896],{},[131,5889,5890],{},[32,5891,5892],{},".btn-primary",[131,5894,5895],{},"기본 액션(저장\u002F발송)",[131,5897,5898,5900],{},[32,5899,5426],{}," BG \u002F 흰 텍스트",[107,5902,5903,5908,5911],{},[131,5904,5905],{},[32,5906,5907],{},".btn-accent",[131,5909,5910],{},"AI \u002F Primary CTA (페이지 ≤1)",[131,5912,5913,5916,5917,5919],{},[32,5914,5915],{},"accent"," BG \u002F ",[32,5918,5426],{}," 텍스트",[107,5921,5922,5933,5936],{},[131,5923,5924,4536,5927,4536,5930],{},[32,5925,5926],{},".btn-soft",[32,5928,5929],{},".btn-outline",[32,5931,5932],{},".btn-neutral",[131,5934,5935],{},"보조\u002F취소",[131,5937,5938,5939,5941],{},"흰 BG \u002F ",[32,5940,5110],{}," 보더",[107,5943,5944,5949,5952],{},[131,5945,5946],{},[32,5947,5948],{},".btn-error",[131,5950,5951],{},"위험(삭제)",[131,5953,5938,5954,5957,5958],{},[32,5955,5956],{},"danger-line"," 보더 \u002F ",[32,5959,5960],{},"danger-ink",[107,5962,5963,5968,5971],{},[131,5964,5965],{},[32,5966,5967],{},".btn-error-solid",[131,5969,5970],{},"강한 위험 확정",[131,5972,5973,5900],{},[32,5974,5975],{},"danger",[107,5977,5978,5983,5986],{},[131,5979,5980],{},[32,5981,5982],{},".btn-ghost",[131,5984,5985],{},"아이콘·메뉴 진입",[131,5987,5988,5989],{},"투명 → hover ",[32,5990,5991],{},"ink-50",[18,5993,5994,5995,5998,5999,6002,6003,6006,6007,6010],{},"크기 ",[32,5996,5997],{},".btn-sm","(28) \u002F 기본(32) \u002F ",[32,6000,6001],{},".btn-lg","(36) · ",[32,6004,6005],{},".btn-icon"," 정사각.\nNuxt UI 대응: ",[32,6008,6009],{},"UButton color=\"neutral\"","(기본=ink), AI\u002FCTA만 별도 accent 처리.",[237,6012,6014,6015,4343],{"id":6013},"_62-카드-card","6.2 카드 (",[32,6016,4360],{},[18,6018,6019,6020,4339,6023,5439,6026,6029,6030,6033,6034,6037,6038],{},"흰 BG + ",[32,6021,6022],{},"1px line",[32,6024,6025],{},"r-lg",[32,6027,6028],{},".card-header","(step + title + required + hint) \u002F ",[32,6031,6032],{},".card-body","(20px). 잠금 시 ",[32,6035,6036],{},".card-body.locked","(중앙 안내). ",[21,6039,6040],{},"그림자·그라데이션 없음.",[237,6042,6044,6045,4343],{"id":6043},"_63-폼-form-row","6.3 폼 (",[32,6046,6047],{},".form-row",[18,6049,6050,6053,6054,89,6057,6060,6061,6064,6065,6067,6068,6071,6072,6074,6075,6078,6079,6078,6082,6085],{},[32,6051,6052],{},"label 120px \u002F field 1fr"," 그리드, row 사이 1px line. 입력은 ",[32,6055,6056],{},".input\u002F.select\u002F.textarea",[32,6058,6059],{},"paper"," BG, focus 시 ",[32,6062,6063],{},"white"," BG + ",[32,6066,4758],{}," border(outline 없음). 필수 ",[32,6069,6070],{},"*","는 ",[32,6073,5975],{},". 에러는 인풋 아래 빨간 텍스트. Nuxt UI: ",[32,6076,6077],{},"UForm","+",[32,6080,6081],{},"UFormField",[32,6083,6084],{},"UInput","+Zod.",[237,6087,6089],{"id":6088},"_64-모달-drawer","6.4 모달 \u002F Drawer",[18,6091,6092,6095,6096,6100,6101,6103,6104,6107,6108,6111,6112,6114,6115,4361,6118,6121,6122,6125],{},[32,6093,6094],{},".modal","(r-lg, shadow-modal, slideUp 180ms) — header\u002Fbody\u002Ffooter. footer 확인 버튼은 ",[21,6097,6098],{},[32,6099,5892],{},"(",[32,6102,5426],{},") — 구 ",[32,6105,6106],{},".btn-sky"," 별칭은 폐기·제거됨. 모바일 메뉴는 좌측 ",[32,6109,6110],{},".drawer","(280px, slideRight). 팝업·다이얼로그는 모두 자체 ",[32,6113,4476],{}," 기반(",[32,6116,6117],{},"AppConfirmDialog",[32,6119,6120],{},"App*Dialog",") — ",[32,6123,6124],{},"USlideover","는 사용하지 않으며 모바일 GNB 드로어에만 한정.",[237,6127,6129,6130,4343],{"id":6128},"_65-테이블-table","6.5 테이블 (",[32,6131,4367],{},[18,6133,6134,6135,6138,6139,6142,6143,6145,6146,6149,6150,4703],{},"헤더: 투명 BG, ",[32,6136,6137],{},"10px mono UPPERCASE ink-400",", 하단 1px line. row: ",[32,6140,6141],{},"td 14px",", row 사이 1px line, hover ",[32,6144,6059],{},", 선택 ",[32,6147,6148],{},"accent-soft",". 숫자 셀 ",[32,6151,6152],{},".cell-mono",[252,6154,6156],{"id":6155},"a-테이블-스타일-조회관리-목록-표준","A 테이블 스타일 — 조회·관리 목록 표준",[18,6158,6159,6160,6163,6164,6168,6169,6172,6173,6176],{},"발송 조회·이력·관리 등 ",[21,6161,6162],{},"목록 화면의 정본 테이블 패턴",". 레퍼런스 구현 = ",[26,6165,6167],{"href":6166},"..\u002Fapp\u002Fcomponents\u002FAppHistoryView.vue","AppHistoryView.vue","(발송 조회 5채널 공용), 테이블 요소에 ",[32,6170,6171],{},"data-table-style=\"a\"","로 명시. 화면은 항상 ",[21,6174,6175],{},"3개 영역","으로 구성된다 — ⓐ 검색(필터) 영역 → ⓑ 액션 영역 → ⓒ 테이블 본체.",[5251,6178,6181],{"className":6179,"code":6180,"language":5256},[5254],"ⓐ .filter-bar              검색(필터) 영역 — 독립 카드, 테이블 카드 \"위\"\n─────────────────────────────────────────────────────────\nⓑⓒ .list-card              흰 카드 · 1px line · r-lg · overflow hidden\n   ├─ ⓑ .list-toolbar       액션 영역 — 카드 \"안\" 최상단 (border-bottom 1px)\n   ├─ ⓒ .list-table-scroll  table.table.list-table (가로 스크롤)\n   └─    페이지네이션         카드 하단\n",[32,6182,6180],{"__ignoreMap":4208},[6184,6185,6187,6188],"h5",{"id":6186},"ⓐ-상단-검색필터-영역-filter-bar","ⓐ 상단 검색(필터) 영역 — ",[32,6189,6190],{},".filter-bar",[18,6192,6193,6194,6197,6198,6200,6201,6204],{},"목록 화면 최상단의 ",[21,6195,6196],{},"독립 카드","(흰 BG · 1px line · ",[32,6199,6025],{}," · padding 12 · ",[32,6202,6203],{},"margin-bottom 16","). 테이블 카드와 분리된 별개 블록이다.",[81,6206,6207,6213,6280],{},[84,6208,6209,6210,4703],{},"레이아웃: ",[32,6211,6212],{},"display:flex; gap:8; flex-wrap:wrap; align-items:center",[84,6214,6215,6216,6219,6220,6219,6223,4285,6226],{},"좌 → 우 순서: ",[21,6217,6218],{},"필터 셀렉트들"," → ",[21,6221,6222],{},"날짜 범위",[21,6224,6225],{},"액션 버튼",[81,6227,6228,6235,6254],{},[84,6229,6230,6231,6234],{},"필터 셀렉트(",[32,6232,6233],{},".select.fb-select",", 폭 120px) — 채널\u002F상태\u002F시점\u002F목적 등. 첫 옵션은 항목명 placeholder.",[84,6236,6237,6238,6121,6241,6244,6245,6100,6248,6251,6252,4703],{},"날짜 범위(",[32,6239,6240],{},".fb-daterange",[32,6242,6243],{},"AppDateTimePicker","(시·분 표시, width 190) + ",[32,6246,6247],{},"~",[32,6249,6250],{},".fb-tilde",") + ",[32,6253,6243],{},[84,6255,6256,6257,4473,6260,6263,6264,6100,6267,6251,6270,6100,6273,4339,6276,6279],{},"액션(",[32,6258,6259],{},".fb-actions",[32,6261,6262],{},"margin-left:auto","로 우측 정렬) — ",[32,6265,6266],{},"초기화",[32,6268,6269],{},".btn-neutral .btn-sm",[32,6271,6272],{},"검색하기",[32,6274,6275],{},".btn-primary .btn-sm",[32,6277,6278],{},"i-lucide-search"," 아이콘).",[84,6281,6282,6283,6286],{},"검색 버튼 라벨은 ",[21,6284,6285],{},"\"검색하기\"","(아이콘 포함)로 통일. \"조회\" 금지.",[6184,6288,6290,6291],{"id":6289},"ⓑ-테이블-상단-액션-영역-list-toolbar","ⓑ 테이블 상단 액션 영역 — ",[32,6292,6293],{},".list-toolbar",[18,6295,6296,51,6299,6302,6303,6306,6307,4473,6310,6313],{},[32,6297,6298],{},".list-card",[21,6300,6301],{},"내부 최상단"," 행(",[32,6304,6305],{},"border-bottom: 1px",", padding ",[32,6308,6309],{},"10 12",[32,6311,6312],{},"display:flex; justify-content:space-between","). 좌·우 2그룹.",[81,6315,6316,6327,6352],{},[84,6317,6318,47,6321,6100,6324,5606],{},[21,6319,6320],{},"좌측",[32,6322,6323],{},"총 N건",[32,6325,6326],{},".toolbar-count",[84,6328,6329,47,6332,4420,6335,4420,6338,6341,6342,4420,6345,6348,6349,4703],{},[21,6330,6331],{},"우측",[32,6333,6334],{},"목록 다운로드 요청",[32,6336,6337],{},"다운로드 요청 목록",[32,6339,6340],{},"조회 필드 추가","(다중선택 드롭다운) · ",[32,6343,6344],{},"일괄 취소",[32,6346,6347],{},"선택 취소"," 순. 모두 ",[32,6350,6351],{},".btn-outline .btn-sm",[84,6353,6354,6355,6358],{},"필터 영역(ⓐ)과 액션 영역(ⓑ)은 ",[21,6356,6357],{},"반드시 분리"," — 검색 조건은 카드 위, 목록 조작은 카드 안.",[6184,6360,6362],{"id":6361},"ⓒ-테이블-본체","ⓒ 테이블 본체",[18,6364,6365,6100,6368,6371,6372,6375,6376,6378,6379,6382,6383,6386,6387,6390],{},[32,6366,6367],{},".list-table-scroll",[32,6369,6370],{},"overflow-x:auto",") > ",[32,6373,6374],{},"table.table.list-table","(기본 ",[32,6377,4367],{}," + 셀 ",[32,6380,6381],{},"nowrap","). 1열 체크박스 · ID는 mono ",[32,6384,6385],{},"accent-ink"," 링크 · 상태는 dot 배지 · 채널은 ",[32,6388,6389],{},".ch-pill",". 페이지네이션은 카드 하단.",[15,6392,6393],{},[18,6394,6395],{},"검색 조건이 필요한 목록 화면은 ⓐⓑⓒ 3영역 구조를 그대로 따른다.",[252,6397,6399],{"id":6398},"b-테이블-스타일-검색-없는-단순-목록","B 테이블 스타일 — 검색 없는 단순 목록",[18,6401,6402,6405,6406,6408,6409,6413,6414,6417],{},[21,6403,6404],{},"검색(필터) 영역이 없는"," 가장 단순한 목록 패턴. A 테이블에서 ⓐ ",[32,6407,6190],{},"를 뺀 형태 — ⓑ 액션 영역 + ⓒ 테이블 본체만. 항목 수가 적고 검색이 불필요한 관리 화면(발신 번호·발신 프로필 등)에 쓴다. 레퍼런스 = ",[26,6410,6412],{"href":6411},"..\u002Fapp\u002Fpages\u002Fsender\u002Fnumbers.vue","sender\u002Fnumbers.vue",", 테이블 요소에 ",[32,6415,6416],{},"data-table-style=\"b\"","로 명시.",[5251,6419,6422],{"className":6420,"code":6421,"language":5256},[5254],"ⓑⓒ .list-card              흰 카드 · 1px line · r-lg · overflow hidden\n   ├─ ⓑ .list-toolbar       액션 영역 — 카드 \"안\" 최상단 (border-bottom 1px)\n   │     좌: 총 N개   \u002F   우: 페이지별 액션 버튼\n   └─ ⓒ table.table         테이블 본체 (가로 스크롤 필요 시 .list-table)\n",[32,6423,6421],{"__ignoreMap":4208},[81,6425,6426,6429,6443],{},[84,6427,6428],{},"ⓑ 액션 영역 구성·문구는 A와 동일.",[84,6430,6431,6432,4536,6435,6438,6439,6442],{},"우측 버튼은 화면 목적에 따라 다름(예: ",[32,6433,6434],{},"발신 번호 등록 안내",[32,6436,6437],{},"선택 삭제","). 페이지 상단 우측에는 별도 등록 CTA(",[32,6440,6441],{},"발신 번호 등록"," 등)를 둘 수 있다.",[84,6444,6445,6446,6449,6450,6100,6453,6455],{},"검색이 필요하면 → 단일 검색어면 ",[21,6447,6448],{},"C 테이블","(액션 영역에 검색란), 다중 조건이면 ",[21,6451,6452],{},"A 테이블",[32,6454,6190],{},")로 승격한다.",[252,6457,6459],{"id":6458},"c-테이블-스타일-액션-영역-인라인-검색","C 테이블 스타일 — 액션 영역 인라인 검색",[18,6461,6462,6463,6466,6467,6413,6471,6417],{},"별도 필터 영역 없이, ",[21,6464,6465],{},"ⓑ 액션 영역 안에 검색란 하나","를 둔 패턴. B 테이블에 단일 검색 입력을 더한 형태 — 다중 조건 필터까지는 필요 없지만 이름·도메인 등 한 가지로 찾고 싶을 때 쓴다. 레퍼런스 = ",[26,6468,6470],{"href":6469},"..\u002Fapp\u002Fpages\u002Fsender\u002Fdomains.vue","sender\u002Fdomains.vue",[32,6472,6473],{},"data-table-style=\"c\"",[5251,6475,6478],{"className":6476,"code":6477,"language":5256},[5254],"ⓑⓒ .list-card              흰 카드 · 1px line · r-lg · overflow hidden\n   ├─ ⓑ .list-toolbar       액션 영역 — 카드 \"안\" 최상단 (border-bottom 1px)\n   │     좌: 총 N개 · 검색란   \u002F   우: 페이지별 액션 버튼\n   └─ ⓒ table.table         테이블 본체 (가로 스크롤 필요 시 .list-table)\n",[32,6479,6477],{"__ignoreMap":4208},[81,6481,6482,6499],{},[84,6483,6484,6485,6488,6489,6492,6493,6495,6496,4703],{},"검색란은 좌측 그룹의 ",[32,6486,6487],{},"총 N개"," 다음에 둔다. ",[21,6490,6491],{},"너비 260px 고정",", 돋보기 아이콘은 입력칸 오른쪽, 높이는 ",[32,6494,5997],{},"과 동일한 ",[21,6497,6498],{},"28px",[84,6500,6501],{},"액션 영역 좌\u002F우 구성·문구는 A·B와 동일.",[252,6503,5748],{"id":6504},"a-b-c-비교-1",[101,6506,6507,6520],{},[104,6508,6509],{},[107,6510,6511,6513,6515,6518],{},[110,6512],{},[110,6514,6452],{"align":5759},[110,6516,6517],{"align":5759},"B 테이블",[110,6519,6448],{"align":5759},[126,6521,6522,6535,6548,6559,6569,6582],{},[107,6523,6524,6529,6531,6533],{},[131,6525,6526,6527,4343],{},"ⓐ 별도 검색(필터) 영역 (",[32,6528,6190],{},[131,6530,3869],{"align":5759},[131,6532,5788],{"align":5759},[131,6534,5788],{"align":5759},[107,6536,6537,6542,6544,6546],{},[131,6538,6539,6540,4343],{},"ⓑ 액션 영역 (",[32,6541,6293],{},[131,6543,3869],{"align":5759},[131,6545,3869],{"align":5759},[131,6547,3869],{"align":5759},[107,6549,6550,6553,6555,6557],{},[131,6551,6552],{},"액션 영역 내 검색란",[131,6554,322],{"align":5759},[131,6556,5788],{"align":5759},[131,6558,3869],{"align":5759},[107,6560,6561,6563,6565,6567],{},[131,6562,6362],{},[131,6564,3869],{"align":5759},[131,6566,3869],{"align":5759},[131,6568,3869],{"align":5759},[107,6570,6571,6573,6576,6579],{},[131,6572,4803],{},[131,6574,6575],{"align":5759},"다중 조건 검색 필수 목록(조회·이력)",[131,6577,6578],{"align":5759},"검색 불필요 소규모 목록",[131,6580,6581],{"align":5759},"단일 검색어 목록",[107,6583,6584,6586,6590,6595],{},[131,6585,5828],{},[131,6587,6588],{"align":5759},[32,6589,4555],{},[131,6591,6592],{"align":5759},[32,6593,6594],{},"sender\u002Fnumbers",[131,6596,6597],{"align":5759},[32,6598,6599],{},"sender\u002Fdomains",[237,6601,6603],{"id":6602},"_66-badge-channel-pill","6.6 Badge \u002F Channel pill",[18,6605,6606,6609,6610,6612],{},[32,6607,6608],{},".badge-*"," 6톤(success\u002Fwarning\u002Ferror\u002Fprimary=info\u002Fsky=info\u002Fneutral), h-20, soft BG + line border. 채널은 ",[32,6611,6389],{},"(mono 코드 + 6px 컬러 도트) — 색에만 의존 금지, 항상 라벨 동반.",[237,6614,6616],{"id":6615},"_67-empty-toast","6.7 Empty \u002F Toast",[18,6618,6619,6622,6623,6626,6627,6630,6631,4703],{},[32,6620,6621],{},".empty"," — 40px 아이콘 박스(line border) + h3(ink-900) + p(ink-500) + 1차 액션. ",[32,6624,6625],{},".toast"," — 우측 하단 stack, 흰 BG + line + shadow-popover, 4톤 아이콘색, ",[32,6628,6629],{},"aria-live",". Nuxt UI: ",[32,6632,6633],{},"useToast()",[237,6635,6637],{"id":6636},"_68-채널-미리보기","6.8 채널 미리보기",[18,6639,6640,6643,6644,6647,6648,6651,6652,6655,6656,6659,6660,6663],{},[32,6641,6642],{},".imsg","(iMessage) · ",[32,6645,6646],{},".kakao","(노란 리본+변수 pill) · ",[32,6649,6650],{},".rcs","(ink-900 verified+이미지+버튼) · ",[32,6653,6654],{},".email-preview","(subject\u002Fmeta\u002Fbody) · ",[32,6657,6658],{},".push-lock","(다크 잠금화면+frosted notif). 폰 셸 280×520, ",[32,6661,6662],{},"r-20",", shadow-soft.",[73,6665],{},[76,6667,6669],{"id":6668},"_7-ai-ux-패턴-이-시스템의-차별점","7. AI UX 패턴 (이 시스템의 차별점)",[18,6671,6672],{},"다음 4가지만 사용:",[6674,6675,6676,6689,6697,6703],"ol",{},[84,6677,6678,6681,6682,6685,6686,6688],{},[21,6679,6680],{},"AI 라벨링"," — 텍스트 좌측 ",[32,6683,6684],{},"Sparkles size=12"," (",[32,6687,6385],{},"). 별도 컬러 박스 없음.",[84,6690,6691,89,6694,6696],{},[21,6692,6693],{},"AI 요약 카드",[32,6695,5426],{}," BG + accent Sparkles 박스 + \"생성시간·모델·데이터범위\" 메타 + 재생성 버튼 상시.",[84,6698,6699,6702],{},[21,6700,6701],{},"AI 추천 액션"," — 칩(라벨 + 작은 hint + hover 화살표 translate). 클릭 시 즉시 적용\u002F미리보기(블랙박스 금지).",[84,6704,6705,89,6708,6711,6712,6715,6716],{},[21,6706,6707],{},"AI 생성 중",[32,6709,6710],{},".ai-shimmer","(그린 좌→우 그라데이션 클립)만. 스피너는 ",[32,6713,6714],{},"animate-spin","만. ",[21,6717,6718],{},"무지개·보라 금지.",[73,6720],{},[76,6722,6724],{"id":6723},"_8-모션","8. 모션",[101,6726,6727,6742],{},[104,6728,6729],{},[107,6730,6731,6733,6736,6739],{},[110,6732,4328],{},[110,6734,6735],{},"속성",[110,6737,6738],{},"시간",[110,6740,6741],{},"easing",[126,6743,6744,6758,6770,6784,6797],{},[107,6745,6746,6749,6752,6755],{},[131,6747,6748],{},"사이드바 접기",[131,6750,6751],{},"width",[131,6753,6754],{},"300ms",[131,6756,6757],{},"ease-out",[107,6759,6760,6763,6765,6768],{},[131,6761,6762],{},"hover (bg\u002Fcolor\u002Fborder)",[131,6764,322],{},[131,6766,6767],{},"150ms",[131,6769,5643],{},[107,6771,6772,6775,6778,6781],{},[131,6773,6774],{},"상태 도트(live)",[131,6776,6777],{},"opacity\u002Fscale",[131,6779,6780],{},"1.6s ∞",[131,6782,6783],{},"ease-in-out",[107,6785,6786,6789,6792,6795],{},[131,6787,6788],{},"AI shimmer",[131,6790,6791],{},"background-position",[131,6793,6794],{},"3.5s linear ∞",[131,6796,322],{},[107,6798,6799,6802,6805,6808],{},[131,6800,6801],{},"Modal in",[131,6803,6804],{},"translateY(6px)→0 + opacity",[131,6806,6807],{},"180ms",[131,6809,6810],{},"ease",[18,6812,6813,6816,6817,6821,6822,6825],{},[21,6814,6815],{},"금지"," — 카드 scale hover, 그림자 점프, fade-in 진입. 모션은 항상 ",[6818,6819,6820],"em",{},"상태 변화의 결과",". 무한 애니메이션은 ",[32,6823,6824],{},"prefers-reduced-motion","에서 비활성(main.css에 반영됨).",[73,6827],{},[76,6829,6831],{"id":6830},"_9-접근성","9. 접근성",[81,6833,6834,6848,6855,6858,6868],{},[84,6835,6836,6837,6840,6841,6844,6845,6847],{},"최소 대비: ",[32,6838,6839],{},"ink-500 on white","(4.5:1) 이상. ",[32,6842,6843],{},"ink-400","은 14px+ \u002F ",[32,6846,4758],{},"은 16px+(축 라벨)에서만.",[84,6849,6850,6851,6854],{},"모든 interactive: ",[32,6852,6853],{},"outline:none"," + border-color 변경 또는 ring. focus 항상 가시.",[84,6856,6857],{},"색에만 의존한 상태 전달 금지 — 라벨 + 도트 동반.",[84,6859,6860,6861,6864,6865,5278],{},"아이콘 단독 버튼 ",[32,6862,6863],{},"aria-label"," 필수. 이미지 ",[32,6866,6867],{},"alt",[84,6869,6870,6871,6874],{},"키보드: Tab\u002FEsc\u002FEnter 동작. (",[32,6872,6873],{},"⌘K"," 검색 등 단축키는 도입 시 정의)",[73,6876],{},[76,6878,6880],{"id":6879},"_10-톤-마이크로카피-한국어","10. 톤 & 마이크로카피 (한국어)",[101,6882,6883,6896],{},[104,6884,6885],{},[107,6886,6887,6890,6893],{},[110,6888,6889],{},"항목",[110,6891,6892],{},"가이드",[110,6894,6895],{},"예",[126,6897,6898,6909,6920,6930,6948,6959,6970],{},[107,6899,6900,6903,6906],{},[131,6901,6902],{},"인칭",[131,6904,6905],{},"사용자 시점, 존댓말",[131,6907,6908],{},"\"다시 생성\", \"내보내기\"",[107,6910,6911,6914,6917],{},[131,6912,6913],{},"버튼 길이",[131,6915,6916],{},"6자 이내 권장",[131,6918,6919],{},"\"내보내기\" ✅ \u002F \"리포트를 내보내기\" ❌",[107,6921,6922,6924,6927],{},[131,6923,6738],{},[131,6925,6926],{},"상대 시간 우선",[131,6928,6929],{},"\"방금 전\", \"3분 전\"",[107,6931,6932,6935,6940],{},[131,6933,6934],{},"숫자",[131,6936,6937,6938,4343],{},"천단위 콤마, 단위는 작게(",[32,6939,6843],{},[131,6941,6942,4420,6945],{},[32,6943,6944],{},"128,472",[32,6946,6947],{},"94.6%",[107,6949,6950,6953,6956],{},[131,6951,6952],{},"AI 호칭",[131,6954,6955],{},"\"AI\"로 통일",[131,6957,6958],{},"\"AI 일일 요약\" (봇\u002F에이전트 ✗)",[107,6960,6961,6964,6967],{},[131,6962,6963],{},"빈 상태",[131,6965,6966],{},"짧은 안내 + 1차 액션",[131,6968,6969],{},"\"아직 발송 이력이 없습니다 · 첫 발송 시작\"",[107,6971,6972,6975,6978],{},[131,6973,6974],{},"위험 액션",[131,6976,6977],{},"사유\u002F한번 더 확인",[131,6979,6980],{},"\"정말 삭제하시겠습니까? 되돌릴 수 없습니다.\"",[73,6982],{},[76,6984,6986],{"id":6985},"_11-데이터-시각화","11. 데이터 시각화",[81,6988,6989,7007,7020,7033,7043,7046],{},[84,6990,6991,6992,6994,6995,6997,6998,7001,7002,5439,7004],{},"시리즈 색: 1차 ",[32,6993,5426],{}," \u002F 2차(AI 등) ",[32,6996,5915],{}," \u002F 3차+ ",[32,6999,7000],{},"ink-500",",",[32,7003,4758],{},[21,7005,7006],{},"새 색 금지.",[84,7008,7009,7010,7013,7014,7017,7018,4703],{},"음수\u002F감소 ",[32,7011,7012],{},"rose-600",". 단 ",[32,7015,7016],{},"inverse:true"," KPI(감소가 긍정)는 ",[32,7019,5915],{},[84,7021,7022,7023,7025,7026,7029,7030,4703],{},"Gridline ",[32,7024,4982],{},", y축 0라인만 실선 나머지 ",[32,7027,7028],{},"2 3"," dash. 축 라벨 ",[32,7031,7032],{},"mono 10px ink-300",[84,7034,7035,7036,4339,7039,7042],{},"Tooltip ",[32,7037,7038],{},"border-line",[32,7040,7041],{},"shadow-sm"," + 11px, 시리즈 도트 1.5×1.5 + tabular 숫자.",[84,7044,7045],{},"Area fill = 색 22% → 0% 수직 그라데이션.",[84,7047,7048],{},"차트 라이브러리: Chart.js (STACK 참조).",[73,7050],{},[76,7052,7054],{"id":7053},"_12-발송-페이지-아키텍처-도메인-디자인-전환에도-유지","12. 발송 페이지 아키텍처 (도메인 — 디자인 전환에도 유지)",[15,7056,7057],{},[18,7058,7059,7060,7063],{},"단발 발송 6종(SMS\u002F알림톡\u002FRCS\u002F이메일\u002FPUSH\u002FFlow)의 공통 구조. 2026-05-19~20 작업에서 ",[21,7061,7062],{},"3+1 카드 골격","으로 재배치했다(구 5-카드의 발신\u002F메시지 카드를 통합·재구성).",[237,7065,7067],{"id":7066},"_121-발송-카드-골격-31-카드","12.1 발송 카드 골격 (3+1 카드)",[5251,7069,7072],{"className":7070,"code":7071,"language":5256},[5254],"① 템플릿 선택  (템플릿 사용유무 + 샘플 템플릿 선택)        ← Flow는 \"플로우 선택\"\n② 수신자 설정  (직접 입력·주소록·별칭 클릭 수정·삭제 + 치환자 입력 + 표)\n③ 메시지 설정  (좌: 발신 정보 · 발송 목적\u002F유형 · 본문 \u002F 우: 채널별 폰 미리보기 · AI)\n④ 발송 설정    (즉시 \u002F 예약 발송 + datetime)\n   ──────────  AppSendActionsBar: 초기화 · 발송하기(.btn-primary)\n",[32,7073,7071],{"__ignoreMap":4208},[18,7075,7076,7077,7079,7080,6078,7083,7086,7087,6071,7089,7092,7093,7096,7097,7100,7101,7104],{},"각 카드는 ",[32,7078,4360],{},"(header ",[32,7081,7082],{},"title",[32,7084,7085],{},"required","). ",[32,7088,4479],{},[32,7090,7091],{},"collapsible","(닫기\u002F열기 토글)·",[32,7094,7095],{},":locked","(점진적 disclosure)를 옵션으로 지원한다. 현재 게이팅은 ",[21,7098,7099],{},"Flow의 메시지 설정","(플로우 미선택 시)에만 적용 — SMS\u002F알림톡 등 다른 채널의 수신자·메시지 카드는 항상 사용 가능. 광고용 발송 선택 시 제목에 ",[32,7102,7103],{},"(광고)","가 자동 부착된다(SMS\u002F이메일\u002FPUSH).",[237,7106,7108],{"id":7107},"_122-채널-차이-매트릭스-요약","12.2 채널 차이 매트릭스 (요약)",[101,7110,7111,7135],{},[104,7112,7113],{},[107,7114,7115,7117,7120,7123,7126,7129,7132],{},[110,7116],{},[110,7118,7119],{"align":5759},"SMS",[110,7121,7122],{"align":5759},"알림톡",[110,7124,7125],{"align":5759},"RCS",[110,7127,7128],{"align":5759},"이메일",[110,7130,7131],{"align":5759},"PUSH",[110,7133,7134],{"align":5759},"Flow",[126,7136,7137,7159,7181,7203,7221,7240,7262],{},[107,7138,7139,7142,7145,7148,7151,7153,7156],{},[131,7140,7141],{},"Sender",[131,7143,7144],{"align":5759},"발신번호",[131,7146,7147],{"align":5759},"발신프로필",[131,7149,7150],{"align":5759},"브랜드+번호",[131,7152,7128],{"align":5759},[131,7154,7155],{"align":5759},"없음",[131,7157,7158],{"align":5759},"플로우",[107,7160,7161,7164,7167,7172,7174,7176,7178],{},[131,7162,7163],{},"Template",[131,7165,7166],{"align":5759},"optional",[131,7168,7169],{"align":5759},[21,7170,7171],{},"필수",[131,7173,7166],{"align":5759},[131,7175,7166],{"align":5759},[131,7177,7166],{"align":5759},[131,7179,7180],{"align":5759},"플로우=템플릿",[107,7182,7183,7186,7189,7192,7194,7197,7200],{},[131,7184,7185],{},"본문",[131,7187,7188],{"align":5759},"textarea",[131,7190,7191],{"align":5759},"변수영역만",[131,7193,7188],{"align":5759},[131,7195,7196],{"align":5759},"HTML",[131,7198,7199],{"align":5759},"input+textarea",[131,7201,7202],{"align":5759},"readonly\u002F노드",[107,7204,7205,7208,7211,7213,7215,7217,7219],{},[131,7206,7207],{},"AI 다듬기",[131,7209,7210],{"align":5759},"✓",[131,7212,322],{"align":5759},[131,7214,7210],{"align":5759},[131,7216,7210],{"align":5759},[131,7218,322],{"align":5759},[131,7220,322],{"align":5759},[107,7222,7223,7226,7229,7232,7234,7236,7238],{},[131,7224,7225],{},"치환자",[131,7227,7228],{"align":5759},"템플릿시",[131,7230,7231],{"align":5759},"개별\u002F공통",[131,7233,7231],{"align":5759},[131,7235,7228],{"align":5759},[131,7237,322],{"align":5759},[131,7239,7231],{"align":5759},[107,7241,7242,7245,7248,7251,7253,7256,7259],{},[131,7243,7244],{},"미리보기",[131,7246,7247],{"align":5759},"iMessage",[131,7249,7250],{"align":5759},"카카오",[131,7252,7125],{"align":5759},[131,7254,7255],{"align":5759},"이메일카드",[131,7257,7258],{"align":5759},"잠금화면",[131,7260,7261],{"align":5759},"선택노드",[107,7263,7264,7267,7270,7273,7276,7279,7282],{},[131,7265,7266],{},"단가",[131,7268,7269],{"align":5759},"9.9",[131,7271,7272],{"align":5759},"8.0",[131,7274,7275],{"align":5759},"12.0",[131,7277,7278],{"align":5759},"0.65",[131,7280,7281],{"align":5759},"0.0",[131,7283,7284],{"align":5759},"노드 합산",[18,7286,7287,7288,6100,7291,7294],{},"채널 차이는 단일 ",[32,7289,7290],{},"ChannelMeta",[32,7292,7293],{},"types\u002Fchannel.ts",") 객체로 표현하고 페이지\u002F컴포넌트가 분기 — 상세 타입 정의는 git 이력의 이전 DESIGN.md(커밋 ae40b0b 이전) 참조, 신규 컴포넌트 작업 시 재정리한다.",[237,7296,7298],{"id":7297},"_123-핵심-컴포넌트","12.3 핵심 컴포넌트",[18,7300,7301,89,7304,7306,7307,7309,7310,4361,7312,7314,7315,7317,7318,7321,7322,7325,7326,4420,7329,7332],{},[21,7302,7303],{},"공용 골격",[32,7305,4479],{},"(=",[32,7308,4360],{}," 래퍼, ",[32,7311,7091],{},[32,7313,7095],{},") · ",[32,7316,4482],{},"(직접\u002F주소록\u002F별칭클릭 수정\u002F삭제 + 치환자 + 표, ",[32,7319,7320],{},"keyColumns"," 다중 키 지원) · ",[32,7323,7324],{},"AppSendOptionsCard","(즉시\u002F예약) · ",[32,7327,7328],{},"AppSendActionsBar",[32,7330,7331],{},"AppSendConfirmDialog","(비용 3셀).",[18,7334,7335,89,7338,7341,7342,7314,7344,7347,7348,7351],{},[21,7336,7337],{},"수신자 입력",[32,7339,7340],{},"AppRecipientFormDialog","(휴대폰\u002F이메일 다중 ",[32,7343,7320],{},[32,7345,7346],{},"AppAddressBookDialog","(개별\u002F그룹, 다중 키 컬럼) · ",[32,7349,7350],{},"AppPushRecipientDialog","(PUSH 전용, 별칭+토큰 다중+푸시 유형).",[18,7353,7354,89,7356,7359,7360,4420,7362,4420,7364,7366,7367,6100,7369,5606],{},[21,7355,7244],{},[32,7357,7358],{},"AppPhonePreview","(iMessage, 첨부 이미지) · ",[32,7361,4503],{},[32,7363,4506],{},[32,7365,4509],{},"(다이얼로그·발송 페이지 공용) · ",[32,7368,4512],{},[32,7370,7371],{},"platform: 'android'|'ios'",[18,7373,7374,89,7377,7379,7380,4420,7382,4420,7385,4420,7388,4703],{},[21,7375,7376],{},"템플릿 다이얼로그",[32,7378,4485],{},"(카드 그리드) · ",[32,7381,4518],{},[32,7383,7384],{},"AppRcsTemplateDialog",[32,7386,7387],{},"AppEmailTemplateDialog",[32,7389,7390],{},"AppPushTemplateDialog",[18,7392,7393,89,7396,4420,7399,7402,7403,4361,7405,4361,7408,4361,7411,7414,7415,4703],{},[21,7394,7395],{},"PUSH 부가 항목",[32,7397,7398],{},"AppPushButtonDialog",[32,7400,7401],{},"AppPushMediaDialog","(미디어\u002FAndroid 미디어\u002FiOS 미디어\u002FAndroid 큰 아이콘을 ",[32,7404,7082],{},[32,7406,7407],{},"showType",[32,7409,7410],{},"showExpand",[32,7412,7413],{},"types"," prop으로 분기) · ",[32,7416,7417],{},"AppPushGroupDialog",[18,7419,7420,89,7423,7426,7427,7430,7431,7434],{},[21,7421,7422],{},"복합 플로우",[32,7424,7425],{},"AppFlowManageDialog","(목록·이름 클릭 수정) · ",[32,7428,7429],{},"AppFlowCreateDialog","(등록·수정 겸용, 드래그 순서) · ",[32,7432,7433],{},"AppFlowTemplatePickerDialog","(채널별 템플릿).",[18,7436,7437,89,7440,7443,7444,6100,7446,7449,7450,4703],{},[21,7438,7439],{},"기타",[32,7441,7442],{},"AppAIRewriteDialog","(AI 문장 다듬기) · ",[32,7445,4476],{},[32,7447,7448],{},"utils\u002FscrollLock"," 카운터 기반 본 페이지 스크롤 잠금, 중첩 모달 안전) · ",[32,7451,7452],{},"AppAdNoticeSms080Dialog",[73,7454],{},[76,7456,7458],{"id":7457},"_13-디자인-변경-추가-절차","13. 디자인 변경 \u002F 추가 절차",[6674,7460,7461,7470,7480,7488,7497],{},[84,7462,7463,7469],{},[21,7464,7465,7466,7468],{},"핸드오프(",[32,7467,4280],{},")에 패턴이 있나?"," → 있으면 토큰·클래스 그대로 차용.",[84,7471,7472,7475,7476,7479],{},[21,7473,7474],{},"Nuxt UI에 있나?"," → 있으면 ",[32,7477,7478],{},"U*"," 우선, app.config 토큰으로 스타일.",[84,7481,7482,6219,7485,7487],{},[21,7483,7484],{},"공통 패턴인가?",[32,7486,4597],{}," 컴포넌트로 추출.",[84,7489,7490,7493,7494,7496],{},[21,7491,7492],{},"새 색이 필요한가?"," → 만들지 말 것. 거의 모든 경우 ink\u002Faccent\u002Fsemantic으로 해결. 정말 필요하면 ",[32,7495,4338],{},"에 추가 후 §2 등록.",[84,7498,7499,7502,7503,7505,7506,4703],{},[21,7500,7501],{},"점검"," — 1px hairline \u002F 액센트 ≤6회 \u002F tabular 숫자 \u002F 그림자=floating만 \u002F 1024px 반응형 \u002F focus·",[32,7504,6863],{}," \u002F ",[32,7507,6824],{},[73,7509],{},[76,7511,7513],{"id":7512},"_14-변경-이력","14. 변경 이력",[101,7515,7516,7526],{},[104,7517,7518],{},[107,7519,7520,7523],{},[110,7521,7522],{},"날짜",[110,7524,7525],{},"변경",[126,7527,7528,7536,7551,7562],{},[107,7529,7530,7533],{},[131,7531,7532],{},"~2026-05-13",[131,7534,7535],{},"(구) malgn-notifications 시안 기반 디자인 시스템 — indigo\u002Fsky\u002FNoto Sans KR, 1200px, 909줄 가이드. git 이력 보존.",[107,7537,7538,7543],{},[131,7539,7540],{},[21,7541,7542],{},"2026-05-18",[131,7544,7545,89,7548,7550],{},[21,7546,7547],{},"전면 피벗",[32,7549,4280],{}," Relay-inspired v1.0를 디자인 정본으로 채택. ink 무채색+그린 액센트, Inter\u002FJetBrains Mono\u002FPretendard, 1400px. Phase 1: 토큰·폰트·Nuxt UI 매핑·문서 적용. Phase 2(컴포넌트\u002F페이지)는 체크포인트 후 진행.",[107,7552,7553,7556],{},[131,7554,7555],{},"2026-05-19",[131,7557,7558,7559,7561],{},"Phase 2 마무리 — ",[32,7560,4457],{}," 라이브 카탈로그(17섹션) 추가, 셸·홈·발송 6채널·조회\u002F통계\u002F주소록\u002F충전\u002F인증 적용, Cloudflare Pages 프로덕션 배포. UI 스케일 115%·1400px·푸터 다크·헤더 불투명 등 페이지 설정 확정.",[107,7563,7564,7567],{},[131,7565,7566],{},"2026-05-20",[131,7568,7569,7570,7572,7573,7576],{},"발송 6채널 UX 2차 폴리시 — 3+1 카드 골격 재배치, 채널 템플릿\u002FPUSH 부가 항목 다이얼로그, 복합 플로우 등록·수정 통합 관리, ",[32,7571,4509],{},"\u002F다중 키 컬럼\u002F",[32,7574,7575],{},"scrollLock"," 등 공용 컴포넌트 정리. 발송 옵션→발송 설정 등 문구 통일.",{"title":4208,"searchDepth":4209,"depth":4209,"links":7578},[7579,7580,7583,7591,7594,7600,7612,7631,7632,7633,7634,7635,7636,7641,7642],{"id":4313,"depth":4212,"text":4314},{"id":4614,"depth":4212,"text":4615,"children":7581},[7582],{"id":4735,"depth":4209,"text":4736},{"id":4764,"depth":4212,"text":4765,"children":7584},[7585,7587,7588,7590],{"id":4785,"depth":4209,"text":7586},"2.1 Ink Scale (무채색 11단 ≈ Tailwind zinc)",{"id":5005,"depth":4209,"text":5006},{"id":5091,"depth":4209,"text":7589},"2.3 Semantic \u002F Status (*-soft BG + *-line border + *-ink text)",{"id":5213,"depth":4209,"text":5214},{"id":5248,"depth":4212,"text":5249,"children":7592},[7593],{"id":5293,"depth":4209,"text":5294},{"id":5447,"depth":4212,"text":5448,"children":7595},[7596,7597,7598],{"id":5451,"depth":4209,"text":5452},{"id":5461,"depth":4209,"text":5462},{"id":5523,"depth":4209,"text":7599},"4.3 Elevation — 카드에 그림자 금지",{"id":5545,"depth":4212,"text":5546,"children":7601},[7602,7603,7605,7606],{"id":5549,"depth":4209,"text":5550},{"id":5581,"depth":4209,"text":7604},"5.2 2-Column (.content-2col)",{"id":5594,"depth":4209,"text":5595},{"id":5609,"depth":4209,"text":5610,"children":7607},[7608,7609,7610,7611],{"id":5624,"depth":4219,"text":5625},{"id":5665,"depth":4219,"text":5666},{"id":5702,"depth":4219,"text":5703},{"id":5747,"depth":4219,"text":5748},{"id":5853,"depth":4212,"text":5854,"children":7613},[7614,7616,7618,7620,7621,7628,7629,7630],{"id":5866,"depth":4209,"text":7615},"6.1 버튼 (.btn)",{"id":6013,"depth":4209,"text":7617},"6.2 카드 (.card)",{"id":6043,"depth":4209,"text":7619},"6.3 폼 (.form-row)",{"id":6088,"depth":4209,"text":6089},{"id":6128,"depth":4209,"text":7622,"children":7623},"6.5 테이블 (.table)",[7624,7625,7626,7627],{"id":6155,"depth":4219,"text":6156},{"id":6398,"depth":4219,"text":6399},{"id":6458,"depth":4219,"text":6459},{"id":6504,"depth":4219,"text":5748},{"id":6602,"depth":4209,"text":6603},{"id":6615,"depth":4209,"text":6616},{"id":6636,"depth":4209,"text":6637},{"id":6668,"depth":4212,"text":6669},{"id":6723,"depth":4212,"text":6724},{"id":6830,"depth":4212,"text":6831},{"id":6879,"depth":4212,"text":6880},{"id":6985,"depth":4212,"text":6986},{"id":7053,"depth":4212,"text":7054,"children":7637},[7638,7639,7640],{"id":7066,"depth":4209,"text":7067},{"id":7107,"depth":4209,"text":7108},{"id":7297,"depth":4209,"text":7298},{"id":7457,"depth":4212,"text":7458},{"id":7512,"depth":4212,"text":7513},{},"\u002Fdesign",{"title":4265,"description":4208},"DESIGN","Y0Tc5kN8I47j0nV2F_bWjg3ZjBD6fRGeJXE9rFql48k",{"id":7649,"title":7650,"body":7651,"description":10619,"extension":4257,"meta":10620,"navigation":4259,"path":10621,"seo":10622,"stem":10623,"__hash__":10624},"docs\u002FFRONTEND.md","Frontend 개발 가이드 — 맑은 메시징 사용자단",{"type":8,"value":7652,"toc":10559},[7653,7656,7674,7698,7700,7704,7864,7866,7870,7876,7879,7908,7910,7914,7931,7937,7947,7953,7960,8008,8019,8023,8092,8100,8104,8159,8166,8172,8181,8183,8187,8191,8223,8227,8230,8356,8359,8581,8588,8640,8644,8647,8738,8742,8770,8772,8776,8780,8820,8824,8844,8859,8865,9049,9053,9079,9083,9090,9092,9096,9100,9233,9237,9249,9322,9325,9340,9344,9370,9374,9468,9472,9475,9594,9598,9605,9607,9611,9614,9817,9822,9915,9917,9921,9925,9984,9988,10040,10043,10057,10059,10063,10071,10103,10111,10122,10126,10185,10187,10191,10195,10257,10261,10312,10319,10326,10332,10341,10343,10347,10435,10437,10441,10445,10473,10477,10486,10502,10506,10556],[11,7654,7650],{"id":7655},"frontend-개발-가이드-맑은-메시징-사용자단",[18,7657,7658,7659,7665,7666,7669,7670,7673],{},"이 문서는 ",[26,7660,7662],{"href":7661},"..",[32,7663,7664],{},"malgn-noti"," 사용자단(고객 콘솔)의 ",[21,7667,7668],{},"디자인 시스템 + HTML\u002FCSS\u002FJS 코딩 컨벤션","을 정리합니다. 운영자 콘솔(",[32,7671,7672],{},"malgn-noti-admin",")도 동일 스택이므로 같은 규칙을 따릅니다.",[15,7675,7676,7695],{},[18,7677,7678,7679,4473,7683,7690,7691,7694],{},"프로젝트 전반의 큰 그림은 ",[26,7680,7682],{"href":7681},"..\u002FCLAUDE","CLAUDE.md",[21,7684,7685,7686],{},"디자인 정본(SoT)은 ",[26,7687,7689],{"href":7688},".\u002FDESIGN","DESIGN.md"," (Relay-inspired v1.0). 시안 사이트는 ",[21,7692,7693],{},"IA(페이지 목록·라우트)"," 한정 참조.",[18,7696,7697],{},"⚠️ 2026-05-18 디자인 전면 피벗 — 이 문서의 §3·§9는 신규 디자인 시스템 기준으로 갱신됨. 구 시안(indigo\u002FNoto Sans KR\u002F1200px) 기술은 폐기.",[73,7699],{},[76,7701,7703],{"id":7702},"_1-기술-스택-한눈에","1. 기술 스택 한눈에",[101,7705,7706,7719],{},[104,7707,7708],{},[107,7709,7710,7713,7716],{},[110,7711,7712],{},"영역",[110,7714,7715],{},"선택",[110,7717,7718],{},"비고",[126,7720,7721,7739,7753,7766,7786,7810,7827,7840,7851],{},[107,7722,7723,7726,7732],{},[131,7724,7725],{},"프레임워크",[131,7727,7728,7729],{},"Nuxt 3 + ",[32,7730,7731],{},"future.compatibilityVersion: 4",[131,7733,7734,7735,7738],{},"소스는 ",[32,7736,7737],{},"app\u002F"," 디렉토리",[107,7740,7741,7744,7747],{},[131,7742,7743],{},"언어",[131,7745,7746],{},"TypeScript (strict)",[131,7748,7749,7752],{},[32,7750,7751],{},"any"," 금지",[107,7754,7755,7758,7763],{},[131,7756,7757],{},"UI",[131,7759,7760],{},[21,7761,7762],{},"Nuxt UI v3 (MIT)",[131,7764,7765],{},"Reka UI + Tailwind v4 통합",[107,7767,7768,7771,7778],{},[131,7769,7770],{},"스타일",[131,7772,7773,7774,7777],{},"Tailwind v4 + scoped ",[32,7775,7776],{},"\u003Cstyle>"," + CSS 변수",[131,7779,7780,51,7783],{},[32,7781,7782],{},"@nuxtjs\u002Ftailwindcss",[21,7784,7785],{},"설치 금지",[107,7787,7788,7791,7804],{},[131,7789,7790],{},"아이콘",[131,7792,7793,7794,7797,7798,4473,7801,4343],{},"Iconify (",[32,7795,7796],{},"lucide"," 기본, ",[32,7799,7800],{},"heroicons",[32,7802,7803],{},"bi",[131,7805,7806,7807],{},"일반 UI는 ",[32,7808,7809],{},"i-lucide-*",[107,7811,7812,7815,7818],{},[131,7813,7814],{},"폰트",[131,7816,7817],{},"Inter · JetBrains Mono · Pretendard",[131,7819,7820,7505,7823,7826],{},[32,7821,7822],{},"--font-sans",[32,7824,7825],{},"--font-mono",", 한국어 fallback Pretendard",[107,7828,7829,7831,7837],{},[131,7830,270],{},[131,7832,7833,7834,4343],{},"Pinia (",[32,7835,7836],{},"@pinia\u002Fnuxt",[131,7838,7839],{},"인증\u002F크레딧\u002F사용자",[107,7841,7842,7845,7848],{},[131,7843,7844],{},"검증",[131,7846,7847],{},"Zod",[131,7849,7850],{},"폼\u002F응답",[107,7852,7853,7856,7859],{},[131,7854,7855],{},"배포",[131,7857,7858],{},"Cloudflare Pages",[131,7860,7861],{},[32,7862,7863],{},"nitro.preset = 'cloudflare-pages'",[73,7865],{},[76,7867,7869],{"id":7868},"_2-디렉토리-구조-nuxt-4-호환","2. 디렉토리 구조 (Nuxt 4 호환)",[5251,7871,7874],{"className":7872,"code":7873,"language":5256},[5254],"app\u002F\n├── app.vue                  # 진입 (UApp + NuxtLayout + NuxtPage)\n├── app.config.ts            # Nuxt UI 테마(primary\u002Fneutral)\n├── error.vue                # Nuxt 루트 에러 핸들러\n├── assets\u002F\n│   └── css\u002Fmain.css         # Tailwind + 시안 디자인 토큰\n├── components\u002F\n│   ├── AppGnb.vue           # 상단 GNB\n│   ├── AppFooter.vue        # 푸터\n│   ├── AppConfirmDialog.vue # 위험 액션 컨펌\n│   └── AppPagePlaceholder.vue\n├── composables\u002F\n│   ├── useApi.ts            # $fetch 래퍼 (인증\u002F에러 표준화)\n│   ├── useExportJob.ts      # 비동기 다운로드 요청\n│   └── useAiTemplate.ts     # AI 템플릿 생성\n├── layouts\u002F\n│   ├── default.vue          # GNB + 본문 + Footer\n│   ├── auth.vue             # 중앙 카드 (로그인\u002F회원가입)\n│   └── blank.vue            # 슬롯만 (단독 시스템 페이지)\n├── middleware\u002F\n│   └── auth.global.ts\n├── pages\u002F                   # 자동 라우팅 (시안 IA 기준 16카테고리)\n├── stores\u002F\n│   └── auth.ts              # Pinia\n└── types\u002F\n    ├── domain.ts            # Channel, Tenant, User 등\n    └── api.ts               # Paginated, ExportJob 등\n",[32,7875,7873],{"__ignoreMap":4208},[18,7877,7878],{},"루트:",[81,7880,7881,7886,7894,7902],{},[84,7882,7883,7885],{},[32,7884,4405],{}," — modules \u002F nitro \u002F app \u002F runtimeConfig",[84,7887,7888,89,7891],{},[32,7889,7890],{},"tsconfig.json",[32,7892,7893],{},"extends: .\u002F.nuxt\u002Ftsconfig.json",[84,7895,7896,89,7899],{},[32,7897,7898],{},"eslint.config.mjs",[32,7900,7901],{},"withNuxt()",[84,7903,7904,7907],{},[32,7905,7906],{},".env.example"," — 환경변수 템플릿",[73,7909],{},[76,7911,7913],{"id":7912},"_3-디자인-시스템","3. 디자인 시스템",[15,7915,7916],{},[18,7917,7918,7923,7924,7926,7927,7930],{},[21,7919,7920,7921],{},"정본은 ",[26,7922,7689],{"href":7688}," — Relay-inspired v1.0. 토큰·컴포넌트·발송 아키텍처 전체가 거기 있고, 라이브 카탈로그는 ",[32,7925,4457],{}," 페이지(",[26,7928,7929],{"href":4453},"app\u002Fpages\u002Fguide.vue","). 아래는 코딩 시 자주 쓰는 핵심만 요약.",[237,7932,7934,7935,4343],{"id":7933},"_31-색상-토큰-css-변수-maincss","3.1 색상 토큰 (CSS 변수 — ",[26,7936,4771],{"href":4351},[18,7938,7939,7940],{},"무채색 ink 11단 + 단일 그린 액센트. ",[21,7941,7942,7943,7946],{},"Tailwind 임의값(",[32,7944,7945],{},"bg-[#...]",") 금지, 항상 변수 사용.",[5251,7948,7951],{"className":7949,"code":7950,"language":5256},[5254],"\u002F* 무채색 ink (≈ Tailwind zinc) *\u002F\n--ink-900 #0a0a0a … --ink-50 #f4f4f5\n--paper   #fafaf9     \u002F* 앱 배경 *\u002F\n--line    #ececec     \u002F* 1px hairline border *\u002F\n--white   #ffffff     \u002F* 카드·탑바·입력 표면 *\u002F\n\n\u002F* 단일 액센트 — Green *\u002F\n--accent      #00DC82  \u002F* AI·Live·+delta·Primary CTA *\u002F\n--accent-soft #e6fbf2  \u002F* pill\u002F배경 *\u002F\n--accent-ink  #007a48  \u002F* 라이트 위 액센트 텍스트 *\u002F\n\n\u002F* 시맨틱: --{success|warning|danger|info}-{soft|line|ink} *\u002F\n\u002F* 레이아웃 *\u002F\n--container-max 1400px\n",[32,7952,7950],{"__ignoreMap":4208},[237,7954,7956,7957,4343],{"id":7955},"_32-nuxt-ui-테마-appconfigts","3.2 Nuxt UI 테마 (",[26,7958,7959],{"href":4391},"app.config.ts",[5251,7961,7965],{"className":7962,"code":7963,"language":7964,"meta":4208,"style":4208},"language-ts shiki shiki-themes github-light github-dark","export default defineAppConfig({\n  ui: { colors: { primary: 'zinc', neutral: 'zinc' } }\n})\n","ts",[32,7966,7967,7986,8003],{"__ignoreMap":4208},[7968,7969,7971,7975,7978,7982],"span",{"class":5110,"line":7970},1,[7968,7972,7974],{"class":7973},"szBVR","export",[7968,7976,7977],{"class":7973}," default",[7968,7979,7981],{"class":7980},"sScJk"," defineAppConfig",[7968,7983,7985],{"class":7984},"sVt8B","({\n",[7968,7987,7988,7991,7995,7998,8000],{"class":5110,"line":4212},[7968,7989,7990],{"class":7984},"  ui: { colors: { primary: ",[7968,7992,7994],{"class":7993},"sZZnC","'zinc'",[7968,7996,7997],{"class":7984},", neutral: ",[7968,7999,7994],{"class":7993},[7968,8001,8002],{"class":7984}," } }\n",[7968,8004,8005],{"class":5110,"line":4209},[7968,8006,8007],{"class":7984},"})\n",[18,8009,8010,8011,7505,8013,8015,8016,8018],{},"기본 색은 무채색(zinc). 액센트(그린)는 의미 있는 곳에만 — ",[32,8012,5907],{},[32,8014,5892],{},"(ink-900) 등 ",[26,8017,4771],{"href":4351}," 클래스로 표현.",[237,8020,8022],{"id":8021},"_33-폰트","3.3 폰트",[101,8024,8025,8036],{},[104,8026,8027],{},[107,8028,8029,8031,8033],{},[110,8030,4803],{},[110,8032,7814],{},[110,8034,8035],{},"변수",[126,8037,8038,8051,8066,8080],{},[107,8039,8040,8043,8047],{},[131,8041,8042],{},"UI 텍스트",[131,8044,8045],{},[21,8046,5265],{},[131,8048,8049],{},[32,8050,7822],{},[107,8052,8053,8056,8062],{},[131,8054,8055],{},"숫자·ID·코드",[131,8057,8058,6685,8060,4343],{},[21,8059,5274],{},[32,8061,4702],{},[131,8063,8064],{},[32,8065,7825],{},[107,8067,8068,8071,8075],{},[131,8069,8070],{},"한국어 fallback",[131,8072,8073],{},[21,8074,5283],{},[131,8076,6100,8077,8079],{},[32,8078,7822],{},"에 포함)",[107,8081,8082,8085,8087],{},[131,8083,8084],{},"대형 디스플레이",[131,8086,5289],{},[131,8088,8089],{},[32,8090,8091],{},"--font-serif",[18,8093,8094,51,8096,8099],{},[26,8095,4405],{"href":4404},[32,8097,8098],{},"app.head.link","에서 로드.",[237,8101,8103],{"id":8102},"_34-레이아웃-폭","3.4 레이아웃 폭",[101,8105,8106,8117],{},[104,8107,8108],{},[107,8109,8110,8112,8115],{},[110,8111,7712],{},[110,8113,8114],{},"max-width",[110,8116,7718],{},[126,8118,8119,8130,8149],{},[107,8120,8121,8124,8127],{},[131,8122,8123],{},"Topbar(GNB)",[131,8125,8126],{},"화면 폭",[131,8128,8129],{},"56px 높이, sticky, 단일 행",[107,8131,8132,8138,8146],{},[131,8133,8134,8135,4343],{},"본문 (",[32,8136,8137],{},".app-container",[131,8139,8140,6685,8143,4343],{},[21,8141,8142],{},"1400px",[32,8144,8145],{},"--container-max",[131,8147,8148],{},"좌우·상하 32px",[107,8150,8151,8154,8156],{},[131,8152,8153],{},"푸터",[131,8155,8142],{},[131,8157,8158],{},"본문과 끝 정렬 (다크 배경)",[18,8160,8161,8162,8165],{},"UI는 100% 네이티브 스케일로 렌더된다. (구 ",[32,8163,8164],{},"html { zoom: 1.15 }"," 전역 확대 방식은 2026-05-20 폐기 — 좌표계 어긋남으로 팝오버·정렬 버그를 유발해 제거.)",[237,8167,8169,8170,4343],{"id":8168},"_35-2단-콘텐츠-content-2col","3.5 2단 콘텐츠 (",[32,8171,5585],{},[18,8173,8174,5439,8177,8180],{},[32,8175,8176],{},"grid-template-columns: minmax(0,1fr) 320px; gap: 24px",[32,8178,8179],{},"\u003C= 1024px","에서 1단으로 접힘.",[73,8182],{},[76,8184,8186],{"id":8185},"_4-html-vue-작성-규칙","4. HTML \u002F Vue 작성 규칙",[237,8188,8190],{"id":8189},"_41-컴포넌트-형식","4.1 컴포넌트 형식",[81,8192,8193,8202,8205,8211],{},[84,8194,8195,8201],{},[21,8196,8197,8198],{},"반드시 ",[32,8199,8200],{},"\u003Cscript setup lang=\"ts\">",". Options API 금지.",[84,8203,8204],{},"한 파일에 한 컴포넌트.",[84,8206,8207,8208,5606],{},"파일명: PascalCase (",[32,8209,8210],{},"AppGnb.vue",[84,8212,8213,8214,8219,8220,8222],{},"자체 컴포넌트는 ",[21,8215,8216,8218],{},[32,8217,4597],{}," 접두사",", Nuxt UI는 자동 ",[32,8221,7478],{}," — 두 네임스페이스로 구분.",[237,8224,8226],{"id":8225},"_42-페이지-컴포넌트-템플릿","4.2 페이지 컴포넌트 템플릿",[18,8228,8229],{},"placeholder 단계의 표준:",[5251,8231,8235],{"className":8232,"code":8233,"language":8234,"meta":4208,"style":4208},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nuseHead({ title: 'XXX' })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CAppPagePlaceholder\n    title=\"XXX\"\n    category=\"대분류 · 소분류\"\n    description=\"간단한 설명\"\n  \u002F>\n\u003C\u002Ftemplate>\n","vue",[32,8236,8237,8261,8275,8284,8289,8299,8308,8319,8330,8341,8347],{"__ignoreMap":4208},[7968,8238,8239,8242,8246,8249,8252,8255,8258],{"class":5110,"line":7970},[7968,8240,8241],{"class":7984},"\u003C",[7968,8243,8245],{"class":8244},"s9eBZ","script",[7968,8247,8248],{"class":7980}," setup",[7968,8250,8251],{"class":7980}," lang",[7968,8253,8254],{"class":7984},"=",[7968,8256,8257],{"class":7993},"\"ts\"",[7968,8259,8260],{"class":7984},">\n",[7968,8262,8263,8266,8269,8272],{"class":5110,"line":4212},[7968,8264,8265],{"class":7980},"useHead",[7968,8267,8268],{"class":7984},"({ title: ",[7968,8270,8271],{"class":7993},"'XXX'",[7968,8273,8274],{"class":7984}," })\n",[7968,8276,8277,8280,8282],{"class":5110,"line":4209},[7968,8278,8279],{"class":7984},"\u003C\u002F",[7968,8281,8245],{"class":8244},[7968,8283,8260],{"class":7984},[7968,8285,8286],{"class":5110,"line":4219},[7968,8287,8288],{"emptyLinePlaceholder":4259},"\n",[7968,8290,8292,8294,8297],{"class":5110,"line":8291},5,[7968,8293,8241],{"class":7984},[7968,8295,8296],{"class":8244},"template",[7968,8298,8260],{"class":7984},[7968,8300,8302,8305],{"class":5110,"line":8301},6,[7968,8303,8304],{"class":7984},"  \u003C",[7968,8306,8307],{"class":8244},"AppPagePlaceholder\n",[7968,8309,8311,8314,8316],{"class":5110,"line":8310},7,[7968,8312,8313],{"class":7980},"    title",[7968,8315,8254],{"class":7984},[7968,8317,8318],{"class":7993},"\"XXX\"\n",[7968,8320,8322,8325,8327],{"class":5110,"line":8321},8,[7968,8323,8324],{"class":7980},"    category",[7968,8326,8254],{"class":7984},[7968,8328,8329],{"class":7993},"\"대분류 · 소분류\"\n",[7968,8331,8333,8336,8338],{"class":5110,"line":8332},9,[7968,8334,8335],{"class":7980},"    description",[7968,8337,8254],{"class":7984},[7968,8339,8340],{"class":7993},"\"간단한 설명\"\n",[7968,8342,8344],{"class":5110,"line":8343},10,[7968,8345,8346],{"class":7984},"  \u002F>\n",[7968,8348,8350,8352,8354],{"class":5110,"line":8349},11,[7968,8351,8279],{"class":7984},[7968,8353,8296],{"class":8244},[7968,8355,8260],{"class":7984},[18,8357,8358],{},"본격 구현 시:",[5251,8360,8362],{"className":8232,"code":8361,"language":8234,"meta":4208,"style":4208},"\u003Cscript setup lang=\"ts\">\nuseHead({ title: 'XXX' })\n\n\u002F\u002F 1. 타입 \u002F 스키마 (Zod)\n\u002F\u002F 2. 상태 (ref\u002Fcomputed)\n\u002F\u002F 3. 데이터 fetch (useFetch \u002F useApi)\n\u002F\u002F 4. 핸들러\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"app-container py-8\">\n    \u003Cheader class=\"mb-6\">\n      \u003Ch1 class=\"text-2xl font-bold\">XXX\u003C\u002Fh1>\n      \u003Cp class=\"text-sm text-gray-500 mt-1\">설명\u003C\u002Fp>\n    \u003C\u002Fheader>\n    \u003C!-- 본문 -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n\u002F* 페이지 전용 스타일 *\u002F\n\u003C\u002Fstyle>\n",[32,8363,8364,8380,8390,8394,8400,8405,8410,8415,8423,8427,8435,8452,8470,8492,8513,8523,8529,8539,8548,8553,8566,8572],{"__ignoreMap":4208},[7968,8365,8366,8368,8370,8372,8374,8376,8378],{"class":5110,"line":7970},[7968,8367,8241],{"class":7984},[7968,8369,8245],{"class":8244},[7968,8371,8248],{"class":7980},[7968,8373,8251],{"class":7980},[7968,8375,8254],{"class":7984},[7968,8377,8257],{"class":7993},[7968,8379,8260],{"class":7984},[7968,8381,8382,8384,8386,8388],{"class":5110,"line":4212},[7968,8383,8265],{"class":7980},[7968,8385,8268],{"class":7984},[7968,8387,8271],{"class":7993},[7968,8389,8274],{"class":7984},[7968,8391,8392],{"class":5110,"line":4209},[7968,8393,8288],{"emptyLinePlaceholder":4259},[7968,8395,8396],{"class":5110,"line":4219},[7968,8397,8399],{"class":8398},"sJ8bj","\u002F\u002F 1. 타입 \u002F 스키마 (Zod)\n",[7968,8401,8402],{"class":5110,"line":8291},[7968,8403,8404],{"class":8398},"\u002F\u002F 2. 상태 (ref\u002Fcomputed)\n",[7968,8406,8407],{"class":5110,"line":8301},[7968,8408,8409],{"class":8398},"\u002F\u002F 3. 데이터 fetch (useFetch \u002F useApi)\n",[7968,8411,8412],{"class":5110,"line":8310},[7968,8413,8414],{"class":8398},"\u002F\u002F 4. 핸들러\n",[7968,8416,8417,8419,8421],{"class":5110,"line":8321},[7968,8418,8279],{"class":7984},[7968,8420,8245],{"class":8244},[7968,8422,8260],{"class":7984},[7968,8424,8425],{"class":5110,"line":8332},[7968,8426,8288],{"emptyLinePlaceholder":4259},[7968,8428,8429,8431,8433],{"class":5110,"line":8343},[7968,8430,8241],{"class":7984},[7968,8432,8296],{"class":8244},[7968,8434,8260],{"class":7984},[7968,8436,8437,8439,8442,8445,8447,8450],{"class":5110,"line":8349},[7968,8438,8304],{"class":7984},[7968,8440,8441],{"class":8244},"div",[7968,8443,8444],{"class":7980}," class",[7968,8446,8254],{"class":7984},[7968,8448,8449],{"class":7993},"\"app-container py-8\"",[7968,8451,8260],{"class":7984},[7968,8453,8455,8458,8461,8463,8465,8468],{"class":5110,"line":8454},12,[7968,8456,8457],{"class":7984},"    \u003C",[7968,8459,8460],{"class":8244},"header",[7968,8462,8444],{"class":7980},[7968,8464,8254],{"class":7984},[7968,8466,8467],{"class":7993},"\"mb-6\"",[7968,8469,8260],{"class":7984},[7968,8471,8473,8476,8478,8480,8482,8485,8488,8490],{"class":5110,"line":8472},13,[7968,8474,8475],{"class":7984},"      \u003C",[7968,8477,11],{"class":8244},[7968,8479,8444],{"class":7980},[7968,8481,8254],{"class":7984},[7968,8483,8484],{"class":7993},"\"text-2xl font-bold\"",[7968,8486,8487],{"class":7984},">XXX\u003C\u002F",[7968,8489,11],{"class":8244},[7968,8491,8260],{"class":7984},[7968,8493,8495,8497,8499,8501,8503,8506,8509,8511],{"class":5110,"line":8494},14,[7968,8496,8475],{"class":7984},[7968,8498,18],{"class":8244},[7968,8500,8444],{"class":7980},[7968,8502,8254],{"class":7984},[7968,8504,8505],{"class":7993},"\"text-sm text-gray-500 mt-1\"",[7968,8507,8508],{"class":7984},">설명\u003C\u002F",[7968,8510,18],{"class":8244},[7968,8512,8260],{"class":7984},[7968,8514,8516,8519,8521],{"class":5110,"line":8515},15,[7968,8517,8518],{"class":7984},"    \u003C\u002F",[7968,8520,8460],{"class":8244},[7968,8522,8260],{"class":7984},[7968,8524,8526],{"class":5110,"line":8525},16,[7968,8527,8528],{"class":8398},"    \u003C!-- 본문 -->\n",[7968,8530,8532,8535,8537],{"class":5110,"line":8531},17,[7968,8533,8534],{"class":7984},"  \u003C\u002F",[7968,8536,8441],{"class":8244},[7968,8538,8260],{"class":7984},[7968,8540,8542,8544,8546],{"class":5110,"line":8541},18,[7968,8543,8279],{"class":7984},[7968,8545,8296],{"class":8244},[7968,8547,8260],{"class":7984},[7968,8549,8551],{"class":5110,"line":8550},19,[7968,8552,8288],{"emptyLinePlaceholder":4259},[7968,8554,8556,8558,8561,8564],{"class":5110,"line":8555},20,[7968,8557,8241],{"class":7984},[7968,8559,8560],{"class":8244},"style",[7968,8562,8563],{"class":7980}," scoped",[7968,8565,8260],{"class":7984},[7968,8567,8569],{"class":5110,"line":8568},21,[7968,8570,8571],{"class":8398},"\u002F* 페이지 전용 스타일 *\u002F\n",[7968,8573,8575,8577,8579],{"class":5110,"line":8574},22,[7968,8576,8279],{"class":7984},[7968,8578,8560],{"class":8244},[7968,8580,8260],{"class":7984},[237,8582,8584,8585,4343],{"id":8583},"_43-페이지-메타-definepagemeta","4.3 페이지 메타 (",[32,8586,8587],{},"definePageMeta",[101,8589,8590,8600],{},[104,8591,8592],{},[107,8593,8594,8597],{},[110,8595,8596],{},"페이지 종류",[110,8598,8599],{},"설정",[126,8601,8602,8610,8620,8630],{},[107,8603,8604,8607],{},[131,8605,8606],{},"일반 (GNB + Footer)",[131,8608,8609],{},"(기본, 설정 없음)",[107,8611,8612,8615],{},[131,8613,8614],{},"로그인\u002F회원가입\u002F재설정",[131,8616,8617],{},[32,8618,8619],{},"definePageMeta({ layout: 'auth', auth: false })",[107,8621,8622,8625],{},[131,8623,8624],{},"404\u002F시스템 에러\u002F점검",[131,8626,8627],{},[32,8628,8629],{},"definePageMeta({ layout: 'blank', auth: false })",[107,8631,8632,8635],{},[131,8633,8634],{},"리다이렉트만 하는 페이지",[131,8636,8637],{},[32,8638,8639],{},"definePageMeta({ layout: false })",[237,8641,8643],{"id":8642},"_44-자동-임포트-nuxt-4","4.4 자동 임포트 (Nuxt 4)",[18,8645,8646],{},"다음 API\u002F유틸은 import 없이 사용 가능:",[81,8648,8649,8673,8705,8715,8729],{},[84,8650,8651,8652,4473,8655,4473,8658,4473,8661,4473,8664,4473,8667,4473,8670],{},"Vue: ",[32,8653,8654],{},"ref",[32,8656,8657],{},"computed",[32,8659,8660],{},"watch",[32,8662,8663],{},"onMounted",[32,8665,8666],{},"defineProps",[32,8668,8669],{},"defineEmits",[32,8671,8672],{},"defineModel",[84,8674,8675,8676,4473,8678,4473,8681,4473,8684,4473,8687,4473,8690,4473,8693,4473,8696,4473,8699,4473,8702],{},"Nuxt: ",[32,8677,8265],{},[32,8679,8680],{},"useState",[32,8682,8683],{},"useRoute",[32,8685,8686],{},"useRouter",[32,8688,8689],{},"useFetch",[32,8691,8692],{},"$fetch",[32,8694,8695],{},"navigateTo",[32,8697,8698],{},"useRuntimeConfig",[32,8700,8701],{},"useNuxtApp",[32,8703,8704],{},"useToast",[84,8706,8707,8708,8711,8712,8714],{},"Components: ",[32,8709,8710],{},"app\u002Fcomponents\u002F"," 아래 전부, Nuxt UI ",[32,8713,7478],{}," 전부",[84,8716,8717,8718,8721,8722,4473,8725,8728],{},"composables: ",[32,8719,8720],{},"app\u002Fcomposables\u002F"," 아래 전부 (",[32,8723,8724],{},"useApi",[32,8726,8727],{},"useExportJob"," 등)",[84,8730,8731,8732,8721,8735,4343],{},"stores: ",[32,8733,8734],{},"app\u002Fstores\u002F",[32,8736,8737],{},"useAuthStore",[237,8739,8741],{"id":8740},"_45-접근성","4.5 접근성",[81,8743,8744,8750,8753,8760],{},[84,8745,8746,8747,8749],{},"form 요소에 label 또는 ",[32,8748,6863],{}," 필수",[84,8751,8752],{},"키보드 조작 가능 — Nuxt UI(Reka UI)가 대부분 처리해 줍니다",[84,8754,8755,8756,8759],{},"focus ring 유지 (Tailwind 기본 ",[32,8757,8758],{},"focus-visible:ring-*"," 또는 Nuxt UI 기본 스타일)",[84,8761,8762,8763,8766,8767,8769],{},"한국어 콘텐츠: ",[32,8764,8765],{},"\u003Chtml lang=\"ko\">"," (이미 ",[32,8768,4405],{},"에서 설정)",[73,8771],{},[76,8773,8775],{"id":8774},"_5-css-작성-규칙","5. CSS 작성 규칙",[237,8777,8779],{"id":8778},"_51-우선순위","5.1 우선순위",[6674,8781,8782,8788,8799,8814],{},[84,8783,8784,8787],{},[21,8785,8786],{},"Nuxt UI 컴포넌트의 색·간격·라운드 기본값"," 우선 사용",[84,8789,8790,8791,8794,8795,8798],{},"필요하면 Nuxt UI 컴포넌트의 ",[32,8792,8793],{},"class"," prop 또는 ",[32,8796,8797],{},":ui"," prop으로 미세 조정",[84,8800,8801,8802,8807,8808,4536,8811,8728],{},"자체 CSS는 ",[21,8803,8804],{},[32,8805,8806],{},"\u003Cstyle scoped>"," + CSS 변수(",[32,8809,8810],{},"var(--line)",[32,8812,8813],{},"var(--ink-*)",[84,8815,8816,8817,4343],{},"마지막 수단으로 Tailwind 임의값 (",[32,8818,8819],{},"text-[#1f2937]",[237,8821,8823],{"id":8822},"_52-디자인-토큰-사용","5.2 디자인 토큰 사용",[18,8825,8826,8827,8829,8830,8832,8833,4703],{},"자체 CSS는 항상 ",[26,8828,7689],{"href":7688},"의 ink\u002Faccent\u002Fsemantic 토큰을 CSS 변수로 씁니다. 구 시안 base.css·",[32,8831,4608],{}," 별칭은 2026-05-18 피벗으로 폐기됨(별칭은 main.css에 backward-compat용으로만 남아 있음) — ",[21,8834,8835,8836,4361,8839,4361,8841,8843],{},"신규 코드는 ",[32,8837,8838],{},"--ink-*",[32,8840,4977],{},[32,8842,5027],{},"를 직접 사용",[18,8845,8846,8847,4536,8849,4536,8851,4536,8853,8855,8856,8858],{},"공용 클래스(",[32,8848,4360],{},[32,8850,4364],{},[32,8852,4367],{},[32,8854,6047],{}," 등)는 ",[26,8857,4771],{"href":4351},"가 정본이며, 컴포넌트는 이 클래스를 그대로 차용하면 유지보수가 쉬워집니다.",[18,8860,8861,8862,8864],{},"예 (",[26,8863,8210],{"href":4418},"):",[5251,8866,8868],{"className":8232,"code":8867,"language":8234,"meta":4208,"style":4208},"\u003Cstyle scoped>\n.gnb {\n  position: sticky;\n  top: 0;\n  z-index: 1030;\n  background: var(--white);\n  border-bottom: 1px solid var(--line);\n  height: var(--gnb-height);\n}\n.gnb-nav-item:hover .gnb-dropdown {\n  opacity: 1;\n  visibility: visible;\n  transform: translateY(0);\n}\n\u003C\u002Fstyle>\n",[32,8869,8870,8880,8888,8902,8913,8925,8943,8967,8983,8988,8998,9009,9021,9037,9041],{"__ignoreMap":4208},[7968,8871,8872,8874,8876,8878],{"class":5110,"line":7970},[7968,8873,8241],{"class":7984},[7968,8875,8560],{"class":8244},[7968,8877,8563],{"class":7980},[7968,8879,8260],{"class":7984},[7968,8881,8882,8885],{"class":5110,"line":4212},[7968,8883,8884],{"class":7980},".gnb",[7968,8886,8887],{"class":7984}," {\n",[7968,8889,8890,8894,8896,8899],{"class":5110,"line":4209},[7968,8891,8893],{"class":8892},"sj4cs","  position",[7968,8895,47],{"class":7984},[7968,8897,8898],{"class":8892},"sticky",[7968,8900,8901],{"class":7984},";\n",[7968,8903,8904,8907,8909,8911],{"class":5110,"line":4219},[7968,8905,8906],{"class":8892},"  top",[7968,8908,47],{"class":7984},[7968,8910,5354],{"class":8892},[7968,8912,8901],{"class":7984},[7968,8914,8915,8918,8920,8923],{"class":5110,"line":8291},[7968,8916,8917],{"class":8892},"  z-index",[7968,8919,47],{"class":7984},[7968,8921,8922],{"class":8892},"1030",[7968,8924,8901],{"class":7984},[7968,8926,8927,8930,8932,8935,8937,8940],{"class":5110,"line":8301},[7968,8928,8929],{"class":8892},"  background",[7968,8931,47],{"class":7984},[7968,8933,8934],{"class":8892},"var",[7968,8936,6100],{"class":7984},[7968,8938,4994],{"class":8939},"s4XuR",[7968,8941,8942],{"class":7984},");\n",[7968,8944,8945,8948,8950,8952,8955,8958,8961,8963,8965],{"class":5110,"line":8310},[7968,8946,8947],{"class":8892},"  border-bottom",[7968,8949,47],{"class":7984},[7968,8951,4636],{"class":8892},[7968,8953,8954],{"class":7973},"px",[7968,8956,8957],{"class":8892}," solid",[7968,8959,8960],{"class":8892}," var",[7968,8962,6100],{"class":7984},[7968,8964,4977],{"class":8939},[7968,8966,8942],{"class":7984},[7968,8968,8969,8972,8974,8976,8978,8981],{"class":5110,"line":8321},[7968,8970,8971],{"class":8892},"  height",[7968,8973,47],{"class":7984},[7968,8975,8934],{"class":8892},[7968,8977,6100],{"class":7984},[7968,8979,8980],{"class":8939},"--gnb-height",[7968,8982,8942],{"class":7984},[7968,8984,8985],{"class":5110,"line":8332},[7968,8986,8987],{"class":7984},"}\n",[7968,8989,8990,8993,8996],{"class":5110,"line":8343},[7968,8991,8992],{"class":7980},".gnb-nav-item:hover",[7968,8994,8995],{"class":7980}," .gnb-dropdown",[7968,8997,8887],{"class":7984},[7968,8999,9000,9003,9005,9007],{"class":5110,"line":8349},[7968,9001,9002],{"class":8892},"  opacity",[7968,9004,47],{"class":7984},[7968,9006,4636],{"class":8892},[7968,9008,8901],{"class":7984},[7968,9010,9011,9014,9016,9019],{"class":5110,"line":8454},[7968,9012,9013],{"class":8892},"  visibility",[7968,9015,47],{"class":7984},[7968,9017,9018],{"class":8892},"visible",[7968,9020,8901],{"class":7984},[7968,9022,9023,9026,9028,9031,9033,9035],{"class":5110,"line":8472},[7968,9024,9025],{"class":8892},"  transform",[7968,9027,47],{"class":7984},[7968,9029,9030],{"class":8892},"translateY",[7968,9032,6100],{"class":7984},[7968,9034,5354],{"class":8892},[7968,9036,8942],{"class":7984},[7968,9038,9039],{"class":5110,"line":8494},[7968,9040,8987],{"class":7984},[7968,9042,9043,9045,9047],{"class":5110,"line":8515},[7968,9044,8279],{"class":7984},[7968,9046,8560],{"class":8244},[7968,9048,8260],{"class":7984},[237,9050,9052],{"id":9051},"_53-절대-하지-말-것","5.3 절대 하지 말 것",[81,9054,9055,9060,9067,9073],{},[84,9056,9057,9059],{},[32,9058,7782],{}," 설치 (Nuxt UI 모듈이 Tailwind를 통합 관리)",[84,9061,9062,9063,9066],{},"인라인 ",[32,9064,9065],{},"style=\"color: #6366f1\""," 같은 하드코딩 (단, 동적 색상은 인라인 OK)",[84,9068,9069,9070,9072],{},"전역 ",[32,9071,4338],{}," 변수를 컴포넌트 안에서 재정의 (예외적인 경우만)",[84,9074,9075,9078],{},[32,9076,9077],{},"!important"," 남용 (Tailwind v4의 우선순위가 충분히 강합니다)",[237,9080,9082],{"id":9081},"_54-다크-모드","5.4 다크 모드",[18,9084,9085,9086,9089],{},"현 단계: 라이트 모드 단일. CSS 변수가 다크 호환 가능한 구조라 추후 ",[32,9087,9088],{},":root[data-theme=\"dark\"]","에서 변수 재정의로 도입.",[73,9091],{},[76,9093,9095],{"id":9094},"_6-js-ts-작성-규칙","6. JS \u002F TS 작성 규칙",[237,9097,9099],{"id":9098},"_61-타입","6.1 타입",[81,9101,9102,9113,9129,9189],{},[84,9103,9104,9108,9109,9112],{},[21,9105,9106,7752],{},[32,9107,7751],{}," (불가피하면 ",[32,9110,9111],{},"unknown"," 후 narrow)",[84,9114,9115,9116,9122,9123],{},"도메인 타입은 ",[26,9117,9119],{"href":9118},"..\u002Fapp\u002Ftypes\u002Fdomain.ts",[32,9120,9121],{},"app\u002Ftypes\u002Fdomain.ts",", API 타입은 ",[26,9124,9126],{"href":9125},"..\u002Fapp\u002Ftypes\u002Fapi.ts",[32,9127,9128],{},"app\u002Ftypes\u002Fapi.ts",[84,9130,9131,9132],{},"props\u002Femits는 제네릭 형태:\n",[5251,9133,9135],{"className":7962,"code":9134,"language":7964,"meta":4208,"style":4208},"defineProps\u003C{ title: string; count?: number }>()\ndefineEmits\u003C{ confirm: []; cancel: [] }>()\n",[32,9136,9137,9167],{"__ignoreMap":4208},[7968,9138,9139,9141,9144,9146,9149,9152,9155,9158,9161,9164],{"class":5110,"line":7970},[7968,9140,8666],{"class":7980},[7968,9142,9143],{"class":7984},"\u003C{ ",[7968,9145,7082],{"class":8939},[7968,9147,9148],{"class":7973},":",[7968,9150,9151],{"class":8892}," string",[7968,9153,9154],{"class":7984},"; ",[7968,9156,9157],{"class":8939},"count",[7968,9159,9160],{"class":7973},"?:",[7968,9162,9163],{"class":8892}," number",[7968,9165,9166],{"class":7984}," }>()\n",[7968,9168,9169,9171,9173,9176,9178,9181,9184,9186],{"class":5110,"line":4212},[7968,9170,8669],{"class":7980},[7968,9172,9143],{"class":7984},[7968,9174,9175],{"class":8939},"confirm",[7968,9177,9148],{"class":7973},[7968,9179,9180],{"class":7984}," []; ",[7968,9182,9183],{"class":8939},"cancel",[7968,9185,9148],{"class":7973},[7968,9187,9188],{"class":7984}," [] }>()\n",[84,9190,9191,9192,9194,9195],{},"v-model은 ",[32,9193,8672],{},":\n",[5251,9196,9198],{"className":7962,"code":9197,"language":7964,"meta":4208,"style":4208},"const open = defineModel\u003Cboolean>('open', { default: false })\n",[32,9199,9200],{"__ignoreMap":4208},[7968,9201,9202,9205,9208,9211,9214,9216,9219,9222,9225,9228,9231],{"class":5110,"line":7970},[7968,9203,9204],{"class":7973},"const",[7968,9206,9207],{"class":8892}," open",[7968,9209,9210],{"class":7973}," =",[7968,9212,9213],{"class":7980}," defineModel",[7968,9215,8241],{"class":7984},[7968,9217,9218],{"class":8892},"boolean",[7968,9220,9221],{"class":7984},">(",[7968,9223,9224],{"class":7993},"'open'",[7968,9226,9227],{"class":7984},", { default: ",[7968,9229,9230],{"class":8892},"false",[7968,9232,8274],{"class":7984},[237,9234,9236],{"id":9235},"_62-api-호출","6.2 API 호출",[18,9238,9239,9240,9242,9243,9148],{},"직접 ",[32,9241,8692],{}," 호출 대신 항상 ",[26,9244,9246],{"href":9245},"..\u002Fapp\u002Fcomposables\u002FuseApi.ts",[32,9247,9248],{},"useApi()",[5251,9250,9252],{"className":7962,"code":9251,"language":7964,"meta":4208,"style":4208},"const api = useApi()\nconst items = await api\u003CPaginated\u003CItem>>('\u002Fmessages', {\n  query: { page: 1, channel: 'sms' }\n})\n",[32,9253,9254,9269,9302,9318],{"__ignoreMap":4208},[7968,9255,9256,9258,9261,9263,9266],{"class":5110,"line":7970},[7968,9257,9204],{"class":7973},[7968,9259,9260],{"class":8892}," api",[7968,9262,9210],{"class":7973},[7968,9264,9265],{"class":7980}," useApi",[7968,9267,9268],{"class":7984},"()\n",[7968,9270,9271,9273,9276,9278,9281,9283,9285,9288,9290,9293,9296,9299],{"class":5110,"line":4212},[7968,9272,9204],{"class":7973},[7968,9274,9275],{"class":8892}," items",[7968,9277,9210],{"class":7973},[7968,9279,9280],{"class":7973}," await",[7968,9282,9260],{"class":7980},[7968,9284,8241],{"class":7984},[7968,9286,9287],{"class":7980},"Paginated",[7968,9289,8241],{"class":7984},[7968,9291,9292],{"class":7980},"Item",[7968,9294,9295],{"class":7984},">>(",[7968,9297,9298],{"class":7993},"'\u002Fmessages'",[7968,9300,9301],{"class":7984},", {\n",[7968,9303,9304,9307,9309,9312,9315],{"class":5110,"line":4209},[7968,9305,9306],{"class":7984},"  query: { page: ",[7968,9308,4636],{"class":8892},[7968,9310,9311],{"class":7984},", channel: ",[7968,9313,9314],{"class":7993},"'sms'",[7968,9316,9317],{"class":7984}," }\n",[7968,9319,9320],{"class":5110,"line":4219},[7968,9321,8007],{"class":7984},[18,9323,9324],{},"장점:",[81,9326,9327,9330,9337],{},[84,9328,9329],{},"baseURL \u002F credentials \u002F Accept 자동",[84,9331,9332,9333,9336],{},"401 → ",[32,9334,9335],{},"\u002Flogin"," 리다이렉트 자동",[84,9338,9339],{},"에러 표준화",[237,9341,9343],{"id":9342},"_63-composable-패턴","6.3 composable 패턴",[81,9345,9346,9353,9363],{},[84,9347,9348,9349,9352],{},"파일명·함수명 모두 ",[32,9350,9351],{},"use*","로 시작",[84,9354,9355,9356,4473,9358,4473,9360,4343],{},"한 책임에 집중 (",[32,9357,8724],{},[32,9359,8727],{},[32,9361,9362],{},"useAiTemplate",[84,9364,9365,9366,9369],{},"반환은 객체로 (",[32,9367,9368],{},"return { data, refresh }"," 같은 형태)",[237,9371,9373],{"id":9372},"_64-pinia-store","6.4 Pinia store",[81,9375,9376,9386],{},[84,9377,9378,9379,9382,9383,4343],{},"파일: ",[32,9380,9381],{},"app\u002Fstores\u002F{name}.ts"," → 자동 import (",[32,9384,9385],{},"useXxxStore",[84,9387,9388,9389],{},"옵션 API 형식 (Pinia 표준):\n",[5251,9390,9392],{"className":7962,"code":9391,"language":7964,"meta":4208,"style":4208},"export const useAuthStore = defineStore('auth', {\n  state: () => ({ ... }),\n  getters: { ... },\n  actions: { async logout() { ... } }\n})\n",[32,9393,9394,9416,9436,9446,9464],{"__ignoreMap":4208},[7968,9395,9396,9398,9401,9404,9406,9409,9411,9414],{"class":5110,"line":7970},[7968,9397,7974],{"class":7973},[7968,9399,9400],{"class":7973}," const",[7968,9402,9403],{"class":8892}," useAuthStore",[7968,9405,9210],{"class":7973},[7968,9407,9408],{"class":7980}," defineStore",[7968,9410,6100],{"class":7984},[7968,9412,9413],{"class":7993},"'auth'",[7968,9415,9301],{"class":7984},[7968,9417,9418,9421,9424,9427,9430,9433],{"class":5110,"line":4212},[7968,9419,9420],{"class":7980},"  state",[7968,9422,9423],{"class":7984},": () ",[7968,9425,9426],{"class":7973},"=>",[7968,9428,9429],{"class":7984}," ({ ",[7968,9431,9432],{"class":7973},"...",[7968,9434,9435],{"class":7984}," }),\n",[7968,9437,9438,9441,9443],{"class":5110,"line":4209},[7968,9439,9440],{"class":7984},"  getters: { ",[7968,9442,9432],{"class":7973},[7968,9444,9445],{"class":7984}," },\n",[7968,9447,9448,9451,9454,9457,9460,9462],{"class":5110,"line":4219},[7968,9449,9450],{"class":7984},"  actions: { ",[7968,9452,9453],{"class":7973},"async",[7968,9455,9456],{"class":7980}," logout",[7968,9458,9459],{"class":7984},"() { ",[7968,9461,9432],{"class":7973},[7968,9463,8002],{"class":7984},[7968,9465,9466],{"class":5110,"line":8291},[7968,9467,8007],{"class":7984},[237,9469,9471],{"id":9470},"_65-검증-zod","6.5 검증 (Zod)",[18,9473,9474],{},"폼·외부 응답은 Zod로 파싱:",[5251,9476,9478],{"className":7962,"code":9477,"language":7964,"meta":4208,"style":4208},"import { z } from 'zod'\n\nconst SendSmsSchema = z.object({\n  to: z.string().min(1),\n  body: z.string().min(1).max(2000)\n})\ntype SendSms = z.infer\u003Ctypeof SendSmsSchema>\n",[32,9479,9480,9494,9498,9515,9536,9564,9568],{"__ignoreMap":4208},[7968,9481,9482,9485,9488,9491],{"class":5110,"line":7970},[7968,9483,9484],{"class":7973},"import",[7968,9486,9487],{"class":7984}," { z } ",[7968,9489,9490],{"class":7973},"from",[7968,9492,9493],{"class":7993}," 'zod'\n",[7968,9495,9496],{"class":5110,"line":4212},[7968,9497,8288],{"emptyLinePlaceholder":4259},[7968,9499,9500,9502,9505,9507,9510,9513],{"class":5110,"line":4209},[7968,9501,9204],{"class":7973},[7968,9503,9504],{"class":8892}," SendSmsSchema",[7968,9506,9210],{"class":7973},[7968,9508,9509],{"class":7984}," z.",[7968,9511,9512],{"class":7980},"object",[7968,9514,7985],{"class":7984},[7968,9516,9517,9520,9523,9526,9529,9531,9533],{"class":5110,"line":4219},[7968,9518,9519],{"class":7984},"  to: z.",[7968,9521,9522],{"class":7980},"string",[7968,9524,9525],{"class":7984},"().",[7968,9527,9528],{"class":7980},"min",[7968,9530,6100],{"class":7984},[7968,9532,4636],{"class":8892},[7968,9534,9535],{"class":7984},"),\n",[7968,9537,9538,9541,9543,9545,9547,9549,9551,9553,9556,9558,9561],{"class":5110,"line":8291},[7968,9539,9540],{"class":7984},"  body: z.",[7968,9542,9522],{"class":7980},[7968,9544,9525],{"class":7984},[7968,9546,9528],{"class":7980},[7968,9548,6100],{"class":7984},[7968,9550,4636],{"class":8892},[7968,9552,5606],{"class":7984},[7968,9554,9555],{"class":7980},"max",[7968,9557,6100],{"class":7984},[7968,9559,9560],{"class":8892},"2000",[7968,9562,9563],{"class":7984},")\n",[7968,9565,9566],{"class":5110,"line":8301},[7968,9567,8007],{"class":7984},[7968,9569,9570,9573,9576,9578,9581,9583,9586,9588,9591],{"class":5110,"line":8310},[7968,9571,9572],{"class":7973},"type",[7968,9574,9575],{"class":7980}," SendSms",[7968,9577,9210],{"class":7973},[7968,9579,9580],{"class":7980}," z",[7968,9582,4703],{"class":7984},[7968,9584,9585],{"class":7980},"infer",[7968,9587,8241],{"class":7984},[7968,9589,9590],{"class":7973},"typeof",[7968,9592,9593],{"class":7984}," SendSmsSchema>\n",[237,9595,9597],{"id":9596},"_66-시간","6.6 시간",[18,9599,9600,9601,9604],{},"UTC ISO 8601 (",[32,9602,9603],{},"2026-05-12T03:04:05Z",")로 주고받고, 표시용 변환은 프론트엔드 책임. 라이브러리 미정 (Day.js \u002F date-fns 검토).",[73,9606],{},[76,9608,9610],{"id":9609},"_7-nuxt-ui-컴포넌트-매핑-가이드","7. Nuxt UI 컴포넌트 매핑 가이드",[18,9612,9613],{},"시안의 반복 패턴 → Nuxt UI 컴포넌트 매핑.",[101,9615,9616,9626],{},[104,9617,9618],{},[107,9619,9620,9623],{},[110,9621,9622],{},"시안 패턴",[110,9624,9625],{},"Nuxt UI \u002F 자체 컴포넌트",[126,9627,9628,9641,9657,9671,9685,9702,9714,9725,9738,9756,9766,9776,9786,9797,9807],{},[107,9629,9630,9633],{},[131,9631,9632],{},"모달 (확인\u002F알림)",[131,9634,9635,9637,9638,9640],{},[32,9636,6117],{}," (자체 ",[32,9639,4476],{}," 기반)",[107,9642,9643,9646],{},[131,9644,9645],{},"팝업\u002F다이얼로그(수신자 정보, 발송 컨펌, 템플릿 선택 등)",[131,9647,9648,9649,9651,9652,89,9654,9656],{},"자체 ",[32,9650,4476],{}," 기반 ",[32,9653,6120],{},[32,9655,6124],{},"는 모바일 GNB 드로어 전용",[107,9658,9659,9662],{},[131,9660,9661],{},"다운로드 요청 완료 토스트",[131,9663,9664,9666,9667,9670],{},[32,9665,6633],{}," → 내부에서 ",[32,9668,9669],{},"UNotification"," 표시",[107,9672,9673,9676],{},[131,9674,9675],{},"채널\u002F카테고리 트리",[131,9677,9678,9681,9682],{},[32,9679,9680],{},"UNavigationMenu"," (수직) 또는 자체 ",[32,9683,9684],{},"AppCategoryTree",[107,9686,9687,9690],{},[131,9688,9689],{},"목록 + 정렬 + 페이징",[131,9691,9692,4339,9695,9698,9699],{},[32,9693,9694],{},"UTable",[32,9696,9697],{},"UPagination"," + 공용 ",[32,9700,9701],{},"AppFilterBar",[107,9703,9704,9707],{},[131,9705,9706],{},"폼 + 검증",[131,9708,9709,4339,9711,9713],{},[32,9710,6077],{},[32,9712,6081],{}," + Zod 스키마",[107,9715,9716,9719],{},[131,9717,9718],{},"드롭다운 메뉴",[131,9720,9721,9724],{},[32,9722,9723],{},"UDropdownMenu","(click) 또는 시안 hover 스타일은 자체 CSS",[107,9726,9727,9730],{},[131,9728,9729],{},"라디오 카드 \u002F 체크박스",[131,9731,9732,4473,9735],{},[32,9733,9734],{},"URadioGroup",[32,9736,9737],{},"UCheckbox",[107,9739,9740,9743],{},[131,9741,9742],{},"입력",[131,9744,9745,4473,9747,4473,9750,4473,9753],{},[32,9746,6084],{},[32,9748,9749],{},"UTextarea",[32,9751,9752],{},"USelect",[32,9754,9755],{},"UInputNumber",[107,9757,9758,9761],{},[131,9759,9760],{},"토글",[131,9762,9763],{},[32,9764,9765],{},"USwitch",[107,9767,9768,9771],{},[131,9769,9770],{},"탭",[131,9772,9773],{},[32,9774,9775],{},"UTabs",[107,9777,9778,9781],{},[131,9779,9780],{},"아바타",[131,9782,9783],{},[32,9784,9785],{},"UAvatar",[107,9787,9788,9791],{},[131,9789,9790],{},"카드",[131,9792,9793,9796],{},[32,9794,9795],{},"UCard"," (또는 직접 div + 시안 스타일)",[107,9798,9799,9802],{},[131,9800,9801],{},"배지",[131,9803,9804],{},[32,9805,9806],{},"UBadge",[107,9808,9809,9812],{},[131,9810,9811],{},"알림 박스",[131,9813,9814],{},[32,9815,9816],{},"UAlert",[18,9818,9819,9821],{},[32,9820,6117],{}," 사용 예 (위험 액션 컨펌):",[5251,9823,9825],{"className":8232,"code":9824,"language":8234,"meta":4208,"style":4208},"\u003CAppConfirmDialog\n  v-model:open=\"open\"\n  title=\"이 캠페인을 삭제할까요?\"\n  description=\"이미 발송된 메시지는 영향을 받지 않습니다.\"\n  confirm-color=\"error\"\n  confirm-label=\"삭제\"\n  @confirm=\"onDelete\"\n\u002F>\n",[32,9826,9827,9834,9854,9864,9874,9884,9894,9910],{"__ignoreMap":4208},[7968,9828,9829,9831],{"class":5110,"line":7970},[7968,9830,8241],{"class":7984},[7968,9832,9833],{"class":8244},"AppConfirmDialog\n",[7968,9835,9836,9839,9841,9844,9846,9849,9851],{"class":5110,"line":4212},[7968,9837,9838],{"class":7980},"  v-model",[7968,9840,9148],{"class":7984},[7968,9842,9843],{"class":7980},"open",[7968,9845,8254],{"class":7984},[7968,9847,9848],{"class":7993},"\"",[7968,9850,9843],{"class":7984},[7968,9852,9853],{"class":7993},"\"\n",[7968,9855,9856,9859,9861],{"class":5110,"line":4209},[7968,9857,9858],{"class":7980},"  title",[7968,9860,8254],{"class":7984},[7968,9862,9863],{"class":7993},"\"이 캠페인을 삭제할까요?\"\n",[7968,9865,9866,9869,9871],{"class":5110,"line":4219},[7968,9867,9868],{"class":7980},"  description",[7968,9870,8254],{"class":7984},[7968,9872,9873],{"class":7993},"\"이미 발송된 메시지는 영향을 받지 않습니다.\"\n",[7968,9875,9876,9879,9881],{"class":5110,"line":8291},[7968,9877,9878],{"class":7980},"  confirm-color",[7968,9880,8254],{"class":7984},[7968,9882,9883],{"class":7993},"\"error\"\n",[7968,9885,9886,9889,9891],{"class":5110,"line":8301},[7968,9887,9888],{"class":7980},"  confirm-label",[7968,9890,8254],{"class":7984},[7968,9892,9893],{"class":7993},"\"삭제\"\n",[7968,9895,9896,9899,9901,9903,9905,9908],{"class":5110,"line":8310},[7968,9897,9898],{"class":7984},"  @",[7968,9900,9175],{"class":7980},[7968,9902,8254],{"class":7984},[7968,9904,9848],{"class":7993},[7968,9906,9907],{"class":7984},"onDelete",[7968,9909,9853],{"class":7993},[7968,9911,9912],{"class":5110,"line":8321},[7968,9913,9914],{"class":7984},"\u002F>\n",[73,9916],{},[76,9918,9920],{"id":9919},"_8-아이콘","8. 아이콘",[237,9922,9924],{"id":9923},"_81-컬렉션-이미-설치됨","8.1 컬렉션 (이미 설치됨)",[101,9926,9927,9939],{},[104,9928,9929],{},[107,9930,9931,9934,9936],{},[110,9932,9933],{},"컬렉션",[110,9935,4803],{},[110,9937,9938],{},"prefix",[126,9940,9941,9955,9969],{},[107,9942,9943,9947,9950],{},[131,9944,9945],{},[32,9946,7796],{},[131,9948,9949],{},"일반 UI 아이콘 (메뉴\u002F버튼\u002Fempty state)",[131,9951,9952],{},[32,9953,9954],{},"i-lucide-...",[107,9956,9957,9961,9964],{},[131,9958,9959],{},[32,9960,7800],{},[131,9962,9963],{},"Nuxt UI 기본\u002F대안",[131,9965,9966],{},[32,9967,9968],{},"i-heroicons-...",[107,9970,9971,9976,9979],{},[131,9972,9973,9975],{},[32,9974,7803],{}," (Bootstrap Icons)",[131,9977,9978],{},"시안 매칭용 (채널 카드 등)",[131,9980,9981],{},[32,9982,9983],{},"i-bi-...",[237,9985,9987],{"id":9986},"_82-사용-예","8.2 사용 예",[5251,9989,9991],{"className":8232,"code":9990,"language":8234,"meta":4208,"style":4208},"\u003CUIcon name=\"i-lucide-send\" class=\"text-xl text-primary-500\" \u002F>\n\u003CUIcon name=\"i-bi-chat-text\" class=\"text-2xl\" \u002F>\n",[32,9992,9993,10018],{"__ignoreMap":4208},[7968,9994,9995,9997,10000,10003,10005,10008,10010,10012,10015],{"class":5110,"line":7970},[7968,9996,8241],{"class":7984},[7968,9998,9999],{"class":8244},"UIcon",[7968,10001,10002],{"class":7980}," name",[7968,10004,8254],{"class":7984},[7968,10006,10007],{"class":7993},"\"i-lucide-send\"",[7968,10009,8444],{"class":7980},[7968,10011,8254],{"class":7984},[7968,10013,10014],{"class":7993},"\"text-xl text-primary-500\"",[7968,10016,10017],{"class":7984}," \u002F>\n",[7968,10019,10020,10022,10024,10026,10028,10031,10033,10035,10038],{"class":5110,"line":4212},[7968,10021,8241],{"class":7984},[7968,10023,9999],{"class":8244},[7968,10025,10002],{"class":7980},[7968,10027,8254],{"class":7984},[7968,10029,10030],{"class":7993},"\"i-bi-chat-text\"",[7968,10032,8444],{"class":7980},[7968,10034,8254],{"class":7984},[7968,10036,10037],{"class":7993},"\"text-2xl\"",[7968,10039,10017],{"class":7984},[18,10041,10042],{},"브랜드\u002F페이지 단위로 컬렉션을 일관되게:",[81,10044,10045,10052],{},[84,10046,10047,10048,10051],{},"채널 카드 → ",[32,10049,10050],{},"i-bi-*"," (시안 톤)",[84,10053,10054,10055],{},"일반 액션·메뉴 → ",[32,10056,7809],{},[73,10058],{},[76,10060,10062],{"id":10061},"_9-레이아웃-컴포넌트","9. 레이아웃 컴포넌트",[237,10064,10066,10067,4343],{"id":10065},"_91-appgnb-appgnbvue","9.1 AppGnb (",[26,10068,10069],{"href":4418},[32,10070,8210],{},[81,10072,10073,10084,10091,10094,10097],{},[84,10074,10075,10076,10079,10080,10083],{},"sticky top, ",[21,10077,10078],{},"56px 높이",", 단일 행(시안의 2단 GNB 폐기), ",[32,10081,10082],{},"rgba(255,255,255,*)"," 불투명 배경 + 하단 1px line",[84,10085,10086,10087,10090],{},"좌: 로고 (",[32,10088,10089],{},"AppLogoMark"," + \"맑은\" + \"message\")",[84,10092,10093],{},"중: 7개 메뉴 (서비스 \u002F 메시지 발송 \u002F 발송 조회·통계 \u002F 주소록 \u002F 발신 정보 \u002F 메시지 관리 \u002F 캠페인) — hover 드롭다운",[84,10095,10096],{},"우: 인증 분기 (비로그인: 문의·로그인·회원가입 \u002F 로그인: 문의·충전·사용자 아바타 드롭다운)",[84,10098,10099,10102],{},[32,10100,10101],{},"\u003C 1024px",": 햄버거 → 좌측 Drawer(280px)",[237,10104,10106,10107,4343],{"id":10105},"_92-appfooter-appfootervue","9.2 AppFooter (",[26,10108,10109],{"href":4423},[32,10110,4424],{},[81,10112,10113,10116,10119],{},[84,10114,10115],{},"검정 배경, 상·하단 2단",[84,10117,10118],{},"상단: 로고\u002F슬로건 + 정책 링크 6개",[84,10120,10121],{},"하단: 회사명 + 사업자 정보 7개 + 카피라이트",[237,10123,10125],{"id":10124},"_93-레이아웃별-적용","9.3 레이아웃별 적용",[101,10127,10128,10143],{},[104,10129,10130],{},[107,10131,10132,10135,10138,10141],{},[110,10133,10134],{},"layout",[110,10136,10137],{"align":5759},"GNB",[110,10139,10140],{"align":5759},"Footer",[110,10142,4803],{},[126,10144,10145,10158,10172],{},[107,10146,10147,10151,10153,10155],{},[131,10148,10149],{},[32,10150,5643],{},[131,10152,7210],{"align":5759},[131,10154,7210],{"align":5759},[131,10156,10157],{},"일반 페이지 (홈\u002F발송\u002F조회\u002F관리…)",[107,10159,10160,10165,10167,10169],{},[131,10161,10162],{},[32,10163,10164],{},"auth",[131,10166,322],{"align":5759},[131,10168,322],{"align":5759},[131,10170,10171],{},"로그인\u002F회원가입\u002F비밀번호 재설정",[107,10173,10174,10178,10180,10182],{},[131,10175,10176],{},[32,10177,5713],{},[131,10179,322],{"align":5759},[131,10181,322],{"align":5759},[131,10183,10184],{},"404\u002F시스템 에러\u002F점검\u002F이메일 템플릿",[73,10186],{},[76,10188,10190],{"id":10189},"_10-빌드-배포","10. 빌드 \u002F 배포",[237,10192,10194],{"id":10193},"_101-로컬-명령어","10.1 로컬 명령어",[5251,10196,10200],{"className":10197,"code":10198,"language":10199,"meta":4208,"style":4208},"language-bash shiki shiki-themes github-light github-dark","pnpm install\npnpm dev              # http:\u002F\u002Flocalhost:3000\npnpm typecheck        # nuxt typecheck\npnpm lint\npnpm build            # nitro cloudflare-pages preset → dist\u002F\npnpm preview          # 로컬 빌드 미리보기\n","bash",[32,10201,10202,10210,10220,10230,10237,10247],{"__ignoreMap":4208},[7968,10203,10204,10207],{"class":5110,"line":7970},[7968,10205,10206],{"class":7980},"pnpm",[7968,10208,10209],{"class":7993}," install\n",[7968,10211,10212,10214,10217],{"class":5110,"line":4212},[7968,10213,10206],{"class":7980},[7968,10215,10216],{"class":7993}," dev",[7968,10218,10219],{"class":8398},"              # http:\u002F\u002Flocalhost:3000\n",[7968,10221,10222,10224,10227],{"class":5110,"line":4209},[7968,10223,10206],{"class":7980},[7968,10225,10226],{"class":7993}," typecheck",[7968,10228,10229],{"class":8398},"        # nuxt typecheck\n",[7968,10231,10232,10234],{"class":5110,"line":4219},[7968,10233,10206],{"class":7980},[7968,10235,10236],{"class":7993}," lint\n",[7968,10238,10239,10241,10244],{"class":5110,"line":8291},[7968,10240,10206],{"class":7980},[7968,10242,10243],{"class":7993}," build",[7968,10245,10246],{"class":8398},"            # nitro cloudflare-pages preset → dist\u002F\n",[7968,10248,10249,10251,10254],{"class":5110,"line":8301},[7968,10250,10206],{"class":7980},[7968,10252,10253],{"class":7993}," preview",[7968,10255,10256],{"class":8398},"          # 로컬 빌드 미리보기\n",[237,10258,10260],{"id":10259},"_102-cloudflare-pages-배포","10.2 Cloudflare Pages 배포",[5251,10262,10264],{"className":10197,"code":10263,"language":10199,"meta":4208,"style":4208},"pnpm build\nnpx wrangler pages deploy dist \\\n  --project-name=malgn-noti \\\n  --branch=main \\\n  --commit-dirty=true\n",[32,10265,10266,10273,10293,10300,10307],{"__ignoreMap":4208},[7968,10267,10268,10270],{"class":5110,"line":7970},[7968,10269,10206],{"class":7980},[7968,10271,10272],{"class":7993}," build\n",[7968,10274,10275,10278,10281,10284,10287,10290],{"class":5110,"line":4212},[7968,10276,10277],{"class":7980},"npx",[7968,10279,10280],{"class":7993}," wrangler",[7968,10282,10283],{"class":7993}," pages",[7968,10285,10286],{"class":7993}," deploy",[7968,10288,10289],{"class":7993}," dist",[7968,10291,10292],{"class":8892}," \\\n",[7968,10294,10295,10298],{"class":5110,"line":4209},[7968,10296,10297],{"class":8892},"  --project-name=malgn-noti",[7968,10299,10292],{"class":8892},[7968,10301,10302,10305],{"class":5110,"line":4219},[7968,10303,10304],{"class":8892},"  --branch=main",[7968,10306,10292],{"class":8892},[7968,10308,10309],{"class":5110,"line":8291},[7968,10310,10311],{"class":8892},"  --commit-dirty=true\n",[18,10313,10314,10315],{},"production: ",[26,10316,10317],{"href":10317,"rel":10318},"https:\u002F\u002Fmalgn-noti.pages.dev",[30],[237,10320,10322,10323,4343],{"id":10321},"_103-빌드-산출물-구조-dist","10.3 빌드 산출물 구조 (",[32,10324,10325],{},"dist\u002F",[5251,10327,10330],{"className":10328,"code":10329,"language":5256},[5254],"dist\u002F\n├── _fonts\u002F                  # 폰트 자산\n├── _nuxt\u002F                   # 클라이언트 JS\u002FCSS 청크\n├── _worker.js\u002F              # SSR worker (Nitro)\n├── _routes.json             # Pages Functions 라우팅\n├── _headers\n├── _redirects\n└── nitro.json\n",[32,10331,10329],{"__ignoreMap":4208},[18,10333,10334,7505,10337,10340],{},[32,10335,10336],{},"_routes.json",[32,10338,10339],{},"_headers"," 등은 Nitro가 자동 생성. 수정 금지.",[73,10342],{},[76,10344,10346],{"id":10345},"_11-코딩-체크리스트-pr-전","11. 코딩 체크리스트 (PR 전)",[81,10348,10351,10362,10371,10383,10389,10398,10406,10412,10418,10427],{"className":10349},[10350],"contains-task-list",[84,10352,10355,51,10359,10361],{"className":10353},[10354],"task-list-item",[10356,10357],"input",{"disabled":4259,"type":10358},"checkbox",[32,10360,8200],{}," 사용",[84,10363,10365,51,10367,10370],{"className":10364},[10354],[10356,10366],{"disabled":4259,"type":10358},[32,10368,10369],{},"useHead({ title })"," 페이지 제목 설정",[84,10372,10374,51,10376,10378,10379,10382],{"className":10373},[10354],[10356,10375],{"disabled":4259,"type":10358},[32,10377,7751],{}," 없음 (",[32,10380,10381],{},"tsc --noEmit"," 통과)",[84,10384,10386,10388],{"className":10385},[10354],[10356,10387],{"disabled":4259,"type":10358}," Zod로 외부 입력 파싱",[84,10390,10392,10394,10395,10397],{"className":10391},[10354],[10356,10393],{"disabled":4259,"type":10358}," API 호출은 ",[32,10396,9248],{}," 경유",[84,10399,10401,10403,10404],{"className":10400},[10354],[10356,10402],{"disabled":4259,"type":10358}," 위험 액션은 ",[32,10405,6117],{},[84,10407,10409,10411],{"className":10408},[10354],[10356,10410],{"disabled":4259,"type":10358}," 새 아이콘은 기존 3개 컬렉션 중 선택",[84,10413,10415,10417],{"className":10414},[10354],[10356,10416],{"disabled":4259,"type":10358}," 색상은 CSS 변수 또는 Nuxt UI 토큰 사용 (하드코딩 X)",[84,10419,10421,51,10423,10426],{"className":10420},[10354],[10356,10422],{"disabled":4259,"type":10358},[32,10424,10425],{},"pnpm typecheck"," 통과",[84,10428,10430,51,10432,10426],{"className":10429},[10354],[10356,10431],{"disabled":4259,"type":10358},[32,10433,10434],{},"pnpm lint",[73,10436],{},[76,10438,10440],{"id":10439},"_12-참고-자료","12. 참고 자료",[237,10442,10444],{"id":10443},"_121-디자인-디자인-가이드","12.1 디자인 \u002F 디자인 가이드",[81,10446,10447,10455,10466],{},[84,10448,10449,47,10452,10454],{},[21,10450,10451],{},"디자인 정본",[26,10453,7689],{"href":7688}," (Relay-inspired v1.0)",[84,10456,10457,47,10460,10462,10463,10465],{},[21,10458,10459],{},"라이브 카탈로그",[32,10461,4457],{}," 페이지 — ",[26,10464,7929],{"href":4453}," (17섹션 sticky nav)",[84,10467,10468,47,10471],{},[21,10469,10470],{},"토큰·클래스 정본",[26,10472,4352],{"href":4351},[237,10474,10476],{"id":10475},"_122-시안-ia-참조-전용","12.2 시안 (IA 참조 전용)",[15,10478,10479],{},[18,10480,10481,10482,10485],{},"2026-05-18 디자인 피벗 이후 시안의 ",[21,10483,10484],{},"디자인(CSS\u002Fbase.css)은 더 이상 참조하지 않는다",". IA(페이지 목록·라우트 구조)만 정본으로 유지.",[81,10487,10488,10495],{},[84,10489,10490,10491],{},"페이지 목록(IA 정본): ",[26,10492,10493],{"href":10493,"rel":10494},"https:\u002F\u002Fmalgn-notifications.pages.dev\u002F#\u002Fpagelists",[30],[84,10496,10497,10498],{},"사이트맵 + 기능명세: ",[26,10499,10500],{"href":10500,"rel":10501},"https:\u002F\u002Fmalgn-notifications.pages.dev\u002F#\u002Fsitemap",[30],[237,10503,10505],{"id":10504},"_123-외부-문서","12.3 외부 문서",[81,10507,10508,10515,10522,10529,10536,10543,10550],{},[84,10509,10510],{},[26,10511,10514],{"href":10512,"rel":10513},"https:\u002F\u002Fnuxt.com\u002Fdocs",[30],"Nuxt 3 docs",[84,10516,10517],{},[26,10518,10521],{"href":10519,"rel":10520},"https:\u002F\u002Fui.nuxt.com\u002F",[30],"Nuxt UI v3",[84,10523,10524],{},[26,10525,10528],{"href":10526,"rel":10527},"https:\u002F\u002Ftailwindcss.com\u002F",[30],"Tailwind CSS v4",[84,10530,10531],{},[26,10532,10535],{"href":10533,"rel":10534},"https:\u002F\u002Freka-ui.com\u002F",[30],"Reka UI",[84,10537,10538],{},[26,10539,10542],{"href":10540,"rel":10541},"https:\u002F\u002Ficonify.design\u002F",[30],"Iconify",[84,10544,10545],{},[26,10546,10549],{"href":10547,"rel":10548},"https:\u002F\u002Fpinia.vuejs.org\u002F",[30],"Pinia",[84,10551,10552],{},[26,10553,7847],{"href":10554,"rel":10555},"https:\u002F\u002Fzod.dev\u002F",[30],[8560,10557,10558],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":4208,"searchDepth":4209,"depth":4209,"links":10560},[10561,10562,10563,10573,10581,10587,10595,10596,10600,10607,10613,10614],{"id":7702,"depth":4212,"text":7703},{"id":7868,"depth":4212,"text":7869},{"id":7912,"depth":4212,"text":7913,"children":10564},[10565,10567,10569,10570,10571],{"id":7933,"depth":4209,"text":10566},"3.1 색상 토큰 (CSS 변수 — main.css)",{"id":7955,"depth":4209,"text":10568},"3.2 Nuxt UI 테마 (app.config.ts)",{"id":8021,"depth":4209,"text":8022},{"id":8102,"depth":4209,"text":8103},{"id":8168,"depth":4209,"text":10572},"3.5 2단 콘텐츠 (.content-2col)",{"id":8185,"depth":4212,"text":8186,"children":10574},[10575,10576,10577,10579,10580],{"id":8189,"depth":4209,"text":8190},{"id":8225,"depth":4209,"text":8226},{"id":8583,"depth":4209,"text":10578},"4.3 페이지 메타 (definePageMeta)",{"id":8642,"depth":4209,"text":8643},{"id":8740,"depth":4209,"text":8741},{"id":8774,"depth":4212,"text":8775,"children":10582},[10583,10584,10585,10586],{"id":8778,"depth":4209,"text":8779},{"id":8822,"depth":4209,"text":8823},{"id":9051,"depth":4209,"text":9052},{"id":9081,"depth":4209,"text":9082},{"id":9094,"depth":4212,"text":9095,"children":10588},[10589,10590,10591,10592,10593,10594],{"id":9098,"depth":4209,"text":9099},{"id":9235,"depth":4209,"text":9236},{"id":9342,"depth":4209,"text":9343},{"id":9372,"depth":4209,"text":9373},{"id":9470,"depth":4209,"text":9471},{"id":9596,"depth":4209,"text":9597},{"id":9609,"depth":4212,"text":9610},{"id":9919,"depth":4212,"text":9920,"children":10597},[10598,10599],{"id":9923,"depth":4209,"text":9924},{"id":9986,"depth":4209,"text":9987},{"id":10061,"depth":4212,"text":10062,"children":10601},[10602,10604,10606],{"id":10065,"depth":4209,"text":10603},"9.1 AppGnb (AppGnb.vue)",{"id":10105,"depth":4209,"text":10605},"9.2 AppFooter (AppFooter.vue)",{"id":10124,"depth":4209,"text":10125},{"id":10189,"depth":4212,"text":10190,"children":10608},[10609,10610,10611],{"id":10193,"depth":4209,"text":10194},{"id":10259,"depth":4209,"text":10260},{"id":10321,"depth":4209,"text":10612},"10.3 빌드 산출물 구조 (dist\u002F)",{"id":10345,"depth":4212,"text":10346},{"id":10439,"depth":4212,"text":10440,"children":10615},[10616,10617,10618],{"id":10443,"depth":4209,"text":10444},{"id":10475,"depth":4209,"text":10476},{"id":10504,"depth":4209,"text":10505},"이 문서는 malgn-noti 사용자단(고객 콘솔)의 디자인 시스템 + HTML\u002FCSS\u002FJS 코딩 컨벤션을 정리합니다. 운영자 콘솔(malgn-noti-admin)도 동일 스택이므로 같은 규칙을 따릅니다.",{},"\u002Ffrontend",{"title":7650,"description":10619},"FRONTEND","xc2HGolPf7vl6HTYJny10J6JQ6ig-vkX-CuPH8xF4qg",{"id":10626,"title":10627,"body":10628,"description":4208,"extension":4257,"meta":11482,"navigation":4259,"path":11483,"seo":11484,"stem":11485,"__hash__":11486},"docs\u002Fhistory\u002Fhistory.20260511.md","2026-05-11 — 프로젝트 착수",{"type":8,"value":10629,"toc":11463},[10630,10633,10637,10647,10649,10653,10796,10800,10803,10823,10826,10843,10847,10861,10865,10895,10899,10906,11056,11085,11089,11099,11155,11158,11162,11177,11181,11214,11218,11232,11236,11269,11272,11299,11303,11318,11360,11364,11390,11394,11400,11407,11409,11413,11445,11449],[11,10631,10627],{"id":10632},"_2026-05-11-프로젝트-착수",[76,10634,10636],{"id":10635},"한-줄-요약","한 줄 요약",[18,10638,10639,10640,7505,10642,7505,10644,10646],{},"맑은 메시징 SaaS의 세 레포(",[32,10641,7664],{},[32,10643,7672],{},[32,10645,50],{},")를 부트스트랩하고 첫 배포까지 마쳤다.",[73,10648],{},[76,10650,10652],{"id":10651},"_1-큰-결정","1. 큰 결정",[101,10654,10655,10664],{},[104,10656,10657],{},[107,10658,10659,10661],{},[110,10660,7712],{},[110,10662,10663],{},"결정",[126,10665,10666,10677,10695,10708,10715,10729,10741,10752,10766,10774,10788],{},[107,10667,10668,10671],{},[131,10669,10670],{},"프로젝트 구조",[131,10672,10673,10674],{},"3개 레포(사용자단·운영자단·API) 형제 디렉토리 + GitHub ",[32,10675,10676],{},"malgnsoft\u002F*",[107,10678,10679,10682],{},[131,10680,10681],{},"프론트 스택",[131,10683,10684,10694],{},[21,10685,10686,10687,10690,10691,10693],{},"Nuxt 3.21 + ",[32,10688,10689],{},"compatibilityVersion: 4"," (Nuxt 4 호환 모드, ",[32,10692,7737],{}," 디렉토리)"," + Vue 3.5 + TS 5.9",[107,10696,10697,10700],{},[131,10698,10699],{},"UI 라이브러리",[131,10701,10702,10704,10705,10707],{},[21,10703,7762],{}," 단일 — Reka UI + Tailwind v4 통합. ",[32,10706,7782],{}," 미설치",[107,10709,10710,10712],{},[131,10711,7814],{},[131,10713,10714],{},"Noto Sans KR (Google Fonts)",[107,10716,10717,10719],{},[131,10718,7790],{},[131,10720,10721,10722,7505,10724,7505,10726,10728],{},"Iconify — ",[32,10723,7796],{},[32,10725,7800],{},[32,10727,7803],{},"(시안 매칭)",[107,10730,10731,10734],{},[131,10732,10733],{},"API 스택",[131,10735,10736,10737,10740],{},"Cloudflare Workers + ",[21,10738,10739],{},"Hono 4"," + TypeScript",[107,10742,10743,10746],{},[131,10744,10745],{},"DB",[131,10747,10748,10751],{},[21,10749,10750],{},"AWS Aurora MySQL 8.0"," (D1 아님)",[107,10753,10754,10757],{},[131,10755,10756],{},"DB 연결",[131,10758,10759,4339,10762,10765],{},[21,10760,10761],{},"Cloudflare Hyperdrive(MySQL)",[32,10763,10764],{},"mysql2"," 확정 (RDS Data API 미채택)",[107,10767,10768,10771],{},[131,10769,10770],{},"Aurora 노출",[131,10772,10773],{},"퍼블릭 엔드포인트 + SG에 Hyperdrive egress IP 화이트리스트 (개발 단계)",[107,10775,10776,10779],{},[131,10777,10778],{},"지원 채널",[131,10780,10781,10782,10784,10785],{},"SMS\u002FLMS\u002FMMS · ",[21,10783,7125],{}," · 알림톡\u002F친구톡 · Email · Push (5) + ",[21,10786,10787],{},"Flow(복합)",[107,10789,10790,10793],{},[131,10791,10792],{},"배포 트리거",[131,10794,10795],{},"wrangler CLI 직접 배포 (GitHub Actions 자동화는 추후)",[76,10797,10799],{"id":10798},"_2-claudemd-3개-작성","2. CLAUDE.md 3개 작성",[18,10801,10802],{},"세 디렉토리에 프로젝트 컨텍스트 문서를 작성.",[81,10804,10805,10811,10817],{},[84,10806,10807,10810],{},[32,10808,10809],{},"malgn-noti\u002FCLAUDE.md"," — 사용자단 (Nuxt 3 + Nuxt UI)",[84,10812,10813,10816],{},[32,10814,10815],{},"malgn-noti-admin\u002FCLAUDE.md"," — 운영자단(BackOffice)",[84,10818,10819,10822],{},[32,10820,10821],{},"malgn-noti-api\u002FCLAUDE.md"," — API (Workers + Hono + Aurora via Hyperdrive)",[18,10824,10825],{},"각 문서에 공통 원칙 명시:",[81,10827,10828,10831,10834,10837,10840],{},[84,10829,10830],{},"NHN\u002FPG\u002FAI 직접 호출은 api 서버만",[84,10832,10833],{},"시크릿\u002FAppKey는 envelope 암호화 후 DB 저장, 프론트 노출 금지",[84,10835,10836],{},"Flow(채널 폴백)는 NHN 콘솔이 아니라 우리 백엔드에서 정의·실행",[84,10838,10839],{},"크레딧 차감은 hold → 확정\u002F환불, append-only 원장 + 멱등키",[84,10841,10842],{},"admin은 사내망\u002FAccess로 보호",[76,10844,10846],{"id":10845},"_3-nhn-notification-hub-정찰","3. NHN Notification Hub 정찰",[81,10848,10849,10855,10858],{},[84,10850,10851,10852],{},"공식 문서 ",[32,10853,10854],{},"docs.nhncloud.com\u002F...\u002FNotification%20Hub\u002Fko\u002Foverview\u002F",[84,10856,10857],{},"통합 6채널 (SMS · 알림톡 · RCS · Email · Push · 친구톡), 발송량 제한(SMS 월 5,000 \u002F 알림톡 일 1,000), 플로우(폴백) 기능 확인",[84,10859,10860],{},"우리 백엔드에서 자체 Flow 엔진 구현하기로 결정 — 멀티 테넌트 한도\u002F감사 통합 위함",[76,10862,10864],{"id":10863},"_4-사용자단-시안-분석-정본화","4. 사용자단 시안 분석 (정본화)",[81,10866,10867,10873,10879,10885,10888],{},[84,10868,10869,10870],{},"외부 시안: ",[26,10871,10493],{"href":10493,"rel":10872},[30],[84,10874,10875,10876],{},"ViewLogic Router 기반 SPA, 페이지 데이터는 ",[32,10877,10878],{},"\u002Fsrc\u002Flogic\u002Fpagelists.js",[84,10880,10881,10884],{},[21,10882,10883],{},"263개 페이지\u002F팝업 · 16 카테고리"," 추출 → 단일 정본(SoT)으로 채택",[84,10886,10887],{},"1급 도메인: 단발 발송 \u002F 캠페인 \u002F 채널별 이력+통계 \u002F 주소록 \u002F 발신 인프라 6종 \u002F 템플릿+카테고리+샘플+AI \u002F 크레딧 \u002F 결제 \u002F 1:1 문의 \u002F 회원가입+OTP+휴대폰 인증 \u002F ExportJob",[84,10889,10890,10891,10894],{},"메모리에 ",[32,10892,10893],{},"reference_user_console_sitemap.md","로 정본 위치 기록",[76,10896,10898],{"id":10897},"_5-사용자단-nuxt-부트스트랩","5. 사용자단 Nuxt 부트스트랩",[18,10900,10901,10902,10905],{},"빈 디렉토리에 Nuxt 프로젝트 직접 작성 (",[32,10903,10904],{},"nuxi init"," 미사용).",[101,10907,10908,10917],{},[104,10909,10910],{},[107,10911,10912,10914],{},[110,10913,7712],{},[110,10915,10916],{},"산출물",[126,10918,10919,10946,10966,10982,10999,11007,11020,11031,11042],{},[107,10920,10921,10924],{},[131,10922,10923],{},"기본 설정",[131,10925,10926,4420,10929,4420,10931,4420,10933,4420,10936,4420,10939,4420,10941,4420,10944],{},[32,10927,10928],{},"package.json",[32,10930,4405],{},[32,10932,7890],{},[32,10934,10935],{},".gitignore",[32,10937,10938],{},".editorconfig",[32,10940,7906],{},[32,10942,10943],{},".npmrc",[32,10945,7898],{},[107,10947,10948,10951],{},[131,10949,10950],{},"진입",[131,10952,10953,4420,10956,6685,10958,4473,10961,7314,10964],{},[32,10954,10955],{},"app\u002Fapp.vue",[32,10957,4392],{},[32,10959,10960],{},"primary: indigo",[32,10962,10963],{},"neutral: slate",[32,10965,4352],{},[107,10967,10968,10971],{},[131,10969,10970],{},"레이아웃 3종",[131,10972,10973,10975,10976,10978,10979,10981],{},[32,10974,5643],{},"(AppShell) · ",[32,10977,10164],{},"(중앙 카드) · ",[32,10980,5713],{},"(시스템 페이지)",[107,10983,10984,10987],{},[131,10985,10986],{},"컴포넌트 3종",[131,10988,10989,10992,10993,4420,10996],{},[32,10990,10991],{},"AppShell.vue","(좌측 사이드바) · ",[32,10994,10995],{},"AppPagePlaceholder.vue",[32,10997,10998],{},"AppConfirmDialog.vue",[107,11000,11001,11004],{},[131,11002,11003],{},"페이지 골격",[131,11005,11006],{},"50개 페이지 (16카테고리 전체)",[107,11008,11009,11012],{},[131,11010,11011],{},"composables 3종",[131,11013,11014,4420,11016,4420,11018],{},[32,11015,8724],{},[32,11017,8727],{},[32,11019,9362],{},[107,11021,11022,11025],{},[131,11023,11024],{},"middleware",[131,11026,11027,11030],{},[32,11028,11029],{},"auth.global.ts"," (현재 통과 스텁)",[107,11032,11033,11036],{},[131,11034,11035],{},"store",[131,11037,11038,11041],{},[32,11039,11040],{},"stores\u002Fauth.ts"," (Pinia)",[107,11043,11044,11046],{},[131,11045,7413],{},[131,11047,11048,11051,11052,11055],{},[32,11049,11050],{},"domain.ts"," (Channel\u002FTenant\u002FUser) · ",[32,11053,11054],{},"api.ts"," (Paginated\u002FExportJob)",[81,11057,11058,11064,11079],{},[84,11059,11060,11063],{},[32,11061,11062],{},"pnpm install","로 의존성 863개 설치",[84,11065,11066,11068,11069,11072,11073,4473,11076,4343],{},[32,11067,10425],{}," 통과 (5개 초기 타입 에러 수정: ",[32,11070,11071],{},"$Fetch"," 타입, ",[32,11074,11075],{},"titleTemplate",[32,11077,11078],{},"process.env",[84,11080,11081,11084],{},[32,11082,11083],{},"pnpm dev","로 dev server 시동 성공 (port 3000)",[76,11086,11088],{"id":11087},"_6-git-연결-github-푸시","6. Git 연결 + GitHub 푸시",[18,11090,11091,11092,4339,11095,11098],{},"세 디렉토리 모두 ",[32,11093,11094],{},"git init -b main",[32,11096,11097],{},"origin"," 추가 + 첫 커밋 + 푸시.",[101,11100,11101,11114],{},[104,11102,11103],{},[107,11104,11105,11108,11111],{},[110,11106,11107],{},"레포",[110,11109,11110],{},"URL",[110,11112,11113],{},"첫 커밋",[126,11115,11116,11129,11142],{},[107,11117,11118,11120,11126],{},[131,11119,7664],{},[131,11121,11122],{},[26,11123,11124],{"href":11124,"rel":11125},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-noti",[30],[131,11127,11128],{},"Nuxt 3 + Nuxt UI 사용자단 부트스트랩",[107,11130,11131,11133,11139],{},[131,11132,50],{},[131,11134,11135],{},[26,11136,11137],{"href":11137,"rel":11138},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-noti-api",[30],[131,11140,11141],{},"CLAUDE.md (Cloudflare Workers + Hono + Aurora MySQL)",[107,11143,11144,11146,11152],{},[131,11145,7672],{},[131,11147,11148],{},[26,11149,11150],{"href":11150,"rel":11151},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-noti-admin",[30],[131,11153,11154],{},"CLAUDE.md (BackOffice 관리자단)",[18,11156,11157],{},"커밋 메시지에 Claude co-author trailer 포함.",[76,11159,11161],{"id":11160},"_7-cloudflare-첫-배포","7. Cloudflare 첫 배포",[18,11163,11164,11167,11168,11172,11173,11176],{},[32,11165,11166],{},"wrangler whoami"," 인증 확인 (",[26,11169,11171],{"href":11170},"mailto:info@malgnsoft.com","info@malgnsoft.com",", account ",[32,11174,11175],{},"d2b8c5524b...","). 필요 권한(workers\u002Fpages\u002Fd1\u002Fqueues\u002Fhyperdrive) 보유.",[237,11178,11180],{"id":11179},"_71-사용자단-malgn-noti","7.1 사용자단 (malgn-noti)",[81,11182,11183,11191,11199,11204,11209],{},[84,11184,11185,5069,11187,11190],{},[32,11186,4405],{},[32,11188,11189],{},"nitro.preset: 'cloudflare-pages'"," 추가",[84,11192,11193,6219,11196,11198],{},[32,11194,11195],{},"pnpm build",[32,11197,10325],{}," 산출 (1.37 MB \u002F 400 KB gzip)",[84,11200,11201],{},[32,11202,11203],{},"wrangler pages project create malgn-noti --production-branch=main",[84,11205,11206],{},[32,11207,11208],{},"wrangler pages deploy dist --project-name=malgn-noti --branch=main",[84,11210,10314,11211],{},[26,11212,10317],{"href":10317,"rel":11213},[30],[237,11215,11217],{"id":11216},"_72-운영자단-malgn-noti-admin","7.2 운영자단 (malgn-noti-admin)",[81,11219,11220,11226],{},[84,11221,11222,11225],{},[32,11223,11224],{},"public\u002Findex.html","로 placeholder 정적 페이지 1개",[84,11227,10314,11228],{},[26,11229,11230],{"href":11230,"rel":11231},"https:\u002F\u002Fmalgn-noti-admin.pages.dev",[30],[237,11233,11235],{"id":11234},"_73-api-malgn-noti-api","7.3 API (malgn-noti-api)",[81,11237,11238,11249,11257,11262],{},[84,11239,11240,11241,47,11244,4473,11246,4343],{},"최소 Hono Worker (",[32,11242,11243],{},"src\u002Findex.ts",[32,11245,4361],{},[32,11247,11248],{},"\u002Fhealth",[84,11250,11251,47,11254],{},[32,11252,11253],{},"wrangler.toml",[32,11255,11256],{},"nodejs_compat",[84,11258,11259],{},[32,11260,11261],{},"wrangler deploy",[84,11263,11264,11265],{},"endpoint: ",[26,11266,11267],{"href":11267,"rel":11268},"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev",[30],[18,11270,11271],{},"응답 검증:",[81,11273,11274,11280,11285,11291],{},[84,11275,11276,11279],{},[32,11277,11278],{},"\u002Fhome"," HTTP 200 (54 KB)",[84,11281,11282,11284],{},[32,11283,9335],{}," HTTP 200 (auth 레이아웃)",[84,11286,11287,11290],{},[32,11288,11289],{},"\u002F404"," HTTP 200 (blank 레이아웃)",[84,11292,11293,51,11296],{},[32,11294,11295],{},"\u002Fapi\u002Fhealth",[32,11297,11298],{},"{\"ok\":true,\"env\":\"production\",...}",[76,11300,11302],{"id":11301},"_8-시안-디자인-매칭-1차-gnb-도입","8. 시안 디자인 매칭 1차 — GNB 도입",[18,11304,11305,11306,11309,11310,11313,11314,11317],{},"시안 분석 결과 ",[21,11307,11308],{},"시안은 사이드바가 아니라 상단 GNB 방식","임을 확인. 우리 ",[32,11311,11312],{},"AppShell","(좌측 사이드바)을 폐기하고 ",[32,11315,11316],{},"AppGnb","(상단 가로 네비)로 교체.",[81,11319,11320,11342,11351,11357],{},[84,11321,11322,11324,11325],{},[32,11323,4352],{},"에 시안 정본의 CSS 변수 도입\n",[81,11326,11327],{},[84,11328,11329,11332,11333,4473,11336,4473,11339],{},[32,11330,11331],{},"--primary-color: #6366f1",", gray 11단, brand ",[32,11334,11335],{},"sky-soft\u002Fsky\u002Fsky-vivid\u002Findigo",[32,11337,11338],{},"--gnb-height: 64px",[32,11340,11341],{},"--content-max: 1320px",[84,11343,11344,11346,11347,11350],{},[32,11345,8210],{}," 작성: 시안 ",[32,11348,11349],{},".gnb-*"," 클래스 그대로 차용 (sticky, hover dropdown, 사용자 아바타 드롭다운 + 크레딧 표시)",[84,11352,11353,11356],{},[32,11354,11355],{},"layouts\u002Fdefault.vue","를 GNB 기반으로 갈아끼움",[84,11358,11359],{},"home 페이지 시안 톤으로 리디자인 (그라데이션 크레딧 카드 + 6채널 빠른 발송 + 2단 콘텐츠)",[76,11361,11363],{"id":11362},"_9-푸터-추가","9. 푸터 추가",[81,11365,11366,11373,11381,11384,11387],{},[84,11367,11368,11369,11372],{},"시안 base.css의 ",[32,11370,11371],{},".footer-upper\u002F.footer-lower"," 클래스 그대로 차용",[84,11374,11375,11377,11378,11380],{},[32,11376,4424],{}," 신규 컴포넌트, ",[32,11379,11355],{},"에 연결",[84,11382,11383],{},"상단: 로고 + 슬로건 + 정책 링크 6개",[84,11385,11386],{},"하단: (주)맑은소프트 로고 + 사업자 정보 7개 + 카피라이트",[84,11388,11389],{},"검정 배경, 시안과 동일한 톤",[76,11391,11393],{"id":11392},"_10-gnb-메뉴-매핑","10. GNB 메뉴 매핑",[18,11395,11396,11397],{},"스크린샷 기준 시안의 실제 메뉴 9개 구성으로 갱신:\n",[21,11398,11399],{},"서비스 \u002F 메시지 발송 \u002F 발송 조회·통계 \u002F 주소록 \u002F 발신 정보 \u002F 메시지관리 \u002F 캠페인 관리1 \u002F 캠페인 관리2 \u002F 운영가이드",[18,11401,11402,11403,11406],{},"자식 메뉴는 ",[32,11404,11405],{},"pagelists.js"," 카테고리 기반으로 구성.",[73,11408],{},[76,11410,11412],{"id":11411},"산출물-당일-추가","산출물 (당일 추가)",[81,11414,11415,11420,11425,11439],{},[84,11416,11417,11419],{},[21,11418,11107],{}," 3개 + 첫 커밋 + 푸시",[84,11421,11422,11424],{},[21,11423,7858],{}," 프로젝트 2개 + Worker 1개 (모두 운영 URL 활성)",[84,11426,11427,11430,11431,4420,11434,4420,11437],{},[21,11428,11429],{},"메모리"," 3종: ",[32,11432,11433],{},"project_malgn_noti_overview.md",[32,11435,11436],{},"reference_nhn_notification_hub.md",[32,11438,10893],{},[84,11440,11441,11444],{},[21,11442,11443],{},"사용자단 코드",": 약 74개 파일 (페이지 50 + 컴포넌트 3 + composables 3 + 레이아웃 3 + 기본 설정)",[76,11446,11448],{"id":11447},"다음-단계로-남긴-것","다음 단계로 남긴 것",[81,11450,11451,11454,11457,11460],{},[84,11452,11453],{},"시안과 정확한 GNB 메뉴 정합성 (캠페인 관리 1\u002F2 등 임시 명칭)",[84,11455,11456],{},"콘텐츠 폭 미세 조정",[84,11458,11459],{},"인증 페이지 \u002F 발송 페이지 실제 구현",[84,11461,11462],{},"Aurora 토폴로지(Provisioned vs Serverless v2), 결제 PG\u002FAI 제공자 선정 등 미정 항목",{"title":4208,"searchDepth":4209,"depth":4209,"links":11464},[11465,11466,11467,11468,11469,11470,11471,11472,11477,11478,11479,11480,11481],{"id":10635,"depth":4212,"text":10636},{"id":10651,"depth":4212,"text":10652},{"id":10798,"depth":4212,"text":10799},{"id":10845,"depth":4212,"text":10846},{"id":10863,"depth":4212,"text":10864},{"id":10897,"depth":4212,"text":10898},{"id":11087,"depth":4212,"text":11088},{"id":11160,"depth":4212,"text":11161,"children":11473},[11474,11475,11476],{"id":11179,"depth":4209,"text":11180},{"id":11216,"depth":4209,"text":11217},{"id":11234,"depth":4209,"text":11235},{"id":11301,"depth":4212,"text":11302},{"id":11362,"depth":4212,"text":11363},{"id":11392,"depth":4212,"text":11393},{"id":11411,"depth":4212,"text":11412},{"id":11447,"depth":4212,"text":11448},{},"\u002Fhistory\u002Fhistory.20260511",{"title":10627,"description":4208},"history\u002Fhistory.20260511","D0zBFV6zZ7NfmQVdxENPOr42QgMI7FX3YDFQ-3SYSuw",{"id":11488,"title":11489,"body":11490,"description":4208,"extension":4257,"meta":12007,"navigation":4259,"path":12008,"seo":12009,"stem":12010,"__hash__":12011},"docs\u002Fhistory\u002Fhistory.20260512.md","2026-05-12 — 시안 정밀 매칭 + 문서화",{"type":8,"value":11491,"toc":11982},[11492,11495,11497,11504,11506,11510,11513,11575,11586,11590,11593,11598,11609,11613,11620,11624,11627,11633,11655,11659,11677,11681,11703,11707,11710,11717,11720,11753,11757,11770,11774,11783,11789,11796,11804,11811,11818,11825,11833,11837,11917,11923,11927,11942,11944,11946,11967,11971],[11,11493,11489],{"id":11494},"_2026-05-12-시안-정밀-매칭-문서화",[76,11496,10636],{"id":10635},[18,11498,11499,11500,11503],{},"콘텐츠 폭 1200px 통일, GNB를 utility bar + main 2단으로 분리, 메뉴 8개로 정돈했고 디자인·기술 문서 3종을 ",[32,11501,11502],{},"doc\u002F","에 추가했다.",[73,11505],{},[76,11507,11509],{"id":11508},"_1-콘텐츠-폭-1200px-통일","1. 콘텐츠 폭 1200px 통일",[18,11511,11512],{},"스크린샷 검수에서 시안 톤보다 본문이 약간 넓다는 결정.",[101,11514,11515,11528],{},[104,11516,11517],{},[107,11518,11519,11521,11525],{},[110,11520,7712],{},[110,11522,11524],{"align":11523},"right","이전",[110,11526,11527],{"align":11523},"이후",[126,11529,11530,11544,11560],{},[107,11531,11532,11536,11539],{},[131,11533,8134,11534,4343],{},[32,11535,8137],{},[131,11537,11538],{"align":11523},"1320px",[131,11540,11541],{"align":11523},[21,11542,11543],{},"1200px",[107,11545,11546,11552,11555],{},[131,11547,11548,11549,4343],{},"푸터 (",[32,11550,11551],{},".footer-container",[131,11553,11554],{"align":11523},"1600px \u002F pad 60",[131,11556,11557],{"align":11523},[21,11558,11559],{},"1200px \u002F pad 24",[107,11561,11562,11568,11570],{},[131,11563,11564,11565,4343],{},"GNB (",[32,11566,11567],{},".gnb-inner",[131,11569,8126],{"align":11523},[131,11571,11572,11574],{"align":11523},[21,11573,11543],{}," (max-width 추가)",[18,11576,11577,11578,11581,11582,11585],{},"CSS 변수 한 줄(",[32,11579,11580],{},"--content-max: 1200px",")로 일괄 적용. ",[32,11583,11584],{},"doc\u002FFRONTEND.md §3.4","도 갱신.",[76,11587,11589],{"id":11588},"_2-gnb-메뉴-시안-정합","2. GNB 메뉴 시안 정합",[18,11591,11592],{},"이전 추정 메뉴를 시안 스크린샷의 실제 9개로 교체:",[18,11594,11595],{},[21,11596,11597],{},"서비스 ▾ · 메시지 발송 ▾ · 발송 조회\u002F통계 ▾ · 주소록 ▾ · 발신 정보 ▾ · 메시지관리 ▾ · 캠페인 관리1 · 캠페인 관리2 · 운영가이드",[81,11599,11600,11603,11606],{},[84,11601,11602],{},"발송과 발송 조회\u002F통계를 별도 메뉴로 분리",[84,11604,11605],{},"캠페인 관리 변형 2개를 별도 메뉴로 노출",[84,11607,11608],{},"메뉴 자식 라벨도 시안 정본의 카테고리 표현으로 정돈",[76,11610,11612],{"id":11611},"_3-gnb-메뉴-8개로-통합","3. GNB 메뉴 8개로 통합",[18,11614,11615,11616,11619],{},"스크린샷에서 캠페인 관리1\u002F2를 따로 두기보단 단일 \"캠페인 관리\"로 통합 결정. ",[32,11617,11618],{},"\u002Fcampaign3","은 변형 디자인 검토용으로 라우트만 유지하고 메뉴에서 제외.",[76,11621,11623],{"id":11622},"_4-utility-bar-분리-헤더-2단-구조","4. Utility Bar 분리 (헤더 2단 구조)",[18,11625,11626],{},"GNB가 메뉴 9개 + 우측 액션으로 한 줄에 답답한 문제를 분리로 해결.",[5251,11628,11631],{"className":11629,"code":11630,"language":5256},[5254],"┌───────────────────────────────────────────────────────────┐\n│  Utility Bar (h 32, bg gray-50)                           │\n│                          문의  충전  관 관리자 ▾           │\n├───────────────────────────────────────────────────────────┤\n│  Main GNB (h 64, bg white)                                │\n│   로고     서비스 ▾  메시지 발송 ▾  ...   운영가이드        │\n└───────────────────────────────────────────────────────────┘\n",[32,11632,11630],{"__ignoreMap":4208},[81,11634,11635,11647,11650],{},[84,11636,11637,11640,11641,4339,11644,11646],{},[32,11638,11639],{},".gnb-wrapper","(sticky) 안에 ",[32,11642,11643],{},".gnb-utility",[32,11645,8884],{},"(main) 2단",[84,11648,11649],{},"두 줄 모두 sticky → 사용자 메뉴 상시 접근",[84,11651,11652,11654],{},[32,11653,10101],{},"에서 utility bar 숨김, 햄버거 슬라이드 메뉴에서 액션 접근",[76,11656,11658],{"id":11657},"_5-헤더-사이즈-미세-조정","5. 헤더 사이즈 미세 조정",[81,11660,11661,11667,11674],{},[84,11662,11663,11664],{},"Main GNB 높이 64 → 52로 일시 줄였다가, 사용자 피드백으로 ",[21,11665,11666],{},"64로 복원",[84,11668,11669,11670,11673],{},"Utility Bar 높이는 44 → ",[21,11671,11672],{},"32"," 유지",[84,11675,11676],{},"Utility Bar 내부 pill 버튼·아바타·텍스트는 12px \u002F 22×22로 컴팩트 유지",[76,11678,11680],{"id":11679},"_6-gnb-메뉴-오른쪽-정렬","6. GNB 메뉴 오른쪽 정렬",[18,11682,11683,11686,11687,6219,11690,4473,11693,11686,11696,6219,11699,11702],{},[32,11684,11685],{},".gnb-nav-wrap","의 ",[32,11688,11689],{},"justify-content: center",[32,11691,11692],{},"flex-end",[32,11694,11695],{},".gnb-nav",[32,11697,11698],{},"margin: 0 auto",[32,11700,11701],{},"margin: 0",". 로고와 메뉴 사이 시각적 여백 확보.",[76,11704,11706],{"id":11705},"_7-로고-정렬-수정","7. 로고 정렬 수정",[18,11708,11709],{},"스크린샷 검수에서 \"맑은\"(22px\u002F900)이 \"메시징\"(20px\u002F300)보다 약간 아래로 처져 보이는 이슈.",[18,11711,11712,11713,11716],{},"원인: 한국어 폰트(Noto Sans KR)의 ascender가 SVG 아이콘 박스보다 비대칭으로 커서 ",[32,11714,11715],{},"align-items: center","만으로는 시각 중앙이 안 맞음.",[18,11718,11719],{},"해결:",[81,11721,11722,11728,11739,11750],{},[84,11723,11724,11725,11727],{},"로고 전체는 ",[32,11726,11715],{}," (아이콘과 텍스트 그룹)",[84,11729,11730,11731,11734,11735,11738],{},"텍스트 그룹은 별도 wrapper(",[32,11732,11733],{},".gnb-logo-text-group",")에 ",[32,11736,11737],{},"align-items: baseline","으로 묶음",[84,11740,11741,11742,11745,11746,11749],{},"자식 모두 ",[32,11743,11744],{},"line-height: 1"," 통일, ",[32,11747,11748],{},"display: inline-flex"," 적용",[84,11751,11752],{},"아이콘 28 → 24로 축소(텍스트 22와 시각 비례)",[76,11754,11756],{"id":11755},"_8-페이지-폭-1200px-적용","8. 페이지 폭 1200px 적용",[18,11758,11759,11762,11763,6219,11766,11769],{},[32,11760,11761],{},"AppPagePlaceholder","가 화면 전체 폭을 쓰고 있어 GNB·푸터와 폭 정렬 불일치. ",[32,11764,11765],{},"p-6 lg:p-8",[32,11767,11768],{},"app-container py-8 lg:py-10","로 갱신해 47개 placeholder 페이지가 모두 1200px 안에 정렬.",[76,11771,11773],{"id":11772},"_9-푸터-이메일-변경","9. 푸터 이메일 변경",[18,11775,11776,6219,11779,11782],{},[32,11777,11778],{},"MALGN@MALGNSOFT.COM",[32,11780,11781],{},"massage@malgnsoft.com"," (실제 메시징 서비스 메일 주소). 단순 텍스트 교체.",[76,11784,11786,11787,4343],{"id":11785},"_10-문서-3종-추가-doc","10. 문서 3종 추가 (",[32,11788,11502],{},[237,11790,11792,11793],{"id":11791},"_101-frontendmd","10.1 ",[26,11794,4296],{"href":11795},"..\u002FFRONTEND",[81,11797,11798,11801],{},[84,11799,11800],{},"프론트엔드 코딩 컨벤션 (Vue\u002FCSS\u002FTS), Nuxt UI 매핑, 아이콘 사용, 레이아웃 컴포넌트, 빌드\u002F배포, PR 체크리스트, base.css 섹션 카탈로그",[84,11802,11803],{},"12개 섹션",[237,11805,11807,11808],{"id":11806},"_102-stackmd","10.2 ",[26,11809,4301],{"href":11810},"..\u002FSTACK",[81,11812,11813,11816],{},[84,11814,11815],{},"전체 토폴로지 ASCII 다이어그램, 사용자\u002F운영자\u002FAPI 스택, 데이터\u002F스토리지\u002F큐, 외부 서비스, 개발·운영 도구, 배포, 선택 이유\u002F대안 비교, 버전 호환 표, 미정 항목",[84,11817,11803],{},[237,11819,11821,11822],{"id":11820},"_103-designmd","10.3 ",[26,11823,7689],{"href":11824},"..\u002FDESIGN",[81,11826,11827,11830],{},[84,11828,11829],{},"디자인 원칙, 컬러 시스템, 타이포그래피, 간격·그리드·레이아웃, 라운드·그림자·보더, 컴포넌트 패턴, 아이콘, 레이아웃 패턴, 반응형, 접근성, 톤·마이크로카피, 시안 매핑(base.css ↔ 우리 구현 매핑 + 페이지별 라인 번호 33개)",[84,11831,11832],{},"14개 섹션",[237,11834,11836],{"id":11835},"_104-문서-분담","10.4 문서 분담",[101,11838,11839,11852],{},[104,11840,11841],{},[107,11842,11843,11846,11849],{},[110,11844,11845],{},"문서",[110,11847,11848],{},"관점",[110,11850,11851],{},"시기",[126,11853,11854,11869,11885,11901],{},[107,11855,11856,11860,11866],{},[131,11857,11858],{},[32,11859,7682],{},[131,11861,11862,11865],{},[21,11863,11864],{},"Why"," — 결정 사항, 큰 그림",[131,11867,11868],{},"AI 에이전트 + 신규 합류자 첫 읽기",[107,11870,11871,11876,11882],{},[131,11872,11873],{},[32,11874,11875],{},"doc\u002FSTACK.md",[131,11877,11878,11881],{},[21,11879,11880],{},"What"," — 무엇을 쓰는가",[131,11883,11884],{},"외부 검토, 의존성 업데이트",[107,11886,11887,11892,11898],{},[131,11888,11889],{},[32,11890,11891],{},"doc\u002FDESIGN.md",[131,11893,11894,11897],{},[21,11895,11896],{},"Visual How"," — 토큰·컴포넌트·톤",[131,11899,11900],{},"새 화면 그리기",[107,11902,11903,11908,11914],{},[131,11904,11905],{},[32,11906,11907],{},"doc\u002FFRONTEND.md",[131,11909,11910,11913],{},[21,11911,11912],{},"Code How"," — Vue\u002FCSS\u002FTS 컨벤션",[131,11915,11916],{},"코딩 중 매번",[18,11918,11919,11922],{},[32,11920,11921],{},"README.md"," 없이 4개 문서가 보완하는 구조.",[76,11924,11926],{"id":11925},"_11-doc-폴더-재배치","11. doc 폴더 재배치",[18,11928,11929,11931,11932,11934,11935,6219,11938,11941],{},[32,11930,4296],{},"를 루트에서 ",[32,11933,11907],{},"로 이동하면서 내부 상대 경로 8건을 한 단계 위로 갱신 (",[32,11936,11937],{},"](.\u002FCLAUDE.md)",[32,11939,11940],{},"](..\u002FCLAUDE.md)"," 등).",[73,11943],{},[76,11945,11412],{"id":11411},[81,11947,11948,11956,11961,11964],{},[84,11949,11950,4420,11952,4420,11954],{},[32,11951,11907],{},[32,11953,11875],{},[32,11955,11891],{},[84,11957,11958,11960],{},[32,11959,8210],{}," 구조 개편 (utility bar + main 2단)",[84,11962,11963],{},"콘텐츠 폭 1200 통일 (main.css + AppFooter + AppGnb + AppPagePlaceholder)",[84,11965,11966],{},"로고 baseline 정렬",[76,11968,11970],{"id":11969},"알려진-한계","알려진 한계",[81,11972,11973,11976,11979],{},[84,11974,11975],{},"인증 흐름 미연결 (auth.global.ts는 항상 통과 스텁)",[84,11977,11978],{},"발송 페이지는 placeholder만",[84,11980,11981],{},"캠페인\u002FAI 템플릿 페이지 미착수",{"title":4208,"searchDepth":4209,"depth":4209,"links":11983},[11984,11985,11986,11987,11988,11989,11990,11991,11992,11993,11994,12004,12005,12006],{"id":10635,"depth":4212,"text":10636},{"id":11508,"depth":4212,"text":11509},{"id":11588,"depth":4212,"text":11589},{"id":11611,"depth":4212,"text":11612},{"id":11622,"depth":4212,"text":11623},{"id":11657,"depth":4212,"text":11658},{"id":11679,"depth":4212,"text":11680},{"id":11705,"depth":4212,"text":11706},{"id":11755,"depth":4212,"text":11756},{"id":11772,"depth":4212,"text":11773},{"id":11785,"depth":4212,"text":11995,"children":11996},"10. 문서 3종 추가 (doc\u002F)",[11997,11999,12001,12003],{"id":11791,"depth":4209,"text":11998},"10.1 FRONTEND.md",{"id":11806,"depth":4209,"text":12000},"10.2 STACK.md",{"id":11820,"depth":4209,"text":12002},"10.3 DESIGN.md",{"id":11835,"depth":4209,"text":11836},{"id":11925,"depth":4212,"text":11926},{"id":11411,"depth":4212,"text":11412},{"id":11969,"depth":4212,"text":11970},{},"\u002Fhistory\u002Fhistory.20260512",{"title":11489,"description":4208},"history\u002Fhistory.20260512","gFnxLWHcsh_t9kD3PeQMrhnfscUukAo4HeyLv7b0cuI",{"id":12013,"title":12014,"body":12015,"description":4208,"extension":4257,"meta":12420,"navigation":4259,"path":12421,"seo":12422,"stem":12423,"__hash__":12424},"docs\u002Fhistory\u002Fhistory.20260513.md","2026-05-13 — Smart Placement + 발송 페이지 풍부화",{"type":8,"value":12016,"toc":12402},[12017,12020,12022,12025,12027,12031,12036,12053,12072,12076,12079,12083,12109,12113,12151,12155,12214,12218,12238,12242,12262,12265,12272,12292,12299,12319,12323,12326,12352,12354,12356,12383,12385,12399],[11,12018,12014],{"id":12019},"_2026-05-13-smart-placement-발송-페이지-풍부화",[76,12021,10636],{"id":10635},[18,12023,12024],{},"API Worker에 Smart Placement를 적용해 Aurora 가까이에서 실행되도록 했고, SMS\u002F알림톡 발송 페이지가 21종 공용 컴포넌트 위에 풍부하게 구현되기 시작했다.",[73,12026],{},[76,12028,12030],{"id":12029},"_1-api-worker-smart-placement","1. API Worker Smart Placement",[18,12032,12033,12035],{},[32,12034,11253],{},"에 추가:",[5251,12037,12041],{"className":12038,"code":12039,"language":12040,"meta":4208,"style":4208},"language-toml shiki shiki-themes github-light github-dark","[placement]\nmode = \"smart\"\n","toml",[32,12042,12043,12048],{"__ignoreMap":4208},[7968,12044,12045],{"class":5110,"line":7970},[7968,12046,12047],{},"[placement]\n",[7968,12049,12050],{"class":5110,"line":4212},[7968,12051,12052],{},"mode = \"smart\"\n",[81,12054,12055,12058,12061,12064],{},[84,12056,12057],{},"Cloudflare가 트래픽 패턴(외부 호출 빈도·지연)을 분석해 Worker 실행 위치를 자동 최적화",[84,12059,12060],{},"Aurora MySQL(AWS) ↔ Hyperdrive 호출이 잦아지면 Aurora 리전에 가까운 데이터센터에서 실행",[84,12062,12063],{},"학습 기반이라 초기 며칠은 일반 placement, 트래픽 누적 후 최적 위치로 수렴",[84,12065,12066,12068,12069,4343],{},[32,12067,11261],{}," 즉시 적용 (Version ",[32,12070,12071],{},"8583dd1e-f8a2-46b6-8704-944662f97175",[76,12073,12075],{"id":12074},"_2-발송-페이지-공용-컴포넌트-21종-추가","2. 발송 페이지 공용 컴포넌트 21종 추가",[18,12077,12078],{},"SMS\u002F알림톡 발송 페이지를 풍부하게 구현하면서 공용 컴포넌트가 정착.",[237,12080,12082],{"id":12081},"_21-수신자-영역","2.1 수신자 영역",[81,12084,12085,12091,12097,12103],{},[84,12086,12087,12090],{},[32,12088,12089],{},"AppRecipientTable.vue"," — 수신자 행 표시, 다중 선택",[84,12092,12093,12096],{},[32,12094,12095],{},"AppRecipientFormDialog.vue"," — 수신자 직접 입력 \u002F 수정 다이얼로그",[84,12098,12099,12102],{},[32,12100,12101],{},"AppRecipientActions.vue"," — 추가\u002F편집\u002F삭제 액션바",[84,12104,12105,12108],{},[32,12106,12107],{},"AppAddressBookDialog.vue"," — 주소록에서 연락처\u002F그룹 선택 모달",[237,12110,12112],{"id":12111},"_22-발송-폼-영역","2.2 발송 폼 영역",[81,12114,12115,12121,12127,12133,12139,12145],{},[84,12116,12117,12120],{},[32,12118,12119],{},"AppSendFormCard.vue"," — 점진적 disclosure 카드(잠금 표시 포함)",[84,12122,12123,12126],{},[32,12124,12125],{},"AppSendOptionsCard.vue"," — 발송 시점\u002F대체 문자 등 옵션",[84,12128,12129,12132],{},[32,12130,12131],{},"AppSendActionsBar.vue"," — 발송\u002F예약\u002F초기화 버튼 묶음",[84,12134,12135,12138],{},[32,12136,12137],{},"AppSendConfirmDialog.vue"," — 발송 컨펌(수신자 수, 차감 크레딧, 잔여 등)",[84,12140,12141,12144],{},[32,12142,12143],{},"AppSenderSearchSelect.vue"," — 발신 프로필\u002F번호 검색·선택 콤보",[84,12146,12147,12150],{},[32,12148,12149],{},"AppDateTimePicker.vue"," — 예약 발송 시각 선택",[237,12152,12154],{"id":12153},"_23-메시지-본문-영역","2.3 메시지 본문 영역",[81,12156,12157,12163,12169,12175,12184,12190,12196,12202,12208],{},[84,12158,12159,12162],{},[32,12160,12161],{},"SmsMessageBody.vue"," — SMS\u002FLMS\u002FMMS 본문 작성",[84,12164,12165,12168],{},[32,12166,12167],{},"KakaoMessageBody.vue"," — 알림톡 템플릿 코드\u002F강조형\u002F변수",[84,12170,12171,12174],{},[32,12172,12173],{},"AppChannelMessageCard.vue"," — 채널 공통 메시지 영역 래퍼",[84,12176,12177,89,12180,12183],{},[32,12178,12179],{},"AppTemplateVariableTextarea.vue",[32,12181,12182],{},"#{name}"," 같은 변수 토큰 입력",[84,12185,12186,12189],{},[32,12187,12188],{},"AppTemplatePickerDialog.vue"," — 저장된 템플릿 선택 모달",[84,12191,12192,12195],{},[32,12193,12194],{},"AppSmsTemplateCard.vue"," — SMS 템플릿 카드",[84,12197,12198,12201],{},[32,12199,12200],{},"AppAIRewriteDialog.vue"," — AI 템플릿 재작성\u002F생성 모달",[84,12203,12204,12207],{},[32,12205,12206],{},"AppAdNoticeSms080Dialog.vue"," — 광고 수신 동의\u002F080 안내 모달",[84,12209,12210,12213],{},[32,12211,12212],{},"AppFileUploader.vue"," — 파일 업로드 공용 컴포넌트",[237,12215,12217],{"id":12216},"_24-미리보기-영역","2.4 미리보기 영역",[81,12219,12220,12226,12232],{},[84,12221,12222,12225],{},[32,12223,12224],{},"AppPhonePreview.vue"," — 휴대폰 모형 미리보기 컨테이너",[84,12227,12228,12231],{},[32,12229,12230],{},"AppPhonePreviewSmsBubble.vue"," — SMS 말풍선",[84,12233,12234,12237],{},[32,12235,12236],{},"AppPhonePreviewKakaoBubble.vue"," — 알림톡 말풍선",[76,12239,12241],{"id":12240},"_3-composable-타입-추가","3. composable \u002F 타입 추가",[81,12243,12244,12250],{},[84,12245,12246,12249],{},[32,12247,12248],{},"composables\u002FuseChannelMeta.ts"," — 채널별 메타(가격·발신정보 형식·disclosure 단계 정의)",[84,12251,12252,12254,12255,4473,12258,12261],{},[32,12253,7293],{}," — Channel 타입, ",[32,12256,12257],{},"isCardUnlocked",[32,12259,12260],{},"isSubstitutionActive"," 헬퍼",[18,12263,12264],{},"이 단일 composable이 5채널(SMS\u002FRCS\u002F알림톡\u002F이메일\u002FPUSH) 발송 페이지의 공통 동작(점진적 disclosure·가격 계산·치환 변수 활성 여부)을 한 곳에 모음.",[76,12266,12268,12269,4343],{"id":12267},"_4-sms-발송-페이지-sendsms","4. SMS 발송 페이지 (",[32,12270,12271],{},"\u002Fsend\u002Fsms",[81,12273,12274,12280,12283,12286,12289],{},[84,12275,12276,12279],{},[32,12277,12278],{},"useChannelMeta('sms')"," 기반",[84,12281,12282],{},"발신 번호 검색 → 잠금 해제 → 수신자 등록 → 메시지 입력 → 발송",[84,12284,12285],{},"휴대폰 미리보기 실시간 반영",[84,12287,12288],{},"광고 수신 안내(080) 모달 트리거",[84,12290,12291],{},"발송 컨펌(수신자 수·차감 크레딧·잔여)",[76,12293,12295,12296,4343],{"id":12294},"_5-알림톡-발송-페이지-sendkakao","5. 알림톡 발송 페이지 (",[32,12297,12298],{},"\u002Fsend\u002Fkakao",[81,12300,12301,12306,12313,12316],{},[84,12302,12303,12279],{},[32,12304,12305],{},"useChannelMeta('kakao')",[84,12307,12308,12309,12312],{},"발신 프로필(",[32,12310,12311],{},"@위캔디오"," 등) 검색 → 템플릿 선택 → 수신자 → 발송",[84,12314,12315],{},"카카오 템플릿: 일반\u002F추가 정보형\u002F강조표기형, 버튼 4종(web\u002Fapp\u002Fphone\u002Fmessage), 변수 치환",[84,12317,12318],{},"휴대폰 미리보기에 알림톡 말풍선 + 버튼 렌더",[76,12320,12322],{"id":12321},"_6-작업-분석-워크플랜-정리","6. 작업 분석 \u002F 워크플랜 정리",[18,12324,12325],{},"사용자 요청으로 작업 단계를 세분화. 4개 카테고리 → 세부 태스크 + 의존성 + 마일스톤 8개 (M1~M8) 정리.",[81,12327,12328,12331,12334,12337,12340,12343,12346,12349],{},[84,12329,12330],{},"M1: 데이터 모델링 + 인증 API + 인증 UI 연결",[84,12332,12333],{},"M2: 주소록\u002F발신정보\u002F템플릿 CRUD",[84,12335,12336],{},"M3: SMS\u002F알림톡 발송 + 이력 (MVP)",[84,12338,12339],{},"M4: 충전 + PG 연동 (유료 전환 가능)",[84,12341,12342],{},"M5: Flow + 캠페인 + 대량 발송",[84,12344,12345],{},"M6: AI 템플릿 + ExportJob",[84,12347,12348],{},"M7: 운영자단 전체",[84,12350,12351],{},"M8: 통계\u002FRBAC\u002FRate Limit\u002FOpenAPI",[73,12353],{},[76,12355,11412],{"id":11411},[81,12357,12358,12363,12366,12374,12377],{},[84,12359,12360,12362],{},[32,12361,11253],{}," Smart Placement",[84,12364,12365],{},"발송 페이지 공용 컴포넌트 21종",[84,12367,12368,12371,12372],{},[32,12369,12370],{},"useChannelMeta"," composable + ",[32,12373,7293],{},[84,12375,12376],{},"SMS\u002F알림톡 발송 페이지 풍부 구현",[84,12378,12379,12380,12382],{},"API Worker Version ",[32,12381,12071],{}," 배포",[76,12384,4189],{"id":4188},[81,12386,12387,12390,12393,12396],{},[84,12388,12389],{},"RCS\u002F이메일\u002FPUSH\u002FFlow 발송 페이지 (동일 패턴 재사용)",[84,12391,12392],{},"발송 조회\u002F통계 페이지",[84,12394,12395],{},"주소록\u002F발신 정보\u002F템플릿 페이지 풍부 구현",[84,12397,12398],{},"데이터 모델링 + API 기초 (M1 시작)",[8560,12400,12401],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":4208,"searchDepth":4209,"depth":4209,"links":12403},[12404,12405,12406,12412,12413,12415,12417,12418,12419],{"id":10635,"depth":4212,"text":10636},{"id":12029,"depth":4212,"text":12030},{"id":12074,"depth":4212,"text":12075,"children":12407},[12408,12409,12410,12411],{"id":12081,"depth":4209,"text":12082},{"id":12111,"depth":4209,"text":12112},{"id":12153,"depth":4209,"text":12154},{"id":12216,"depth":4209,"text":12217},{"id":12240,"depth":4212,"text":12241},{"id":12267,"depth":4212,"text":12414},"4. SMS 발송 페이지 (\u002Fsend\u002Fsms)",{"id":12294,"depth":4212,"text":12416},"5. 알림톡 발송 페이지 (\u002Fsend\u002Fkakao)",{"id":12321,"depth":4212,"text":12322},{"id":11411,"depth":4212,"text":11412},{"id":4188,"depth":4212,"text":4189},{},"\u002Fhistory\u002Fhistory.20260513",{"title":12014,"description":4208},"history\u002Fhistory.20260513","oxLAGSjjJITIO3HeziCTs-ONDdgNhO6XcRM7Oen_2Ec",{"id":12426,"title":12427,"body":12428,"description":4208,"extension":4257,"meta":12649,"navigation":4259,"path":12650,"seo":12651,"stem":12652,"__hash__":12653},"docs\u002Fhistory\u002Fhistory.20260514.md","2026-05-14 — 변경사항 GitHub 푸시 + history 폴더 정리",{"type":8,"value":12429,"toc":12635},[12430,12433,12435,12442,12444,12448,12451,12512,12516,12529,12533,12541,12548,12551,12557,12561,12572,12576,12599,12601,12603,12617,12619],[11,12431,12427],{"id":12432},"_2026-05-14-변경사항-github-푸시-history-폴더-정리",[76,12434,10636],{"id":10635},[18,12436,12437,12438,12441],{},"그동안의 디자인·발송 페이지·Smart Placement 변경 전체를 세 레포에 일괄 커밋·푸시하고, 작업 내역을 ",[32,12439,12440],{},"doc\u002Fhistory\u002F","에 날짜별로 분리해 기록했다.",[73,12443],{},[76,12445,12447],{"id":12446},"_1-github-푸시-세-레포-일괄","1. GitHub 푸시 (세 레포 일괄)",[18,12449,12450],{},"이전 첫 커밋 이후 누적된 변경 전부를 main에 커밋·푸시.",[101,12452,12453,12464],{},[104,12454,12455],{},[107,12456,12457,12459,12462],{},[110,12458,11107],{},[110,12460,12461],{},"이전 → 새 커밋",[110,12463,7525],{},[126,12465,12466,12480,12494],{},[107,12467,12468,12472,12477],{},[131,12469,12470],{},[32,12471,7664],{},[131,12473,12474],{},[32,12475,12476],{},"0b444f5 → 8d68005",[131,12478,12479],{},"디자인 시스템 적용, GNB+Footer 추가, 콘텐츠 폭 1200px, 발송 페이지 공용 컴포넌트 21종, 문서 3종",[107,12481,12482,12486,12491],{},[131,12483,12484],{},[32,12485,50],{},[131,12487,12488],{},[32,12489,12490],{},"ac45973 → decfaf0",[131,12492,12493],{},"Hono Worker 부트스트랩, Smart Placement",[107,12495,12496,12500,12505],{},[131,12497,12498],{},[32,12499,7672],{},[131,12501,12502],{},[32,12503,12504],{},"0166ffa → 89abceb",[131,12506,12507,12508,12511],{},"placeholder 정적 페이지, ",[32,12509,12510],{},".wrangler"," ignore",[237,12513,12515],{"id":12514},"사전-작업","사전 작업",[81,12517,12518],{},[84,12519,12520,5069,12523,7505,12525,12528],{},[32,12521,12522],{},"malgn-noti-admin\u002F.gitignore",[32,12524,12510],{},[32,12526,12527],{},".dev.vars"," 추가 (wrangler 빌드 캐시가 git에 포함되지 않도록)",[237,12530,12532],{"id":12531},"커밋-메시지","커밋 메시지",[81,12534,12535,12538],{},[84,12536,12537],{},"한국어 본문, 변경 항목 불릿",[84,12539,12540],{},"Claude co-author trailer 일관 포함",[76,12542,12544,12545,12547],{"id":12543},"_2-dochistory-폴더-신설","2. ",[32,12546,12440],{}," 폴더 신설",[18,12549,12550],{},"날짜별 작업 내역을 별도 폴더에 누적하도록 구조화.",[5251,12552,12555],{"className":12553,"code":12554,"language":5256},[5254],"doc\u002F\n├── CLAUDE.md (루트 — 큰 그림 \u002F 결정)\n├── STACK.md\n├── DESIGN.md\n├── FRONTEND.md\n└── history\u002F\n    ├── history.20260511.md   # 프로젝트 착수\n    ├── history.20260512.md   # 시안 매칭 + 문서화\n    ├── history.20260513.md   # Smart Placement + 발송 페이지\n    └── history.20260514.md   # 이 문서\n",[32,12556,12554],{"__ignoreMap":4208},[237,12558,12560],{"id":12559},"파일명-규칙","파일명 규칙",[81,12562,12563,12569],{},[84,12564,12565,12568],{},[32,12566,12567],{},"history.yyyyMMdd.md"," — 한 날짜 한 파일",[84,12570,12571],{},"작업이 있는 날만 생성",[237,12573,12575],{"id":12574},"각-파일-구조-공통","각 파일 구조 (공통)",[6674,12577,12578,12583,12589,12594],{},[84,12579,12580,12582],{},[21,12581,10636],{}," — 그 날의 가장 큰 변화",[84,12584,12585,12588],{},[21,12586,12587],{},"번호별 작업 섹션"," — 결정 \u002F 코드 변경 \u002F 배포",[84,12590,12591,12593],{},[21,12592,10916],{}," — 추가\u002F수정된 파일·문서·배포 버전",[84,12595,12596],{},[21,12597,12598],{},"다음 단계 \u002F 알려진 한계",[73,12600],{},[76,12602,11412],{"id":11411},[81,12604,12605,12612],{},[84,12606,12607,12608,12611],{},"세 레포 GitHub 동기화 완료 (",[32,12609,12610],{},"origin\u002Fmain"," 일치)",[84,12613,12614,12616],{},[32,12615,12440],{}," 폴더 + 4개 history 파일",[76,12618,4189],{"id":4188},[81,12620,12621,12632],{},[84,12622,12623,12624,12627,12628,12631],{},"마일스톤 ",[21,12625,12626],{},"M1",": 데이터 모델링(",[32,12629,12630],{},"malgn-noti-api\u002Fdb\u002Fschema.ts",") + 인증 API + 사용자단 인증 흐름 연결",[84,12633,12634],{},"또는 RCS\u002F이메일\u002FPUSH\u002FFlow 발송 페이지 (이미 잡힌 공용 컴포넌트 재사용)",{"title":4208,"searchDepth":4209,"depth":4209,"links":12636},[12637,12638,12642,12647,12648],{"id":10635,"depth":4212,"text":10636},{"id":12446,"depth":4212,"text":12447,"children":12639},[12640,12641],{"id":12514,"depth":4209,"text":12515},{"id":12531,"depth":4209,"text":12532},{"id":12543,"depth":4212,"text":12643,"children":12644},"2. doc\u002Fhistory\u002F 폴더 신설",[12645,12646],{"id":12559,"depth":4209,"text":12560},{"id":12574,"depth":4209,"text":12575},{"id":11411,"depth":4212,"text":11412},{"id":4188,"depth":4212,"text":4189},{},"\u002Fhistory\u002Fhistory.20260514",{"title":12427,"description":4208},"history\u002Fhistory.20260514","Fk-sFlE4YIznIH7xzpU-xTbKexr3B_BpGx76eiz0PEI",{"id":12655,"title":12656,"body":12657,"description":4208,"extension":4257,"meta":13209,"navigation":4259,"path":13210,"seo":13211,"stem":13212,"__hash__":13213},"docs\u002Fhistory\u002Fhistory.20260518.md","2026-05-18 — 디자인 시스템 전면 피벗 (Relay-inspired)",{"type":8,"value":12658,"toc":13198},[12659,12662,12664,12685,12687,12691,12724,12728,12795,12799,12843,12847,12920,12924,12985,12989,13030,13032,13036,13151,13154],[11,12660,12656],{"id":12661},"_2026-05-18-디자인-시스템-전면-피벗-relay-inspired",[76,12663,10636],{"id":10635},[18,12665,12666,12667,12669,12670,12673,12674,12676,12677,12680,12681,12684],{},"malgn-notifications 시안 기반 디자인(indigo\u002Fsky\u002FNoto Sans KR, 1200px)을 폐기하고, ",[32,12668,4280],{}," 핸드오프의 ",[21,12671,12672],{},"Relay-inspired Design System v1.0","(ink 무채색 + 단일 그린 액센트 ",[32,12675,4672],{},", Inter\u002FJetBrains Mono\u002FPretendard, 1px hairline, 저밀도)로 ",[21,12678,12679],{},"전면 교체",". 토큰→셸→발송 전 채널까지 적용하고 ",[32,12682,12683],{},"design-system-pivot"," 브랜치로 푸시.",[73,12686],{},[76,12688,12690],{"id":12689},"_1-결정-디자인-정본-전환","1. 결정 — 디자인 정본 전환",[81,12692,12693,12699,12705,12715],{},[84,12694,12695,12696,12698],{},"사용자가 ",[32,12697,4280],{}," 핸드오프(README\u002FDESIGN_GUIDE\u002Fmain.css\u002Fcomponents\u002Fpages, 13화면 hifi) 제공.",[84,12700,12701,12704],{},[21,12702,12703],{},"핵심 충돌",": 핸드오프가 CLAUDE.md의 \"시안 = 단일 정본(SoT)\" 규정과 정면 충돌. 비가역·대규모 변경이라 방향 확인.",[84,12706,12707,12708,4339,12711,12714],{},"사용자 결정: ",[21,12709,12710],{},"전면 피벗 — 신규를 정본으로",[21,12712,12713],{},"토큰 먼저 → 체크포인트"," 단계 진행.",[84,12716,12717,12718,12721,12722,4703],{},"시안은 이제 ",[21,12719,12720],{},"IA(페이지 목록\u002F라우트)만"," 참조, 디자인 언어 정본은 ",[32,12723,11891],{},[76,12725,12727],{"id":12726},"_2-phase-1-토큰-폰트-문서","2. Phase 1 — 토큰 · 폰트 · 문서",[81,12729,12730,12749,12769,12774,12779,12784],{},[84,12731,12732,12734,12735,12737,12738,12740,12741,5439,12743,6100,12746,12748],{},[32,12733,4352],{},": ink 11단 + accent + semantic 토큰, ",[32,12736,4342],{},"(폰트·accent), 핸드오프 컴포넌트 CSS 이식, 구 토큰 backward-compat 별칭(",[32,12739,4608],{},"→ink로 미적용 화면 색 자동 이행), ",[32,12742,6824],{},[21,12744,12745],{},"Tailwind v4 \u002F Nuxt UI v3 통합 유지",[32,12747,7782],{}," 미설치 원칙 준수).",[84,12750,12751,12753,12754,4361,12757,12760,12761,12763,12764,4361,12766,12768],{},[32,12752,4392],{},": Nuxt UI ",[32,12755,12756],{},"primary",[32,12758,12759],{},"neutral"," = ",[32,12762,4382],{},". 그린은 절제 사용 원칙상 ",[32,12765,5907],{},[32,12767,5076],{},"로 명시 적용.",[84,12770,12771,12773],{},[32,12772,4405],{},": Noto Sans KR → Inter + JetBrains Mono + Instrument Serif + Pretendard.",[84,12775,12776,12778],{},[32,12777,11891],{},": 909줄 시안 가이드 → Relay v1.0 정본으로 전면 재작성.",[84,12780,12781,12783],{},[32,12782,7682],{},": 시안=SoT 서술 수정(IA 한정), §2 폰트·디자인시스템 갱신.",[84,12785,12786,12787,12790,12791,12794],{},"사고: CSS 주석 내 ",[32,12788,12789],{},"*\u002F"," 시퀀스(",[32,12792,12793],{},"--gray-*\u002F--primary-*",")가 주석 조기 종료 → PostCSS 파싱 깨짐, 즉시 수정.",[76,12796,12798],{"id":12797},"_3-phase-2a-셸-홈","3. Phase 2a — 셸 + 홈",[81,12800,12801,12818,12823,12831,12836],{},[84,12802,12803,12805,12806,12809,12810,12813,12814,12817],{},[32,12804,8210],{},": 2단 GNB → ",[21,12807,12808],{},"56px 단일 토pbar","(zap 로고 박스, 호버 드롭다운, ",[32,12811,12812],{},".pill-*"," 우측 액션, 모바일 ",[32,12815,12816],{},".drawer-*"," USlideover). 프로젝트 실 라우트 메뉴 유지.",[84,12819,12820,12822],{},[32,12821,4424],{},": 검정 풀블리드 → 화이트 1px hairline.",[84,12824,12825,4536,12827,12830],{},[32,12826,11355],{},[32,12828,12829],{},"auth.vue"," 신규 구조.",[84,12832,12833,12835],{},[32,12834,4441],{},": KPI 4-stat, AI 일일 요약 카드(ink-900+그린 Sparkles), 6채널 row, 다크 크레딧 카드, 최근 발송 테이블. 아이콘 lucide 매핑.",[84,12837,12838,12839,12842],{},"제품명은 핸드오프 \"맑은 노티\" 대신 ",[21,12840,12841],{},"프로젝트 정본 \"맑은 메시징\""," 유지(시각만 채택).",[76,12844,12846],{"id":12845},"_4-phase-2b-1-발송-공용-컴포넌트-sms-레퍼런스","4. Phase 2b-1 — 발송 공용 컴포넌트 + SMS 레퍼런스",[81,12848,12849,12872,12900,12907],{},[84,12850,12851,12852,4361,12854,4361,12857,4361,12860,4361,12863,4361,12866,4473,12869,4703],{},"공용 프리미티브 신규: ",[32,12853,4476],{},[32,12855,12856],{},"AppBadge",[32,12858,12859],{},"AppFormRow",[32,12861,12862],{},"AppRadioGroup",[32,12864,12865],{},"AppByteCounter",[32,12867,12868],{},"AppEmptyState",[32,12870,12871],{},"app\u002Futils\u002FbyteLen.ts",[84,12873,12874,12875,4361,12877,4361,12879,4361,12881,4361,12883,4361,12885,4361,12887,12889,12890,4361,12892,4361,12894,12889,12896,4361,12898,4703],{},"발송 도메인 재작업: ",[32,12876,4479],{},[32,12878,7328],{},[32,12880,7324],{},[32,12882,6117],{},[32,12884,7331],{},[32,12886,7358],{},[32,12888,4482],{},"(신규)\u002F",[32,12891,7346],{},[32,12893,7340],{},[32,12895,4485],{},[32,12897,7452],{},[32,12899,7442],{},[84,12901,12902,12904,12905,4703],{},[32,12903,4472],{},": 5-카드 골격(발신→수신자→메시지+iMessage 프리뷰→발송옵션) + 7종 다이얼로그 + 템플릿 잠금\u002F광고 080\u002FAI\u002F예약\u002F비용계산. 토스트는 Nuxt UI ",[32,12906,6633],{},[84,12908,12909,12912,12913,12916,12917,12919],{},[32,12910,12911],{},"app\u002Ftypes\u002Frecipient.ts"," 분리 — SFC ",[32,12914,12915],{},"\u003Cscript setup>","은 ",[32,12918,7974],{}," 불가 → 타입 별도 모듈.",[76,12921,12923],{"id":12922},"_5-phase-2b-2-알림톡rcs이메일pushflow","5. Phase 2b-2 — 알림톡\u002FRCS\u002F이메일\u002FPUSH\u002FFlow",[81,12925,12926,12945,12956,12979],{},[84,12927,12928,12929,12931,12932,4361,12934,4361,12936,4473,12938,4473,12940,4473,12942,12944],{},"채널 프리뷰 신규: ",[32,12930,4503],{},"(변수 pill)\u002F",[32,12933,4506],{},[32,12935,4509],{},[32,12937,4512],{},[32,12939,4515],{},[32,12941,4518],{},[32,12943,4521],{},"(변수 영역만 편집).",[84,12946,12947,5069,12949,4361,12952,12955],{},[32,12948,4482],{},[32,12950,12951],{},"locked",[32,12953,12954],{},"lockedHint"," prop 추가(알림톡 점진적 disclosure).",[84,12957,12958,12959,12962,12963,12966,12967,12970,12971,12974,12975,12978],{},"페이지 재작업: ",[32,12960,12961],{},"kakao","(disclosure)\u002F",[32,12964,12965],{},"rcs","(3-단 select+버튼빌더)\u002F",[32,12968,12969],{},"email","(HTML+첨부)\u002F",[32,12972,12973],{},"push","(segmented+토큰)\u002F",[32,12976,12977],{},"flow","(노드 시퀀스).",[84,12980,12981,12984],{},[32,12982,12983],{},"app\u002Ftypes\u002Ftemplate.ts"," 분리(KakaoTpl).",[76,12986,12988],{"id":12987},"_6-git-푸시","6. Git 푸시",[81,12990,12991,13000,13007,13023],{},[84,12992,12993,12996,12997,12999],{},[32,12994,12995],{},"main","이 기본 브랜치 → ",[32,12998,12683],{}," 브랜치 분기 후 푸시.",[84,13001,13002,13003,13006],{},"커밋 ",[32,13004,13005],{},"a1b4993"," — 44개 파일, +5547 \u002F -4393.",[84,13008,13009,13010,4536,13012,4536,13014,4536,13016,5439,13018],{},"범위: ",[32,13011,7737],{},[32,13013,7682],{},[32,13015,11891],{},[32,13017,4405],{},[21,13019,13020,13022],{},[32,13021,12440],{},"는 무관·기존 untracked이라 제외.",[84,13024,13025,13026],{},"PR 링크: ",[26,13027,13028],{"href":13028,"rel":13029},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-noti\u002Fpull\u002Fnew\u002Fdesign-system-pivot",[30],[73,13031],{},[76,13033,13035],{"id":13034},"산출물-당일","산출물 (당일)",[81,13037,13038,13047,13056,13087,13117,13129,13137,13145],{},[84,13039,13040,13041,4473,13043,4473,13045],{},"토큰\u002F설정: ",[32,13042,4352],{},[32,13044,4392],{},[32,13046,4405],{},[84,13048,13049,13050,13052,13053,13055],{},"문서: ",[32,13051,11891],{},"(재작성), ",[32,13054,7682],{},"(SoT 수정), 이 history 파일",[84,13057,13058,13059,4361,13061,4361,13063,4361,13065,4361,13067,4361,13069,4361,13071,4361,13073,4361,13075,4361,13077,4361,13079,4361,13081,4361,13083,4361,13085],{},"신규 컴포넌트 14: ",[32,13060,4476],{},[32,13062,12856],{},[32,13064,12859],{},[32,13066,12862],{},[32,13068,12865],{},[32,13070,12868],{},[32,13072,4482],{},[32,13074,4485],{},[32,13076,4503],{},[32,13078,4506],{},[32,13080,4509],{},[32,13082,4512],{},[32,13084,4515],{},[32,13086,4518],{},[84,13088,13089,13090,4361,13092,4361,13095,4361,13097,4361,13099,4361,13101,4361,13103,4361,13105,4361,13107,4361,13109,4361,13111,4361,13113,4361,13115],{},"재작업: ",[32,13091,11316],{},[32,13093,13094],{},"AppFooter",[32,13096,4479],{},[32,13098,7328],{},[32,13100,7324],{},[32,13102,6117],{},[32,13104,7331],{},[32,13106,7358],{},[32,13108,7346],{},[32,13110,7340],{},[32,13112,7452],{},[32,13114,7442],{},[32,13116,4521],{},[84,13118,13119,13120,4473,13123,4473,13126],{},"페이지: ",[32,13121,13122],{},"home",[32,13124,13125],{},"send\u002F{sms,kakao,rcs,email,push,flow}",[32,13127,13128],{},"layouts\u002F{default,auth}",[84,13130,13131,13132,4473,13135],{},"타입\u002F유틸: ",[32,13133,13134],{},"app\u002Ftypes\u002F{recipient,template}.ts",[32,13136,12871],{},[84,13138,13139,13140,6685,13142,13144],{},"브랜치 ",[32,13141,12683],{},[32,13143,11097],{}," 푸시 완료)",[84,13146,13147,13148,13150],{},"검증: 전 단계 ",[32,13149,11083],{}," 체크포인트 통과 — 컴파일\u002F하이드레이션\u002FVue 경고 0",[76,13152,12598],{"id":13153},"다음-단계-알려진-한계",[81,13155,13156,13178,13185,13192],{},[84,13157,13158,13161,13162],{},[21,13159,13160],{},"Phase 2b-3 남음",": 이력·통계·주소록·발신정보·메시지관리·캠페인·충전·인증 페이지.\n",[81,13163,13164,13171],{},[84,13165,13166,13167,13170],{},"핸드오프 ",[32,13168,13169],{},"other-pages.jsx","에 발송조회\u002F통계\u002F주소록\u002F충전\u002F로그인\u002F회원가입 시안 존재.",[84,13172,13173,13174,13177],{},"발신정보·메시지관리·캠페인 등은 ",[21,13175,13176],{},"핸드오프에 시안 없음"," → 별도 협의 필요.",[84,13179,13180,13181,13184],{},"미적용 화면은 backward-compat 별칭으로 ",[21,13182,13183],{},"색만"," 이행된 상태(간격·폰트·형태는 구 시안).",[84,13186,13187,6219,13189,13191],{},[32,13188,12683],{},[32,13190,12995],{}," 머지\u002FPR 미생성 (사용자 결정 대기).",[84,13193,13194,13195,13197],{},"형제 레포(",[32,13196,7672],{},")는 별도 디자인 가이드 필요(공용 코어\u002F사용자단 분리 논의 보류 — 핸드오프가 사용자단 한정).",{"title":4208,"searchDepth":4209,"depth":4209,"links":13199},[13200,13201,13202,13203,13204,13205,13206,13207,13208],{"id":10635,"depth":4212,"text":10636},{"id":12689,"depth":4212,"text":12690},{"id":12726,"depth":4212,"text":12727},{"id":12797,"depth":4212,"text":12798},{"id":12845,"depth":4212,"text":12846},{"id":12922,"depth":4212,"text":12923},{"id":12987,"depth":4212,"text":12988},{"id":13034,"depth":4212,"text":13035},{"id":13153,"depth":4212,"text":12598},{},"\u002Fhistory\u002Fhistory.20260518",{"title":12656,"description":4208},"history\u002Fhistory.20260518","mMc9AjM3FD__p9c9NYC8IbPnxlVLkMMUtWsOtoDc3eE",{"id":13215,"title":13216,"body":13217,"description":4208,"extension":4257,"meta":14602,"navigation":4259,"path":14603,"seo":14604,"stem":14605,"__hash__":14606},"docs\u002Fhistory\u002Fhistory.20260519.md","2026-05-19 — 디자인 가이드 페이지 + Cloudflare 프로덕션 배포",{"type":8,"value":13218,"toc":14579},[13219,13222,13224,13230,13232,13238,13288,13292,13375,13379,13397,13399,13403,13468,13472,13529,13533,13591,13595,13632,13636,13691,13695,13698,13845,13849,13944,13948,14012,14016,14106,14110,14230,14234,14272,14276,14323,14327,14362,14366,14373,14462,14464,14466,14556,14558],[11,13220,13216],{"id":13221},"_2026-05-19-디자인-가이드-페이지-cloudflare-프로덕션-배포",[76,13223,10636],{"id":10635},[18,13225,13226,13227,13229],{},"Relay-inspired 정본을 시각화한 ",[32,13228,4457],{}," 라이브 카탈로그 페이지(18섹션)를 추가하고, 디자인 피벗 전체(Phase 1~2b-2 + 가이드)를 Cloudflare Pages 프로덕션에 배포. 이후 UI 스케일 115%·1400px 폭·정렬 근본수정·로고 이미지화·푸터 다크·채널칩 통일·카드강조 B·헤더 불투명·수신자 접기 등 페이지 설정과 **Phase 2b-3(발송조회\u002F통계\u002F주소록\u002F충전\u002F인증)**까지 적용하며 재배포(총 10회).",[73,13231],{},[76,13233,13235,13236,4343],{"id":13234},"_1-디자인-가이드-페이지-guide","1. 디자인 가이드 페이지 (",[32,13237,4457],{},[81,13239,13240,13256,13273,13282],{},[84,13241,13242,13244,13245],{},[32,13243,7929],{}," 신설 — sticky 사이드 nav + 스크롤 스파이 + 18섹션 라이브 카탈로그.\n",[81,13246,13247,13250],{},[84,13248,13249],{},"소개(hero) · 01 원칙(7) · 02 컬러(ink 11단\u002Faccent\u002Fsemantic\u002F채널 도트) · 03 타이포 · 04 간격 · 05 라운드·그림자 · 06 버튼 · 07 Pill · 08 폼 · 09 배지 · 10 카드 · 11 테이블 · 12 빈상태 · 13 토스트 · 14 미리보기 폰 5종 · 15 레이아웃 · 16 5-카드 골격+매트릭스 · 17 톤",[84,13251,13252,13253,13255],{},"실제 ",[32,13254,4597],{}," 컴포넌트로 라이브 예시 렌더 (AppBadge\u002FAppEmptyState\u002FPhone·Kakao·Rcs·Email·Push Preview\u002FAppFormRow\u002FAppRadioGroup\u002FAppSegmented\u002FAppSendFormCard).",[84,13257,13258,13260,13261,13264,13265,13268,13269,13272],{},[21,13259,10663],{},": 핸드오프 ",[32,13262,13263],{},"design-guide.jsx","는 구 토큰이 섞인 stale 아티팩트(",[32,13266,13267],{},"--color-sky-vivid",", Noto Sans KR, 1200px, indigo 그라데이션)라 그대로 복사하지 않고, ",[21,13270,13271],{},"현재 정본(ink\u002Faccent, Inter\u002FJetBrains Mono, 1400px, r-sm\u002Fmd\u002Flg, shadow-soft\u002Fpopover\u002Fmodal)에 맞춰 값·라벨 재작성",". 가이드 표기값 = main.css 일치.",[84,13274,13275,13278,13279,13281],{},[32,13276,13277],{},"app\u002Fpages\u002Fhome.vue"," 바로가기에 \"디자인 가이드\"(",[32,13280,4457],{},") 링크 추가.",[84,13283,13284,13285,13287],{},"검증: ",[32,13286,4457],{}," HTTP 200 (87.6KB), 컴파일·하이드레이션 오류 0.",[76,13289,13291],{"id":13290},"_2-cloudflare-pages-프로덕션-배포","2. Cloudflare Pages 프로덕션 배포",[81,13293,13294,13307,13319,13326,13365],{},[84,13295,13296,13297,13299,13300,13303,13304,13306],{},"빌드: ",[32,13298,11195],{}," (Nitro ",[32,13301,13302],{},"cloudflare-pages"," 프리셋) → ",[32,13305,10325],{}," 1.35MB \u002F gzip 423KB.",[84,13308,13309,13310,6219,13312,13314,13315,13318],{},"인증: ",[32,13311,11166],{},[26,13313,11171],{"href":11170}," (account ",[32,13316,13317],{},"d2b8c552…",", 기존 배포 계정 동일).",[84,13320,13321,13322,13325],{},"배포: ",[32,13323,13324],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main"," (프로덕션).",[84,13327,13328,13329],{},"결과:\n",[81,13330,13331,13342,13349],{},[84,13332,13333,13334,89,13337,4536,13339,13341],{},"프로덕션: ",[26,13335,10317],{"href":10317,"rel":13336},[30],[32,13338,11278],{},[32,13340,4457],{}," HTTP 200",[84,13343,13344,13345],{},"배포 alias: ",[26,13346,13347],{"href":13347,"rel":13348},"https:\u002F\u002F4b3da057.malgn-noti.pages.dev",[30],[84,13350,13351,13352,4420,13355,4420,13358,13361,13362,13364],{},"라이브 마커 확인: ",[32,13353,13354],{},"gnb-wrap",[32,13356,13357],{},"ai-card",[32,13359,13360],{},"credit-hero"," · Inter 폰트 · ",[32,13363,5426],{}," · \"운영 콘솔\"",[84,13366,13367,13368,4361,13371,13374],{},"배포 시점 working tree 기준이라 ",[32,13369,13370],{},"guide.vue",[32,13372,13373],{},"home.vue","\u002F문서가 git 미반영 상태였음 → 본 커밋으로 라이브 ↔ git 일치화.",[76,13376,13378],{"id":13377},"_3-git","3. Git",[81,13380,13381,13386],{},[84,13382,13383,13385],{},[32,13384,12995],{}," 직접 커밋·푸시 (브랜치는 main 단일 운영 — 2026-05-18 사용자 결정 유지).",[84,13387,13009,13388,13390,13391,13393,13394,13396],{},[32,13389,7929],{},"(신규), ",[32,13392,13277],{},"(가이드 링크), ",[32,13395,11891],{},"(§0 현황), 이 history + README 인덱스.",[73,13398],{},[76,13400,13402],{"id":13401},"_4-전역-ui-스케일-115-재배포","4. 전역 UI 스케일 115% + 재배포",[81,13404,13405,13408,13431,13434,13462],{},[84,13406,13407],{},"요청: 전체 폰트 스케일 ~115% 확대.",[84,13409,13410,13412,13413,13416,13417,13419,13420,13423,13424,13427,13428,4703],{},[21,13411,10663],{},": 카드 제목·헤딩·테이블 등 폰트가 px 하드코딩이라 ",[32,13414,13415],{},"--font-base","만 키우면 부분만 커짐. ",[32,13418,4338],{}," 에 ",[32,13421,13422],{},"--ui-scale: 1.15"," 토큰 + ",[32,13425,13426],{},"body { zoom: var(--ui-scale); }"," 한 줄로 텍스트·간격·컴포넌트를 균일 비례 확대(브라우저 줌 효과). sticky GNB·모달·토스트 정상. 복원 = ",[32,13429,13430],{},"--ui-scale: 1",[84,13432,13433],{},"트레이드오프: 폰트만이 아니라 레이아웃·여백도 ~15% 확대(전체 비례). 텍스트-only는 타이포 스케일 재설계 필요(범위 큼) — 보류.",[84,13435,13436,13437,13440,13441],{},"빌드 → ",[32,13438,13439],{},"wrangler pages deploy dist --branch=main"," 재배포.\n",[81,13442,13443,13449],{},[84,13444,13344,13445],{},[26,13446,13447],{"href":13447,"rel":13448},"https:\u002F\u002Fec3368a2.malgn-noti.pages.dev",[30],[84,13450,13451,13452,11734,13455,4420,13458,13461],{},"검증: 프로덕션 CSS 자산(",[32,13453,13454],{},"entry.*.css",[32,13456,13457],{},"ui-scale:1.15",[32,13459,13460],{},"zoom:var(--ui-scale)"," 포함 확인.",[84,13463,13464,13465,13467],{},"변경 파일: ",[32,13466,4352],{}," 단일.",[76,13469,13471],{"id":13470},"_5-디자인-폭-1400px-보정-재배포","5. 디자인 폭 1400px 보정 + 재배포",[81,13473,13474,13477,13490,13506,13525],{},[84,13475,13476],{},"요청: 디자인 너비 1400px.",[84,13478,13479,13480,13482,13483,13485,13486,13489],{},"진단: ",[32,13481,8145],{}," 토큰은 이미 ",[32,13484,8142],{},"였으나 §4의 ",[32,13487,13488],{},"body{zoom:1.15}","가 곱해져 화면 실제 폭 ≈ 1610px.",[84,13491,13492,47,13495,6219,13498,13501,13502,13505],{},[21,13493,13494],{},"수정",[32,13496,13497],{},"--container-max: calc(1400px \u002F var(--ui-scale))",[32,13499,13500],{},"1217.4px × 1.15 = 1400px"," 화면폭. ",[32,13503,13504],{},"--ui-scale"," 변경 시 자동 유지(절대 디자인 폭). 본문·GNB·푸터 일괄.",[84,13507,13436,13508,13510,13511],{},[32,13509,13439],{}," 재배포(3회차).\n",[81,13512,13513,13519],{},[84,13514,13344,13515],{},[26,13516,13517],{"href":13517,"rel":13518},"https:\u002F\u002F3ae53fbd.malgn-noti.pages.dev",[30],[84,13520,13521,13522,13461],{},"검증: 프로덕션 CSS에 ",[32,13523,13524],{},"container-max:calc(1400px\u002Fvar(--ui-scale))",[84,13526,13464,13527,13467],{},[32,13528,4352],{},[76,13530,13532],{"id":13531},"_6-헤더본문-좌우-정렬-버그-수정-재배포","6. 헤더\u002F본문 좌우 정렬 버그 수정 + 재배포",[81,13534,13535,13538,13548,13569,13587],{},[84,13536,13537],{},"증상: sticky 헤더(GNB)가 본문보다 안쪽으로 들어와 좌우 끝이 어긋남.",[84,13539,13540,13543,13544,13547],{},[21,13541,13542],{},"원인",": §4의 ",[32,13545,13546],{},"body { zoom }","이 스크롤바(html\u002Fviewport 기준)·sticky GNB·중앙정렬과 다른 좌표계 → sticky 헤더가 스크롤바 폭만큼 가로 오프셋.",[84,13549,13550,13552,13553,13556,13557,13560,13561,4361,13563,13565,13566,13568],{},[21,13551,13494],{},": zoom을 스크롤 컨테이너인 ",[32,13554,13555],{},"html","로 이동 + ",[32,13558,13559],{},"scrollbar-gutter: stable",". 스크롤바·sticky·",[32,13562,8137],{},[32,13564,11567],{},"\u002F푸터가 동일 좌표계 → 좌우 끝 일치. ",[32,13567,8145],{}," calc·115%·1400px 모두 불변(html zoom 균일).",[84,13570,13436,13571,13573,13574],{},[32,13572,13439],{}," 재배포(4회차).\n",[81,13575,13576,13582],{},[84,13577,13344,13578],{},[26,13579,13580],{"href":13580,"rel":13581},"https:\u002F\u002F2c1a6180.malgn-noti.pages.dev",[30],[84,13583,13521,13584,13461],{},[32,13585,13586],{},"html{scrollbar-gutter:stable;zoom:var(--ui-scale)}",[84,13588,13464,13589,13467],{},[32,13590,4352],{},[76,13592,13594],{"id":13593},"_7-운영-컨벤션-문서화","7. 운영 컨벤션 문서화",[81,13596,13597,13627],{},[84,13598,13599,13601,13602],{},[32,13600,7682],{}," §7.1 \"Git · 배포 · 작업 이력 (운영 컨벤션)\" 신규 — 그간 따라온 워크플로 규약화.\n",[81,13603,13604,13610,13617],{},[84,13605,13606,13607,13609],{},"Git: 단일 ",[32,13608,12995],{}," 운영, 커밋\u002F푸시는 명시 요청 시, Co-Authored-By trailer.",[84,13611,13321,13612,6219,13614,13616],{},[32,13613,11195],{},[32,13615,13439],{},", \"배포\"=빌드→배포→검증→커밋·푸시·history 한 흐름, working tree 기준→배포 후 git 일치.",[84,13618,13619,13620,13622,13623,13626],{},"이력: ",[32,13621,12567],{}," 하루 한 파일, 같은 날 추가는 ",[32,13624,13625],{},"§N","+README 인덱스 갱신.",[84,13628,13464,13629,13631],{},[32,13630,7682],{}," 단일 (문서, 무배포).",[76,13633,13635],{"id":13634},"_8-페이지-타이틀-설명-줄-제거-재배포","8. 페이지 타이틀 설명 줄 제거 + 재배포",[81,13637,13638,13649,13667,13687],{},[84,13639,13640,13641,13644,13645,13648],{},"요청: 페이지 타이틀 영역 3번째 줄(설명 ",[32,13642,13643],{},"\u003Cp>",") 제거 — 범위는 ",[21,13646,13647],{},"페이지 헤더 패턴 전체","(사용자 확인).",[84,13650,13651,13652,51,13654,5439,13657,13659,13660,13663,13664,13666],{},"수정: ",[32,13653,4771],{},[32,13655,13656],{},".page-header p { display: none }",[32,13658,5631],{}," 쓰는 모든 페이지(발송 6종 + 향후) 일괄, 홈\u002F가이드는 다른 헤더라 무영향. 복원 = ",[32,13661,13662],{},"display"," 제거. 마크업 ",[32,13665,13643],{},"는 템플릿 유지(숨김).",[84,13668,13436,13669,13671,13672],{},[32,13670,13439],{}," 재배포(5회차).\n",[81,13673,13674,13680],{},[84,13675,13344,13676],{},[26,13677,13678],{"href":13678,"rel":13679},"https:\u002F\u002F08d1a759.malgn-noti.pages.dev",[30],[84,13681,13682,13683,13686],{},"검증: 프로덕션 CSS ",[32,13684,13685],{},"page-header p{…display:none…}"," 확인.",[84,13688,13464,13689,13467],{},[32,13690,4352],{},[76,13692,13694],{"id":13693},"_9-페이지-설정-묶음-로고정렬푸터채널-칩-재배포","9. 페이지 설정 묶음 (로고·정렬·푸터·채널 칩) + 재배포",[18,13696,13697],{},"사용자 요청을 모아 처리한 일괄 변경:",[81,13699,13700,13722,13761,13770,13798,13808,13832],{},[84,13701,13702,13705,13706,13709,13710,13713,13714,13717,13718,13721],{},[21,13703,13704],{},"로고 이미지화",": 워드마크를 이미지대로 변경 — 다크 r-md 타일 + 화이트 글리프, \"맑은\"(700) + \"message\"(연회색). 아이콘은 lucide 단일로 없어 ",[32,13707,13708],{},"AppLogoMark.vue","(말풍선+스파클 커스텀 SVG, currentColor) 신설, GNB(헤더·드로어)·푸터·인증 일관. 단어 간격 축소(",[32,13711,13712],{},".gnb-logo"," gap 10→7 + ",[32,13715,13716],{},".gnb-logo-sub"," margin-left -5 → ~2px, ",[32,13719,13720],{},".auth-logo"," 8, 푸터 name-row 2).",[84,13723,13724,13727,13728,13731,13732,13735,13736,13739,13740,13742,13743,13746,13747,13750,13751,13754,13755,4339,13758,4703],{},[21,13725,13726],{},"헤더\u002F푸터 ↔ 본문 정렬 근본 수정",": 원인은 zoom\u002F스크롤바가 아니라 ",[21,13729,13730],{},"CSS 단축속성 충돌"," — 페이지 루트 ",[32,13733,13734],{},"class=\"app-container page-body\"","에서 ",[32,13737,13738],{},".page-body { padding: 32px 0 64px }","가 ",[32,13741,8137],{},"의 좌우 패딩(32px)을 0으로 덮어써 본문만 풀블리드였음. ",[32,13744,13745],{},".page-body","를 ",[32,13748,13749],{},"padding-block: 32px 64px","(세로 전용)로 변경. 부수 가드: ",[32,13752,13753],{},"html { overflow-y: scroll }","(스크롤바 폭 고정), ",[32,13756,13757],{},".layout-default { overflow-x: clip }",[32,13759,13760],{},".layout-main { min-width: 0 }",[84,13762,13763,13766,13767,13769],{},[21,13764,13765],{},"푸터 다크",": 배경 ",[32,13768,4812],{},", 텍스트는 흰색 투명도 단계로 재배치. 1차 가독성 미흡 피드백 → 작은 텍스트(11~12px) 대비를 WCAG AA 기준으로 상향(회사명·강조 링크 = 순백, 사업자정보 0.75, 기본 0.72 등).",[84,13771,13772,13775,13776,13779,13780,4361,13782,13739,13784,13787,13788,4361,13791,4361,13794,13797],{},[21,13773,13774],{},"채널 칩 통일",": KAKAO만 박스로 보인 원인은 ",[21,13777,13778],{},"클래스 충돌"," — 미리보기 셸 전역 ",[32,13781,6646],{},[32,13783,6650],{},[32,13785,13786],{},".ch-pill kakao\u002Frcs","에 누수. 셸 컨테이너를 ",[32,13789,13790],{},".phone .imsg",[32,13792,13793],{},".phone .kakao",[32,13795,13796],{},".phone .rcs","로 스코프 → 6채널 모두 SMS·EMAIL과 동일(점+모노 코드).",[84,13799,13800,13803,13804,13807],{},[21,13801,13802],{},"타이틀 실사 이미지 검토",": 디자인 시스템(정보 우선·calm)과 충돌해 비권장. 비교 목업 페이지로 6안 제시 → 사용자 ",[21,13805,13806],{},"현행 유지"," 결정, 목업 페이지 폐기(미커밋).",[84,13809,13436,13810,13812,13813],{},[32,13811,13439],{}," 재배포(6회차).\n",[81,13814,13815,13821],{},[84,13816,13344,13817],{},[26,13818,13819],{"href":13819,"rel":13820},"https:\u002F\u002Fcecb5166.malgn-noti.pages.dev",[30],[84,13822,13682,13823,4420,13826,4420,13829,13686],{},[32,13824,13825],{},".footer{background:var(--ink-900)}",[32,13827,13828],{},".phone .kakao{",[32,13830,13831],{},"page-body{…padding-block}",[84,13833,13464,13834,4473,13836,13390,13838,4473,13840,4473,13842,4703],{},[32,13835,4771],{},[32,13837,13708],{},[32,13839,8210],{},[32,13841,4424],{},[32,13843,13844],{},"layouts\u002F{auth,default}.vue",[76,13846,13848],{"id":13847},"_10-카드-헤더-단계번호-제거타이틀-강조-강조안-목업-배포","10. 카드 헤더 단계번호 제거·타이틀 강조 + 강조안 목업 배포",[81,13850,13851,13858,13878,13889,13906,13933],{},[84,13852,13853,13854,13857],{},"요청: 발신\u002F수신자 등 카드의 ",[32,13855,13856],{},"01·02"," 단계 번호 삭제 + 영역 강조(우선 폰트 키우기).",[84,13859,13860,13861,13735,13863,13866,13867,13870,13871,13874,13875,13877],{},"적용: ",[32,13862,12119],{},[32,13864,13865],{},".step"," 번호 요소 제거(발송 6종 전체, ",[32,13868,13869],{},"step"," prop은 호환 위해 유지·미렌더). ",[32,13872,13873],{},".card-header .title"," 13→15px·600→700·tracking -0.01em, ",[32,13876,6028],{}," padding 14→16px.",[84,13879,13880,13881,13884,13885,13888],{},"강조 추가안 8종(현행\u002FA eyebrow\u002FB accent바\u002FA+B\u002FD dot\u002FF 헤더↔바디 accent 경계선\u002FC 섹션헤더 분리\u002FE 헤더톤) 비교용 ",[32,13882,13883],{},"app\u002Fpages\u002Fdev\u002Fcard-emphasis-mockups.vue"," 생성 — ",[21,13886,13887],{},"사용자 요청으로 배포 포함","(이전 title 목업과 달리 폐기 보류, 결정 시 정리).",[84,13890,13891,47,13894,13897,13898,13901,13902,13905],{},[21,13892,13893],{},"배포 사고·수정",[32,13895,13896],{},"wrangler pages deploy","가 git HEAD 한글 커밋 메시지를 배포 메타로 읽다 ",[32,13899,13900],{},"Invalid commit message, must be valid UTF-8","로 실패. ",[32,13903,13904],{},"--commit-message \"\u003Cascii>\" --commit-dirty=true"," 명시로 해결. CLAUDE.md §7.1 배포 절차에 규약 반영.",[84,13907,13908,13909],{},"빌드 → 재배포(7회차).\n",[81,13910,13911,13917],{},[84,13912,13344,13913],{},[26,13914,13915],{"href":13915,"rel":13916},"https:\u002F\u002F20c0b350.malgn-noti.pages.dev",[30],[84,13918,13919,13920,13923,13924,51,13926,13929,13930,13932],{},"검증: 프로덕션 ",[32,13921,13922],{},"\u002Fdev\u002Fcard-emphasis-mockups"," 200, ",[32,13925,12271],{},[32,13927,13928],{},"class=\"step\""," 0건(번호 제거), ",[32,13931,11278],{}," 200.",[84,13934,13464,13935,4473,13937,4473,13939,13390,13941,13943],{},[32,13936,12119],{},[32,13938,4771],{},[32,13940,13883],{},[32,13942,7682],{}," §7.1.",[76,13945,13947],{"id":13946},"_11-강조안-b-확정-적용-목업-폐기","11. 강조안 B 확정 적용 + 목업 폐기",[81,13949,13950,13957,13973,13982,14003],{},[84,13951,13952,13953,13956],{},"사용자 결정: 강조안 ",[21,13954,13955],{},"B (accent 좌측 바)"," 채택.",[84,13958,13860,13959,11686,13961,5069,13963,13966,13967,5069,13969,13972],{},[32,13960,4479],{},[32,13962,6028],{},[32,13964,13965],{},"card-header--accent"," 클래스 부여 + ",[32,13968,4771],{},[32,13970,13971],{},".card-header--accent { border-left: 3px solid var(--accent); padding-left: 17px }",". AppSendFormCard 한정 스코프 → 발송 6종의 4카드(발신\u002F수신자\u002F메시지\u002F발송옵션)에만 적용, 홈·가이드 카드 무영향.",[84,13974,13975,13976,4339,13978,13981],{},"결정 완료로 ",[32,13977,13883],{},[32,13979,13980],{},"app\u002Fpages\u002Fdev\u002F"," 폐기.",[84,13983,13984,13985],{},"빌드 → 재배포(8회차).\n",[81,13986,13987,13993],{},[84,13988,13344,13989],{},[26,13990,13991],{"href":13991,"rel":13992},"https:\u002F\u002Fe86488b5.malgn-noti.pages.dev",[30],[84,13994,13919,13995,51,13997,13999,14000,14002],{},[32,13996,12271],{},[32,13998,13965],{}," 4건(B 적용), ",[32,14001,13922],{}," 404(목업 제거).",[84,14004,13464,14005,4473,14007,4473,14009,14011],{},[32,14006,12119],{},[32,14008,4771],{},[32,14010,13980],{},"(삭제).",[76,14013,14015],{"id":14014},"_12-헤더-불투명화-수신자-카드-접기펼치기","12. 헤더 불투명화 + 수신자 카드 접기\u002F펼치기",[81,14017,14018,14038,14064,14074,14097],{},[84,14019,14020,14021,14024,14025,4339,14027,14030,14031,14037],{},"헤더: ",[32,14022,14023],{},".gnb-wrap"," 반투명(",[32,14026,5564],{},[32,14028,14029],{},"backdrop-filter blur",") → ",[21,14032,14033,14034],{},"불투명 ",[32,14035,14036],{},"var(--white)",", backdrop-filter 제거(성능).",[84,14039,14040,14041,5069,14043,4361,14045,14048,14049,4536,14052,14054,14055,7086,14058,14060,14061,14063],{},"수신자 카드 접기\u002F펼치기: ",[32,14042,4479],{},[32,14044,7091],{},[32,14046,14047],{},"defaultCollapsed"," prop + chevron 토글(헤더 클릭\u002F버튼, ",[32,14050,14051],{},"aria-expanded",[32,14053,6863],{},", ▾↔▸ 회전, 바디 ",[32,14056,14057],{},"v-show",[32,14059,4482],{},"만 ",[32,14062,7091],{}," 적용 — 발신정보·메시지·발송옵션·홈·가이드 무영향.",[84,14065,14066,14067,14070,14071,14073],{},"아이콘 위치: 사용자 요청으로 헤더 우측 끝 → ",[21,14068,14069],{},"타이틀 바로 옆","으로 이동(hint \"총 N명\"은 기존 ",[32,14072,6262],{},"로 우측 유지).",[84,14075,14076,14077],{},"빌드 → 재배포(9회차).\n",[81,14078,14079,14085],{},[84,14080,13344,14081],{},[26,14082,14083],{"href":14083,"rel":14084},"https:\u002F\u002F7910043f.malgn-noti.pages.dev",[30],[84,14086,13682,14087,14090,14091,51,14093,14096],{},[32,14088,14089],{},".gnb-wrap{background:var(--white)}","(불투명), ",[32,14092,12271],{},[32,14094,14095],{},"card-toggle"," 존재(접기 토글), 200.",[84,14098,13464,14099,4473,14101,4473,14103,4703],{},[32,14100,4771],{},[32,14102,12119],{},[32,14104,14105],{},"AppRecipientCard.vue",[76,14107,14109],{"id":14108},"_13-phase-2b-3-발송조회통계주소록충전인증","13. Phase 2b-3 — 발송조회\u002F통계\u002F주소록\u002F충전\u002F인증",[81,14111,14112,14168,14181,14201,14224],{},[84,14113,13166,14114,14116,14117],{},[32,14115,13169],{}," 6개 페이지군 이식:\n",[81,14118,14119,14132,14138,14144,14156],{},[84,14120,14121,14123,14124,14127,14128,14131],{},[32,14122,6167],{}," 신규 — 발송 조회(4 stat-card+segmented 기간+채널\u002F상태 필터+검색+테이블+페이지네이션). ",[32,14125,14126],{},"\u002Fhistory\u002F{sms,rcs,kakao,email,push}"," 5채널이 ",[32,14129,14130],{},"defaultChannel","만 달리해 공유.",[84,14133,14134,14137],{},[32,14135,14136],{},"history\u002Fstats.vue"," — 4 KPI + 일별 stacked bar + 채널 donut(SVG) + TOP 템플릿 progress. 차트색 = DESIGN §2.4 채널 도트 체계(info\u002Famber\u002Fwarning\u002Faccent\u002Fviolet).",[84,14139,14140,14143],{},[32,14141,14142],{},"contacts\u002Flist.vue"," — 240px 그룹 사이드바 + 필터바 + 연락처 테이블 + 선택 액션.",[84,14145,14146,89,14149,14151,14152,14155],{},[32,14147,14148],{},"charge\u002Findex.vue",[32,14150,5585],{},", 프리셋 5 + 결제수단 3 + 최근내역 \u002F aside ",[32,14153,14154],{},".credit-hero"," + 결제요약.",[84,14157,14158,4536,14161,14164,14165,14167],{},[32,14159,14160],{},"login\u002Findex.vue",[32,14162,14163],{},"signup.vue"," — 기존 ",[32,14166,10164],{}," 레이아웃 셸 안에 카드 내용만 렌더(로그인: 비번토글\u002F유지\u002F찾기, 회원가입: 3-step 스테퍼).",[84,14169,14170,14171,14174,14175,4473,14178,14180],{},"핸드오프 적응: 구 토큰 → ink\u002Faccent\u002Fsemantic+채널색, ",[32,14172,14173],{},"Icon","→",[32,14176,14177],{},"UIcon i-lucide-*",[32,14179,8704],{},"→Nuxt UI, 기존 공용 클래스 재사용.",[84,14182,14183,14184],{},"빌드 → 재배포(10회차).\n",[81,14185,14186,14192],{},[84,14187,13344,14188],{},[26,14189,14190],{"href":14190,"rel":14191},"https:\u002F\u002F08f9446c.malgn-noti.pages.dev",[30],[84,14193,14194,14195,14198,14199,13686],{},"검증: 10개 URL 200, stats ",[32,14196,14197],{},"donut-wrap","·charge ",[32,14200,13360],{},[84,14202,14203,14204,13390,14206,4473,14209,4473,14212,4473,14215,4473,14218,4473,14221,14223],{},"변경: ",[32,14205,6167],{},[32,14207,14208],{},"pages\u002Fhistory\u002F{sms,rcs,kakao,email,push,stats}.vue",[32,14210,14211],{},"pages\u002Fcontacts\u002Flist.vue",[32,14213,14214],{},"pages\u002Fcharge\u002Findex.vue",[32,14216,14217],{},"pages\u002Flogin\u002Findex.vue",[32,14219,14220],{},"pages\u002Fsignup.vue",[32,14222,11891],{}," §0.",[84,14225,14226,14229],{},[21,14227,14228],{},"잔여",": 발신정보·메시지관리·캠페인·계정설정·문의·시스템페이지 — 핸드오프 시안 없음, 디자인 방향 별도 협의 필요.",[76,14231,14233],{"id":14232},"_14-gnb-발송-관리-메뉴-분리","14. GNB \"발송 관리\" 메뉴 분리",[81,14235,14236,14239,14252,14268],{},[84,14237,14238],{},"요청: 단일 \"발송 관리\" 드롭다운을 둘로 분리.",[84,14240,14241,14243,14244,14247,14248,14251],{},[32,14242,8210],{}," MENU_TREE: ",[32,14245,14246],{},"발송 조회\u002F통계","(채널별 발송조회 5 + 구분선 + 통계) \u002F ",[32,14249,14250],{},"주소록","(연락처\u002F그룹\u002F수신거부) 2개 그룹으로 분리. 메뉴는 데이터+CSS 호버 기반이라 인덱스 의존 없음.",[84,14253,14254,14255],{},"빌드 → 재배포(11회차).\n",[81,14256,14257,14263],{},[84,14258,13344,14259],{},[26,14260,14261],{"href":14261,"rel":14262},"https:\u002F\u002F3af5079b.malgn-noti.pages.dev",[30],[84,14264,13919,14265,14267],{},[32,14266,11278],{}," 200, \"발송 조회\u002F통계\"·\"주소록\" 렌더, \"발송 관리\" 0건.",[84,14269,13464,14270,13467],{},[32,14271,8210],{},[76,14273,14275],{"id":14274},"_15-sms-타이틀-변경-수신자-카드-최상단-이동","15. SMS 타이틀 변경 + 수신자 카드 최상단 이동",[81,14277,14278,14290,14296,14318],{},[84,14279,14280,14282,14283,6219,14286,14289],{},[32,14281,4472],{},": H1·문서타이틀 ",[32,14284,14285],{},"SMS 발송",[32,14287,14288],{},"문자메시지 발송"," (브레드크럼은 이미 일치).",[84,14291,14292,14293,14295],{},"발송 6채널(sms\u002Fkakao\u002Frcs\u002Femail\u002Fpush\u002Fflow) 전체: ",[32,14294,4482],{},"를 카드 스택 첫 번째(타이틀 다음)로 이동. 새 순서 = 수신자 → 발신\u002F플로우 → 메시지 → 발송옵션. 사용자 결정: 6채널 전체 적용(알림톡은 잠긴 수신자 카드가 먼저 노출되는 UX 감수).",[84,14297,14298,14299],{},"빌드 → 재배포(12회차).\n",[81,14300,14301,14307],{},[84,14302,13344,14303],{},[26,14304,14305],{"href":14305,"rel":14306},"https:\u002F\u002F5ad321a1.malgn-noti.pages.dev",[30],[84,14308,14309,14310,14313,14314,14317],{},"검증: 소스상 6파일 모두 ",[32,14311,14312],{},"\u003CAppRecipientCard","가 첫 ",[32,14315,14316],{},"\u003CAppSendFormCard","보다 앞, 프로덕션 200.",[84,14319,14203,14320,4703],{},[32,14321,14322],{},"pages\u002Fsend\u002F{sms,kakao,rcs,email,push,flow}.vue",[76,14324,14326],{"id":14325},"_16-알림톡-발신-정보-카드-최상단-복귀","16. 알림톡 발신 정보 카드 최상단 복귀",[81,14328,14329,14332,14338,14358],{},[84,14330,14331],{},"요청: 알림톡 페이지만 발신 정보 카드를 타이틀 다음(첫 카드)으로.",[84,14333,14334,14337],{},[32,14335,14336],{},"pages\u002Fsend\u002Fkakao.vue",": 발신 정보 ↔ 수신자 순서 교체 → 발신 정보 → 수신자 → 메시지 → 옵션. 알림톡은 발신 프로필+템플릿 선택 전 수신자\u002F메시지 잠금(progressive disclosure)이라 발신 정보 선행이 자연스러움. 나머지 5채널은 수신자-우선 유지.",[84,14339,14340,14341],{},"빌드 → 재배포(13회차).\n",[81,14342,14343,14349],{},[84,14344,13344,14345],{},[26,14346,14347],{"href":14347,"rel":14348},"https:\u002F\u002F372eab23.malgn-noti.pages.dev",[30],[84,14350,14351,14352,14354,14355,14357],{},"검증: 소스 ",[32,14353,14316],{},"@85 \u003C ",[32,14356,14312],{},"@120, 프로덕션 200.",[84,14359,14203,14360,13467],{},[32,14361,14336],{},[76,14363,14365],{"id":14364},"_17-발송-6채널-3-카드-재배치-템플릿-다이얼로그-캡처-기준","17. 발송 6채널 3-카드 재배치 + 템플릿 다이얼로그 (캡처 기준)",[18,14367,14368,14369,14372],{},"사용자 제공 캡처 6종 기준으로 발송 페이지를 ",[21,14370,14371],{},"템플릿 선택 \u002F 수신자 설정 \u002F 메시지 설정 3-카드"," 패턴으로 통일:",[81,14374,14375,14388,14393,14398,14406,14414,14422,14430,14444],{},[84,14376,14377,47,14380,14382,14383,51,14385,14387],{},[21,14378,14379],{},"공용 컴포넌트",[32,14381,4479],{}," 접기 토글 → \"닫기\u002F열기\" 텍스트형. ",[32,14384,4482],{},[32,14386,7082],{}," prop 추가(기본 \"수신자\"), 액션 버튼 라벨\u002F스타일 캡처 기준(+ 직접입력\u002F+ 주소록에서 선택=다크, 수신자 정보 수정\u002F삭제=핑크), \"수신자 추가\" 라벨 제거.",[84,14389,14390,14392],{},[21,14391,7119],{},": 3-카드, 발송목적 일반용\u002F인증용\u002F광고용, 발신번호 안내문, 국내\u002F국제 2줄 byte 카운터, 템플릿 행 통일(선택된 템플릿 없음 + 선택), 수신자 초기 공란.",[84,14394,14395,14397],{},[21,14396,7122],{},": 발신 프로필 셀렉트화, 메시지 설정=읽기전용 메타(템플릿코드\u002F카카오톡코드\u002F발송목적\u002F메시지유형\u002F강조유형\u002F내용\u002F보안여부), 프로필 모달 제거.",[84,14399,14400,14402,14403,14405],{},[21,14401,7125],{},": 발신 브랜드+번호 2-셀렉트, 발송유형 3-단, 수신 대기 만료 기한, ",[32,14404,7384],{}," 신규(미리보기 미지원 안내).",[84,14407,14408,14410,14411,14413],{},[21,14409,7128],{},": 발송목적\u002F발신메일\u002F제목(0\u002F1000)\u002F내용\u002F첨부파일(안내 4줄)+인라인 이메일 미리보기(텍스트\u002FHTML 토글), ",[32,14412,7387],{}," 신규(미리보기 포함).",[84,14415,14416,14418,14419,14421],{},[21,14417,7131],{},": 발송목적·입력유형 라디오, HTML 스타일 박스, 배지, 확장 6행(버튼\u002F미디어\u002FAndroid·iOS 미디어\u002F큰 아이콘\u002F그룹), Android·iOS 미리보기 2-up, ",[32,14420,7390],{}," 신규.",[84,14423,14424,14426,14427,14429],{},[21,14425,7134],{},": 안내 2줄, 플로우 이름 셀렉트+생성관리, 발송 순서 chips+메시지 채널 셀렉트, 읽기전용 발신\u002F목적\u002F유형, ",[32,14428,7425],{}," 신규(생성 관리 목록).",[84,14431,14432,14433],{},"빌드 → 재배포(14회차).\n",[81,14434,14435,14441],{},[84,14436,13344,14437],{},[26,14438,14439],{"href":14439,"rel":14440},"https:\u002F\u002F093effc9.malgn-noti.pages.dev",[30],[84,14442,14443],{},"검증: 발송 6채널 + 홈 200, 컴파일\u002F하이드레이션 0.",[84,14445,14446,14447,4361,14449,4361,14451,4361,14453,14455,14456,4536,14458,4339,14460,4703],{},"신규 컴포넌트: ",[32,14448,7384],{},[32,14450,7387],{},[32,14452,7390],{},[32,14454,7425],{},". 변경: ",[32,14457,4479],{},[32,14459,4482],{},[32,14461,14322],{},[73,14463],{},[76,14465,13035],{"id":13034},[81,14467,14468,14473,14478,14483,14497,14502,14507,14513,14523,14529,14538,14541,14544,14547,14550],{},[84,14469,14470,14472],{},[32,14471,7929],{}," (신규, 18섹션 라이브 가이드)",[84,14474,14475,14477],{},[32,14476,13277],{}," (바로가기 5번째: 디자인 가이드)",[84,14479,14480,14482],{},[32,14481,11891],{}," (§0 적용 현황에 가이드 페이지 행 추가)",[84,14484,14485,14487,14488,6078,14491,4473,14494,14496],{},[32,14486,4352],{}," (전역 115% ",[32,14489,14490],{},"html{zoom}",[32,14492,14493],{},"scrollbar-gutter",[32,14495,8145],{}," 1400px 보정, 헤더 정렬 수정)",[84,14498,14499,14501],{},[32,14500,7682],{}," §7.1 운영 컨벤션(Git·배포·이력) 신규",[84,14503,14504,14506],{},[32,14505,13708],{}," 신규(브랜드 마크 SVG) · AppGnb\u002FAppFooter\u002Fauth\u002Fdefault 로고·정렬·푸터 다크",[84,14508,14509,14512],{},[32,14510,14511],{},"doc\u002Fhistory\u002Fhistory.20260519.md"," + README 인덱스",[84,14514,14515,14516,14519,14520,14522],{},"강조안 ",[21,14517,14518],{},"B(accent 좌측 바)"," 확정 적용 → ",[32,14521,13965],{},", 비교 목업 폐기",[84,14524,14525,14526,14528],{},"헤더 불투명화 + 수신자 카드 접기\u002F펼치기(",[32,14527,4479],{}," collapsible)",[84,14530,14531,14534,14535,14537],{},[21,14532,14533],{},"Phase 2b-3"," — 발송조회(",[32,14536,4555],{},")\u002F통계\u002F주소록\u002F충전\u002F로그인\u002F회원가입 6페이지군",[84,14539,14540],{},"GNB \"발송 관리\" → \"발송 조회\u002F통계\" + \"주소록\" 분리",[84,14542,14543],{},"SMS→문자메시지 발송 타이틀 변경 + 발송 6채널 수신자 카드 최상단 이동",[84,14545,14546],{},"알림톡 발신 정보 카드 최상단 복귀(disclosure 정합)",[84,14548,14549],{},"발송 6채널 3-카드 재배치(템플릿 선택\u002F수신자 설정\u002F메시지 설정) + 채널 템플릿 다이얼로그 4종 신규",[84,14551,14552,14553,4343],{},"Cloudflare Pages 프로덕션 배포 ×14 (",[26,14554,10317],{"href":10317,"rel":14555},[30],[76,14557,12598],{"id":13153},[81,14559,14560,14573,14576],{},[84,14561,14562,13161,14564],{},[21,14563,13160],{},[81,14565,14566,14570],{},[84,14567,13166,14568,13170],{},[32,14569,13169],{},[84,14571,14572],{},"발신정보·메시지관리·캠페인은 핸드오프 시안 없음 → 별도 협의.",[84,14574,14575],{},"미적용 화면은 backward-compat 별칭으로 색만 이행(간격·폰트·형태는 구 시안).",[84,14577,14578],{},"GitHub Actions 자동 배포 미구성 — 현재는 wrangler CLI 수동 배포.",{"title":4208,"searchDepth":4209,"depth":4209,"links":14580},[14581,14582,14584,14585,14586,14587,14588,14589,14590,14591,14592,14593,14594,14595,14596,14597,14598,14599,14600,14601],{"id":10635,"depth":4212,"text":10636},{"id":13234,"depth":4212,"text":14583},"1. 디자인 가이드 페이지 (\u002Fguide)",{"id":13290,"depth":4212,"text":13291},{"id":13377,"depth":4212,"text":13378},{"id":13401,"depth":4212,"text":13402},{"id":13470,"depth":4212,"text":13471},{"id":13531,"depth":4212,"text":13532},{"id":13593,"depth":4212,"text":13594},{"id":13634,"depth":4212,"text":13635},{"id":13693,"depth":4212,"text":13694},{"id":13847,"depth":4212,"text":13848},{"id":13946,"depth":4212,"text":13947},{"id":14014,"depth":4212,"text":14015},{"id":14108,"depth":4212,"text":14109},{"id":14232,"depth":4212,"text":14233},{"id":14274,"depth":4212,"text":14275},{"id":14325,"depth":4212,"text":14326},{"id":14364,"depth":4212,"text":14365},{"id":13034,"depth":4212,"text":13035},{"id":13153,"depth":4212,"text":12598},{},"\u002Fhistory\u002Fhistory.20260519",{"title":13216,"description":4208},"history\u002Fhistory.20260519","-WrrlxUI19tRkvihhQBL0qlnQ-2wVZL-cYZxVhXBQXU",{"id":14608,"title":14609,"body":14610,"description":4208,"extension":4257,"meta":16819,"navigation":4259,"path":16820,"seo":16821,"stem":16822,"__hash__":16823},"docs\u002Fhistory\u002Fhistory.20260520.md","2026-05-20 — 발송 페이지 UX 폴리시 2차 + PUSH 부가항목·플로우 관리 완성",{"type":8,"value":14611,"toc":16789},[14612,14615,14617,14644,14648,14753,14757,14822,14826,14870,14874,14892,14896,14958,14962,15079,15083,15196,15200,15246,15250,15287,15291,15357,15361,15431,15435,15440,15525,15529,15537,15685,15689,15834,15838,15942,15946,16055,16059,16167,16171,16276,16280,16351,16355,16443,16445,16449,16512,16516,16587,16589,16670,16673,16753,16757],[11,14613,14609],{"id":14614},"_2026-05-20-발송-페이지-ux-폴리시-2차-push-부가항목플로우-관리-완성",[76,14616,10636],{"id":10635},[18,14618,14619,14620,14622,14623,14625,14626,14628,14629,14631,14632,14635,14636,14639,14640,14643],{},"§17(5\u002F19) 이후 발송 6채널 전반의 UX를 다듬고, PUSH 메시지 설정의 부가 항목(버튼·미디어·Android 미디어·iOS 미디어·Android 큰 아이콘·그룹)을 모두 실 동작 다이얼로그로 구현하고, 복합 플로우의 등록·수정·삭제·이름 클릭 편집까지 한 다이얼로그로 통합. 공용 컴포넌트(이메일 미리보기·다중 키 컬럼 수신자 위젯·중첩 모달 스크롤 잠금)도 다듬어 Cloudflare Pages에 배포 (#15). 이후 문구 정리(발송 옵션→발송 설정, 띄어쓰기, 푸터 이메일 오타)로 재배포 (#16), 5\u002F18 피벗 이후 누적분을 DESIGN\u002FFRONTEND\u002FSTACK\u002FCLAUDE·가이드 페이지에 현행화하여 재배포 (#17). 끝으로 FRONTEND\u002FDESIGN 문서에 남아 있던 stale 매핑(USlideover·구 ",[32,14621,4608],{}," 토큰 예시)을 코드 현실에 맞춰 정정하여 재배포 (#18). 이어서 발송 조회 페이지(",[32,14624,4555],{},")의 목록 영역·검색 필터·다이얼로그를 캡처 기준으로 전면 재작업하고, ",[32,14627,6106],{}," 레거시 클래스를 프로젝트 전역에서 제거(→",[32,14630,5892],{},")하여 재배포 (#19). 이후 통계 페이지를 Chart.js로 재구성하고, ",[32,14633,14634],{},"zoom"," 전역 스케일을 폐기한 뒤 폰트 타입 스케일을 토큰화(",[32,14637,14638],{},"--fz-scale",")하여 +15% 적용, 재배포 (#20). 이어서 발송 6채널의 '템플릿 사용유무' 토글 동작을 개선 — 토글 시 수신자 목록을 항상 유지하고 메시지 설정만 stash\u002F복원하도록 ",[32,14641,14642],{},"useTemplateToggle"," composable로 통일, 재배포 (#21). 이어서 주소록 관리 페이지를 강화 — 등록·일괄등록·그룹이동 모달, 선택 발송 채널 드롭다운, 이름 클릭 수정, 페이지네이션·토큰 컬럼을 추가하여 재배포 (#22). 이어서 발신 정보·발신 번호 관리 페이지를 신규 구성하고, 개인정보 동의 → 등록 방식 선택 → 서류 인증\u002F휴대폰 본인인증 3단계 등록 마법사를 추가했으며, 누적 타입 에러 8건을 정리해 재배포 (#23). 이어서 그룹 관리 페이지를 신규 구성하고(그룹 등록·수정 모달, 행별 메시지 발송 채널 드롭다운, 검색·페이지네이션), 주소록·그룹 관리 툴바를 통일하여 재배포 (#24). 이어서 RCS 브랜드 관리 페이지를 신규 구성하고(RCS Biz Center 연동 흐름, 브랜드 목록 표, 하단 번호형 페이지바·새로고침), 발신 정보 리스트 페이지 구조를 통일하여 재배포 (#25). 끝으로 이메일 도메인 관리 페이지를 신규 구성하고(도메인 등록·DKIM 설정 모달), 재배포 (#26).",[76,14645,14647],{"id":14646},"_1-수신자-입력-다이얼로그-일괄-강화","1. 수신자 입력 다이얼로그 일괄 강화",[81,14649,14650,14682,14715],{},[84,14651,14652,14654],{},[21,14653,7340],{},[81,14655,14656,14666,14673,14679],{},[84,14657,14658,14661,14662,14665],{},[32,14659,14660],{},"keyColumn"," 단일 → ",[32,14663,14664],{},"keyColumns?: ('phone'|'email'|'token')[]"," 다중 지원. 단일 모드는 기존 검증 유지, 다중 모드는 \"최소 한 항목 + 입력된 항목 형식 검사\" 정책.",[84,14667,14668,14669,14672],{},"휴대폰은 하이픈 자동 포맷팅을 다시 제거하고 ",[21,14670,14671],{},"국내 11자리 숫자만 입력"," 으로 단순화(국가코드 칸도 폐기). 안내문 동일 갱신.",[84,14674,14675,14676,5606],{},"이메일 행에 정규식 형식 검증(",[32,14677,14678],{},"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",[84,14680,14681],{},"\"(이름 없음)\" 폴백 등 UX 보정.",[84,14683,14684,14686],{},[21,14685,4482],{},[81,14687,14688,14701],{},[84,14689,14690,14693,14694,7505,14697,14700],{},[32,14691,14692],{},"keyColumns?:"," prop 추가 → 표 헤더·셀이 N개 컬럼으로 확장 (",[32,14695,14696],{},"headOf",[32,14698,14699],{},"valOf"," 도우미).",[84,14702,14703,14706,14707,14710,14711,14714],{},[21,14704,14705],{},"별칭 클릭 → 수정 다이얼로그"," 오픈(",[32,14708,14709],{},"\u003Cbutton class=\"rcp-name-btn\">",", accent-ink + bold + hover underline). ",[32,14712,14713],{},"수신자 정보 수정"," 버튼은 6채널 모두에서 제거.",[84,14716,14717,14719],{},[21,14718,7346],{},[81,14720,14721,14730,14736],{},[84,14722,14723,14725,14726,14729],{},[32,14724,14692],{}," 동일 지원, valOf에 ",[32,14727,14728],{},"token"," 분기 추가.",[84,14731,14732,14733,14735],{},"개별 샘플 8명에 FCM\u002FAPNS 형태 ",[32,14734,14728],{}," 필드 부여 + 그룹 합성 수신자에도 토큰 포함 → PUSH 페이지 토큰 열 의미화.",[84,14737,14738,14739,6219,14742,14745,14746,4339,14749,14752],{},"행 클릭 시 체크박스 더블 토글로 체크가 누락되던 버그 수정: ",[32,14740,14741],{},"\u003Clabel class=\"checkbox\">",[32,14743,14744],{},"\u003Cspan class=\"checkbox\">","(라벨 포워딩 제거), input은 ",[32,14747,14748],{},"pointer-events: none",[32,14750,14751],{},"tabindex=\"-1\""," 표시 전용으로 분리.",[76,14754,14756],{"id":14755},"_2-sms-발송-페이지","2. SMS 발송 페이지",[81,14758,14759,14771,14780,14787,14800,14816],{},[84,14760,14761,14763,14764,5069,14767,14770],{},[21,14762,4485],{},": 단순 리스트 → \"샘플 템플릿 선택\" 카드 그리드(검색 + 단문\u002F장문\u002F포토 탭 + 우측 미리보기). SMS 8종(봄 인사) + LMS 2종 + MMS 3종 시드. ",[32,14765,14766],{},"Tpl",[32,14768,14769],{},"images?"," 추가.",[84,14772,14773,47,14776,14779],{},[21,14774,14775],{},"MMS 템플릿 선택 시 자동 첨부 + 수정 불가",[32,14777,14778],{},"attachLocked"," computed로 칩 회색 처리 + 자물쇠 아이콘 + \"(수정 불가)\" 안내, 삭제 ×·이미지 선택 버튼 숨김.",[84,14781,14782,47,14784,14786],{},[21,14783,7358],{},[32,14785,14769],{}," prop 추가 → 발송 페이지 미리보기에도 첨부 이미지가 그대로 표시(아이콘+파일명+KB 카드).",[84,14788,14789,14792,14793,4339,14796,14799],{},[21,14790,14791],{},"이미지 첨부 실제 파일 픽커",": 숨김 ",[32,14794,14795],{},"\u003Cinput type=\"file\" accept=\".jpg,.jpeg,image\u002Fjpeg\" multiple>",[32,14797,14798],{},"onPickImages()"," 검증(JPG\u002FJPEG · 3장 · 1장 ≤300KB · 합산 ≤800KB).",[84,14801,14802,47,14805,14808,14809,4339,14812,14815],{},[21,14803,14804],{},"템플릿 사용유무 토글 시 페이지 내용 초기화",[32,14806,14807],{},"resetContent()"," 추출 + watcher(",[32,14810,14811],{},"flush: 'sync'",[32,14813,14814],{},"suppressTplReset"," 가드) → 전체 초기화 다이얼로그도 같은 함수를 재사용.",[84,14817,14818,14821],{},[21,14819,14820],{},"광고용 자동 (광고) 접두 강제",": purpose \u002F subject 두 개 watcher가 idempotent하게 부착·해제.",[76,14823,14825],{"id":14824},"_3-이메일-발송-페이지","3. 이메일 발송 페이지",[81,14827,14828,14844,14855,14867],{},[84,14829,14830,14833,14834,14836,14837,14840,14841,14843],{},[21,14831,14832],{},"공용 컴포넌트 추출",": 다이얼로그 미리보기와 발송 페이지 미리보기가 구조적으로 달랐던 문제를 ",[32,14835,4509],{},"(제목·보낸사람·첨부·헤딩·본문 카드·버튼·HTML\u002F텍스트 토글)로 통합. ",[32,14838,14839],{},"EmailTpl","을 ",[32,14842,12983],{},"로 이동, 다이얼로그\u002F페이지 모두 같은 컴포넌트 사용.",[84,14845,14846,5069,14848,7505,14851,14854],{},[32,14847,14839],{},[32,14849,14850],{},"heading",[32,14852,14853],{},"buttonLabel"," 까지 전달되어 페이지 미리보기에 즉시 반영.",[84,14856,14857,14858,6219,14861,14866],{},"미리보기 컬럼 너비 ",[32,14859,14860],{},"320px",[21,14862,14863],{},[32,14864,14865],{},"460px"," 확대.",[84,14868,14869],{},"광고용 자동 (광고) 접두 강제 동일 적용.",[76,14871,14873],{"id":14872},"_4-알림톡-발송-페이지","4. 알림톡 발송 페이지",[81,14875,14876,14882],{},[84,14877,14878,14879,14881],{},"수신자 카드 잠금 게이팅(",[32,14880,7095],{},") 제거 → 다른 채널과 동일 동작.",[84,14883,14884,14885,14887,14888,14891],{},"메시지 설정 카드도 ",[32,14886,7095],{}," 제거 + ",[32,14889,14890],{},"v-if=\"template\""," 게이팅 해제 → 템플릿 미선택 시에도 각 항목 표시 + 값 공란, 내용 textarea placeholder 안내.",[76,14893,14895],{"id":14894},"_5-rcs-발송-페이지","5. RCS 발송 페이지",[81,14897,14898,14925,14940,14949],{},[84,14899,14900,14901],{},"발송 유형 셀렉트 옵션 캡처 반영\n",[81,14902,14903,14914],{},[84,14904,14905,14906,6219,14909],{},"2번째: ",[32,14907,14908],{},"스탠드얼론 \u002F 템플릿",[21,14910,14911],{},[32,14912,14913],{},"스탠드얼론 \u002F 대화형",[84,14915,14916,14917,6219,14920],{},"3번째: ",[32,14918,14919],{},"SMS \u002F LMS \u002F 대체 없음",[21,14921,14922],{},[32,14923,14924],{},"SMS \u002F 통합 SMS",[84,14926,14927,14928,6219,14931,14936,14937,5606],{},"수신 대기 만료 기한: ",[32,14929,14930],{},"1시간\u002F6시간\u002F24시간\u002F3일",[21,14932,14933],{},[32,14934,14935],{},"40초\u002F3분\u002F1시간\u002F24시간"," (기본 ",[32,14938,14939],{},"24h",[84,14941,14942,14945,14946,4703],{},[32,14943,14944],{},"RcsTpl"," 공용 타입 + 9개 비디오팩 템플릿에 발송 목적·유형 3종·내용·이미지·버튼·만료 기한 시드 → 템플릿 선택 시 ",[21,14947,14948],{},"메시지 설정 자동 채움",[84,14950,14951,14952,7505,14954,14957],{},"템플릿 사용유무 토글 → SMS와 동일한 ",[32,14953,14807],{},[32,14955,14956],{},"handleReset()"," \u002F suppress watcher 패턴.",[76,14959,14961],{"id":14960},"_6-push-발송-페이지-부가-항목-완성","6. PUSH 발송 페이지 — 부가 항목 완성",[81,14963,14964,14977,15001,15069,15072],{},[84,14965,14966,47,14968,14970,14971,4361,14973,14976],{},[21,14967,4512],{},[32,14969,7371],{}," prop 추가 → 두 디바이스를 별도 잠금화면 레이아웃으로 렌더(상태바\u002F시계\u002F날짜\u002F알림 카드\u002F하단 버튼). 콘텐츠(",[32,14972,7082],{},[32,14974,14975],{},"body",")는 항상 동일 바인딩.",[84,14978,14979,14981,14982,14985,14986,14989,14990,14993,14994,14996,14997,15000],{},[21,14980,7350],{},"(수신자 직접 입력): 타이틀 \"수신자 직접 입력\" \u002F \"수신자 수정\", ",[21,14983,14984],{},"별칭"," 단일 입력 + 토큰 다중 추가(푸시 유형 7종 셀렉트). 수정 모드에서는 ",[32,14987,14988],{},"+ 추가","·푸시 유형 뱃지·× 삭제 모두 숨김. 데이터: ",[32,14991,14992],{},"name","=별칭, ",[32,14995,14728],{},"=토큰, ",[32,14998,14999],{},"vars.pushType","=유형.",[84,15002,15003,15004,15006,15007],{},"메시지 설정 부가 항목 전용 다이얼로그 6종(공용 ",[32,15005,7401],{},"를 prop으로 분기):\n",[81,15008,15009,15018,15061],{},[84,15010,15011,15013,15014,15017],{},[21,15012,7398],{},": 유형 4종(응답\u002F앱 열기\u002FURL 열기\u002F닫기). 칩 = ",[32,15015,15016],{},"유형 ✏ ×","(연필 클릭 시 수정 다이얼로그 재오픈)",[84,15019,15020,15022,15023,7505,15025,7505,15027,7505,15029,15031,15032],{},[21,15021,7401],{}," (재사용): ",[32,15024,7082],{},[32,15026,7407],{},[32,15028,7410],{},[32,15030,7413],{}," prop으로 다음 4행을 한 컴포넌트로 처리\n",[81,15033,15034,15043,15049,15055],{},[84,15035,15036,15039,15040],{},[21,15037,15038],{},"미디어",": URL · 유형 4종 · 펼치기 (사용\u002F사용 안함). 칩 ",[32,15041,15042],{},"유형 · URL ×",[84,15044,15045,15048],{},[21,15046,15047],{},"Android 미디어",": URL · 유형(이미지만) · 펼치기 → 단일 옵션 자동 선택",[84,15050,15051,15054],{},[21,15052,15053],{},"iOS 미디어",": URL · 유형 4종 (펼치기 없음)",[84,15056,15057,15060],{},[21,15058,15059],{},"Android 큰 아이콘",": URL만 (유형\u002F펼치기 없음), 타이틀 \"Android 큰 아이콘\"",[84,15062,15063,15065,15066],{},[21,15064,7417],{},": 키 + 설명. 칩 ",[32,15067,15068],{},"key ✏ ×",[84,15070,15071],{},"광고용 자동 (광고) 접두 강제(pushType + title)",[84,15073,15074,15075,15078],{},"주소록 다이얼로그를 ",[32,15076,15077],{},"key-column=\"token\"","으로 전환 → 토큰 컬럼 노출",[76,15080,15082],{"id":15081},"_7-복합-플로우-페이지-등록수정관리-통합","7. 복합 플로우 페이지 — 등록\u002F수정\u002F관리 통합",[81,15084,15085,15100,15121,15130,15143,15148],{},[84,15086,15087,15088,15091,15092,15095,15096,15099],{},"초기 로드 시 ",[21,15089,15090],{},"플로우 미선택"," 상태로 시작. select에 placeholder ",[32,15093,15094],{},"\u003Coption value=\"\">플로우를 선택하세요\u003C\u002Foption>"," 추가, 메시지 설정 각 항목은 표시·값 공란, 발신\u002F제목\u002F내용 disabled, 발송 버튼 ",[32,15097,15098],{},"!selectedFlow"," 시 비활성.",[84,15101,15102,15103,15106,15107,15110,15111,4536,15114,14840,15117,15120],{},"수신자 설정도 다른 채널과 동일하게 빈 상태 시작 — ",[32,15104,15105],{},"varKeys"," computed로 노드 본문에서 ",[32,15108,15109],{},"#{...}"," 동적 수집, ",[32,15112,15113],{},":show-vars",[32,15115,15116],{},":show-substitution",[32,15118,15119],{},"varKeys.length > 0","으로 게이팅. 플로우 전환 시 recipients\u002FselectedRcpt\u002FcommonVars\u002FsubstitutionMode 초기화.",[84,15122,15123,89,15126,15129],{},[21,15124,15125],{},"수신자 휴대폰 + 이메일 동시 입력 + 표 동시 표시",[32,15127,15128],{},":key-columns=\"['phone', 'email']\"","(card\u002Fdialog\u002Faddress book 모두).",[84,15131,15132,15134,15135,15138,15139,15142],{},[21,15133,7429],{},": 등록·수정 겸용 (",[32,15136,15137],{},"edit?: FlowDraft | null"," prop \u002F ",[32,15140,15141],{},"isEdit"," computed로 타이틀·확인 버튼 라벨 분기). 폼 카드 2개(\"플로우 발송 정보\" + \"플로우 설정\"), 채널 옵션 4종(SMS\u002F알림톡\u002F이메일\u002FPUSH) + 채널 변경 시 템플릿 자동 비움 + HTML5 드래그 앤 드롭 순서 변경 + 첫 행 채널 미선택은 placeholder.",[84,15144,15145,15147],{},[21,15146,7433],{},"(신규): \"선택\" 버튼 → 채널별 템플릿 라디오 선택(검색 + 커스텀 라디오). 채널별 목업 템플릿(SMS 3·알림톡 3·이메일 1·PUSH 1).",[84,15149,15150,15152,15153,15155,15156,4536,15159,4536,15162,15165,15166,4536,15169,15172,15173,15176,15177,4339,15180,15183,15184,15187,15188,15191,15192,15195],{},[21,15151,7425],{}," 리팩토링: ",[32,15154,7134],{}," 인터페이스를 raw(",[32,15157,15158],{},"purpose",[32,15160,15161],{},"mode",[32,15163,15164],{},"channels: { id, ch, template }[]",")로 정규화, 표시는 ",[32,15167,15168],{},"purposeLabel",[32,15170,15171],{},"channelsLabel"," 도우미. ",[32,15174,15175],{},"openCreate"," 단일 상태 → ",[32,15178,15179],{},"openFlowDialog",[32,15181,15182],{},"editingFlow: FlowDraft | null"," 공용. ",[21,15185,15186],{},"플로우 이름 클릭 → 수정 다이얼로그",", 별도 \"플로우 수정\" 버튼은 제거. \"플로우 생성\" → ",[21,15189,15190],{},"\"플로우 등록\"",", \"플로우 생성 관리\" → ",[21,15193,15194],{},"\"플로우 관리\""," 라벨\u002F타이틀 통일.",[76,15197,15199],{"id":15198},"_8-appmodal-스크롤-잠금-견고화","8. AppModal — 스크롤 잠금 견고화",[81,15201,15202,15205,15236],{},[84,15203,15204],{},"모달 열 때 본 페이지가 맨 위로 튀던 문제, 그리고 살짝 위로 어긋나던 잔여 문제까지 해결.",[84,15206,15207,51,15210,15213,15214,15217,15218,15221,15222,15225,15226,14840,15229,15232,15233,15235],{},[21,15208,15209],{},"scrollLock 공용 유틸",[32,15211,15212],{},"app\u002Futils\u002FscrollLock.ts"," 신설: 모듈 수준 카운터 + ",[32,15215,15216],{},"savedY","로 ",[21,15219,15220],{},"중첩 모달","에서도 최초 잠금만 body 변경, 마지막 해제 시에만 복원. ",[32,15223,15224],{},"html { zoom: var(--ui-scale) }"," 보정 — ",[32,15227,15228],{},"body.top",[32,15230,15231],{},"savedY \u002F zoom","으로 나눠 실제 시각 오프셋이 정확히 ",[32,15234,15216],{},"가 되도록 함.",[84,15237,15238,15239,15241,15242,15245],{},"AppModal은 인스턴스별 ",[32,15240,12951],{}," 가드로 중복 lock\u002Funlock을 방지하고, ",[32,15243,15244],{},"onBeforeUnmount","에서도 안전 해제.",[76,15247,15249],{"id":15248},"_9-배포커밋이력","9. 배포·커밋·이력",[81,15251,15252,15260,15274,15284],{},[84,15253,15254,6219,15256,15259],{},[32,15255,11195],{},[32,15257,15258],{},"npx -y wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"send-page UX polish 2nd batch + PUSH extension dialogs + flow mgmt\""," 1회 (배포 #15)",[84,15261,15262,15263,13923,15266,15269,15270,15273],{},"프로덕션 검증: ",[32,15264,15265],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsend\u002Fpush",[32,15267,15268],{},"\u002Fsend\u002Fflow"," 200, alias ",[32,15271,15272],{},"https:\u002F\u002Fc4b53baf.malgn-noti.pages.dev\u002Fsend\u002Fpush"," 200",[84,15275,15276,15277,15280,15281,15283],{},"커밋: ",[32,15278,15279],{},"bd7e07e 발송 페이지 UX 폴리시 2차 + PUSH 부가항목·플로우 관리 완성"," (25 files changed, 2355+\u002F447-) → ",[32,15282,12610],{}," 푸시",[84,15285,15286],{},"Cloudflare Pages 자동 배포가 추가로 트리거되었을 수 있음(working tree 기준 wrangler 직접 배포본이 라이브)",[76,15288,15290],{"id":15289},"_10-문구-정리-재배포-10-배포-16","10. 문구 정리 + 재배포 (§10, 배포 #16)",[81,15292,15293,15301,15320,15330,15340,15348],{},[84,15294,15295,47,15298,15300],{},[21,15296,15297],{},"발송 옵션 → 발송 설정",[32,15299,7324],{}," 카드 타이틀 변경 → 6채널 공용 컴포넌트라 한 곳 수정으로 전 발송 페이지 반영.",[84,15302,15303,47,15306,6219,15309,15312,15313,6219,15316,15319],{},[21,15304,15305],{},"띄어쓰기 교정",[32,15307,15308],{},"사용 안함",[32,15310,15311],{},"사용 안 함","(템플릿 사용유무·HTML 스타일 라디오, 5곳), ",[32,15314,15315],{},"직접입력",[32,15317,15318],{},"직접 입력","(AppRecipientCard·AppRecipientActions·DESIGN.md).",[84,15321,15322,47,15325,6219,15327,15329],{},[21,15323,15324],{},"푸터 이메일 오타",[32,15326,11781],{},[32,15328,3775],{}," (AppFooter).",[84,15331,13321,15332,6219,15334,6685,15336,15339],{},[32,15333,11195],{},[32,15335,13896],{},[32,15337,15338],{},"--commit-message \"wording fixes: send option label, spacing, footer email typo\"",") — 배포 #16.",[84,15341,15262,15342,15269,15345,13932],{},[32,15343,15344],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsend\u002Fsms",[32,15346,15347],{},"https:\u002F\u002Fe22f7472.malgn-noti.pages.dev\u002Fsend\u002Fsms",[84,15349,15276,15350,15353,15354,15356],{},[32,15351,15352],{},"704a1b4 문구 정리: 발송 설정 라벨 변경 + 띄어쓰기 + 푸터 이메일 오타"," (10 files, +12 −12) → ",[32,15355,12610],{}," 푸시.",[76,15358,15360],{"id":15359},"_11-문서디자인-가이드-현행화-재배포-11-배포-17","11. 문서·디자인 가이드 현행화 + 재배포 (§11, 배포 #17)",[81,15362,15363,15366,15375,15389,15394,15402,15407,15423],{},[84,15364,15365],{},"5\u002F18 디자인 피벗 이후 누적된 변경을 문서에 반영.",[84,15367,15368,15370,15371,15374],{},[21,15369,7689],{},": §0 적용 현황(Phase 1·2 완료), §6 \"Phase 2 재작업 예정\" 제거, §12 발송 아키텍처를 ",[21,15372,15373],{},"5-카드 → 3+1 카드 골격","(템플릿 선택\u002F수신자 설정\u002F메시지 설정\u002F발송 설정)으로 재작성, §12.3 핵심 컴포넌트 목록 전면 갱신, §14에 5\u002F19·5\u002F20 이력 추가.",[84,15376,15377,15379,15380,15383,15384,4536,15386,15388],{},[21,15378,4296],{},": 디자인 정본을 DESIGN.md로 명시, §3 디자인 시스템을 ink\u002Faccent 토큰·1400px·",[32,15381,15382],{},"zoom 1.15"," 기준으로 전면 재작성(구 indigo\u002FNoto Sans KR\u002F1200px 폐기), §9.1 GNB를 56px·7개 메뉴로 정정, §12의 시안 base.css 섹션 카탈로그 폐기 → ",[32,15385,4457],{},[32,15387,4771],{}," 안내.",[84,15390,15391,15393],{},[21,15392,4301],{},": 폰트 행을 Inter\u002FJetBrains Mono\u002FPretendard로 갱신.",[84,15395,15396,15398,15399,15401],{},[21,15397,7682],{},": §4 UI 패턴을 실제 ",[32,15400,4476],{}," 기반 공용 컴포넌트로 정정(USlideover·존재하지 않는 컴포넌트 제거), §7 \"(계획)\" 표기 삭제, §8 네이밍 예시 실존 컴포넌트로 교체, §2 pnpm 확정, §10 TODO 정리.",[84,15403,15404,15406],{},[21,15405,13370],{},": §16 \"5-카드 골격\" → \"발송 카드 골격\" 섹션을 실제 3+1 카드 구조·매트릭스로 갱신.",[84,15408,15409,15410,6685,15412,15415,15416,15419,15420,13932],{},"배포 #17: ",[32,15411,13896],{},[32,15413,15414],{},"--commit-message \"docs sync: ...\"","), 프로덕션 ",[32,15417,15418],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fguide"," 200 \u002F alias ",[32,15421,15422],{},"https:\u002F\u002Fc9760142.malgn-noti.pages.dev",[84,15424,15276,15425,15428,15429,15356],{},[32,15426,15427],{},"75ab98c 문서·디자인 가이드 현행화 (2026-05-18~20 반영)"," (5 files, +114 −140) → ",[32,15430,12610],{},[76,15432,15434],{"id":15433},"_12-문서-stale-매핑-정정-재배포-12-배포-18","12. 문서 stale 매핑 정정 + 재배포 (§12, 배포 #18)",[18,15436,15437,15439],{},[32,15438,11502],{}," 문서 일독 검수에서 5\u002F18 피벗 이후에도 갱신되지 않은 매핑 2건을 발견해 코드 현실에 맞춰 정정.",[81,15441,15442,15461,15486,15492,15500,15511,15519],{},[84,15443,15444,15447,15448,15450,15451,9651,15453,15455,15456,6071,15458,15460],{},[21,15445,15446],{},"USlideover 매핑 불일치",": FRONTEND.md §7과 DESIGN.md §6.4가 패널형 팝업·상세\u002F편집을 ",[32,15449,6124],{},"로 매핑했으나, 실제로는 모든 팝업이 자체 ",[32,15452,4476],{},[32,15454,6120],{},"이고 ",[32,15457,6124],{},[32,15459,8210],{},"의 모바일 GNB 드로어 전용. CLAUDE.md §4(\"USlideover 사용하지 않음\")와 어긋나 정정.",[84,15462,15463,15469,15470,15472,15473,15475,15476,4361,15478,15480,15481,4361,15483,15485],{},[21,15464,15465,15466,15468],{},"구 ",[32,15467,4608],{}," 토큰 예시",": FRONTEND.md §5.1·§5.2의 자체 CSS 예시가 폐기된 ",[32,15471,4608],{}," 별칭·시안 base.css 1:1 차용을 권장. ",[32,15474,4608],{},"는 main.css에 backward-compat용으로만 남아 있고 신규 코드는 ",[32,15477,8838],{},[32,15479,4977],{}," 직접 사용이 정본 → §5.2를 \"디자인 토큰 사용\"으로 재작성, AppGnb 예시도 ",[32,15482,14036],{},[32,15484,8810],{},"로 교체.",[84,15487,13284,15488,15491],{},[32,15489,15490],{},"history.20260520.md","는 잘리지 않은 완전한 파일임을 확인(142줄 정상 종료) — Read 출력 끝 혼동이었음.",[84,15493,13436,15494,6685,15496,15499],{},[32,15495,13896],{},[32,15497,15498],{},"--commit-message \"docs sync: fix stale USlideover mapping and gray token examples\"",") — 배포 #18.",[84,15501,15262,15502,4536,15505,15269,15507,15510],{},[32,15503,15504],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fhome",[32,15506,4457],{},[32,15508,15509],{},"https:\u002F\u002F3f68045a.malgn-noti.pages.dev\u002Fhome"," 200. (문서만 변경이라 라이브 산출물은 #17과 동일.)",[84,15512,15276,15513,15516,15517,15356],{},[32,15514,15515],{},"f81424b 문서 정정: USlideover 매핑·구 토큰 예시 현행화"," (2 files, +11 −9) → ",[32,15518,12610],{},[84,15520,13464,15521,4473,15523,4703],{},[32,15522,11907],{},[32,15524,11891],{},[76,15526,15528],{"id":15527},"_13-발송-조회-페이지-전면-재작업-btn-sky-제거-13-배포-19","13. 발송 조회 페이지 전면 재작업 + btn-sky 제거 (§13, 배포 #19)",[18,15530,15531,15532,89,15534,15536],{},"사용자 제공 캡처 기준으로 발송 조회(",[32,15533,4555],{},[32,15535,14126],{}," 5채널 공용)의 목록 영역·검색 필터·다이얼로그를 단계적으로 재작업.",[81,15538,15539,15553,15563,15576,15603,15625,15655,15663,15677],{},[84,15540,15541,15544,15545,15548,15549,15552],{},[21,15542,15543],{},"목록 영역 재구성",": 컬럼을 ",[32,15546,15547],{},"메시지 아이디 \u002F 메시지 채널 \u002F 요청 일시 \u002F 발송 시점 \u002F 발신 정보 \u002F 발송 상태 \u002F 발송 목적 \u002F 수신자 정보 \u002F 수신 상태","로 교체. 카드 상단 툴바(선택 취소·일괄 취소·조회 필드 추가 설정·검색 결과 다운로드 요청·다운로드 요청 목록·총 N건), 카드 하단 페이지네이션(",[32,15550,15551],{},"«‹ 1…N ›»","). 목업 데이터·생성기 신규.",[84,15554,15555,15558,15559,15562],{},[21,15556,15557],{},"조회 필드 추가 설정",": 체크박스 다중선택 드롭다운(바깥클릭\u002FEsc 닫힘). 체크 시 ",[32,15560,15561],{},"예약\u002F발송\u002F수신 일시·템플릿 이름·플로우 이름"," 컬럼이 발송 목적↔수신자 정보 사이에 동적 삽입.",[84,15564,15565,15568,15569,15571,15572,15575],{},[21,15566,15567],{},"검색 필터",": 시안 카드형 → 처음의 가로 바(",[32,15570,6190],{},") 스타일로 회귀(사용자 피드백). 필드 라벨 제거 + 셀렉트 기본 옵션을 필드명으로 표시해 한 줄 배치. 발송 상태(9종)·발송 시점·수신 상태·발송 목적 셀렉트 + 요청 일시 날짜 범위 + 조회\u002F초기화. ",[32,15573,15574],{},"조회"," 클릭 시 적용(draft→applied). 메시지 채널 필터는 페이지 고정이라 UI 제외.",[84,15577,15578,15580,15581,15590,15591,15594,15595,15598,15599,15602],{},[21,15579,6243],{},": 미사용 상태였던 컴포넌트를 정비해 요청 일시에 적용. ",[21,15582,15583,6078,15586,15589],{},[32,15584,15585],{},"html{zoom:1.15}",[32,15587,15588],{},"UPopover","(floating-ui JS 위치계산) 충돌로 팝오버가 어긋나던 문제","를 CSS ",[32,15592,15593],{},"position:absolute"," 앵커로 해결. 24시간제(시 00–23)·1분 단위(분 00–59), 시·분은 네이티브 ",[32,15596,15597],{},"\u003Cselect>","(텔레포트 회피), 트리거는 ",[32,15600,15601],{},".input"," 클래스로 셀렉트와 높이(36px) 정렬.",[84,15604,15605,15608,15609,15612,15613,4536,15615,15617,15618,15620,15621,15624],{},[21,15606,15607],{},"다이얼로그 4종",": 다운로드 요청 확인 \u002F 다운로드 요청 목록(신규 ",[32,15610,15611],{},"AppExportListDialog",") \u002F 일괄 취소 \u002F 선택 취소 — 모두 ",[32,15614,6117],{},[32,15616,4476],{}," 기반. ",[32,15619,6117],{}," 본문에 ",[32,15622,15623],{},"white-space: pre-line"," 추가(2단락 메시지). 선택 취소·일괄 취소는 발송 대기·예약 건의 발송 취소 기능(선택 취소=체크 행 \u002F 일괄 취소=검색 결과 전체).",[84,15626,15627,15630,15631,15633,15634,15636,15637,15639,15640,11686,15642,15644,15645,15648,15649,15651,15652,15654],{},[21,15628,15629],{},"btn-sky 전역 제거",": 레거시 ",[32,15632,6106],{},"(정의가 ",[32,15635,5892],{},"와 동일)를 컴포넌트·페이지 21개에서 ",[32,15638,5892],{},"로 교체, ",[32,15641,4771],{},[32,15643,6106],{}," 정의·",[32,15646,15647],{},".modal-footer"," 오버라이드 삭제, ",[32,15650,13370],{}," 카탈로그 중복 예시 제거, ",[32,15653,7689],{}," §6.4 갱신. 시각 변화 없음(클래스명 통일).",[84,15656,13436,15657,6685,15659,15662],{},[32,15658,13896],{},[32,15660,15661],{},"--commit-message \"history list area rebuild, search filter, dialogs, btn-sky cleanup\"",") — 배포 #19.",[84,15664,15262,15665,4536,15668,15269,15670,13923,15673,15676],{},[32,15666,15667],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fhistory\u002Fsms",[32,15669,11278],{},[32,15671,15672],{},"https:\u002F\u002F77a6d8df.malgn-noti.pages.dev",[32,15674,15675],{},"fb-select"," 마커 확인.",[84,15678,15276,15679,15682,15683,15356],{},[32,15680,15681],{},"d0efe8c 발송 조회 페이지 목록·검색 필터·다이얼로그 전면 작업 + btn-sky 정리"," (28 files, +867 −191) → ",[32,15684,12610],{},[76,15686,15688],{"id":15687},"_14-통계-페이지-재구성-폰트-토큰화-zoom-제거-14-배포-20","14. 통계 페이지 재구성 + 폰트 토큰화 + zoom 제거 (§14, 배포 #20)",[81,15690,15691,15718,15749,15785,15790,15807,15826],{},[84,15692,15693,15699,15700,15707,15708,4536,15711,15714,15715,15717],{},[21,15694,15695,15696,15698],{},"통계 페이지(",[32,15697,14136],{},") 전면 재구성",": 기존 KPI·스택바·도넛 → 검색 필터(한 줄 가로 바) + 차트 + 데이터 테이블 3-카드. ",[21,15701,15702,15703,15706],{},"Chart.js(",[32,15704,15705],{},"chart.js@4.5.1",") 도입"," — CLAUDE.md·STACK.md가 지정한 차트 라이브러리(\"예정\"→실도입). 7종 상태(요청·요청취소·발송·발송실패·수신·수신실패·실발송) 막대 그래프 + 합계 행 테이블, 차트·표가 단일 소스(",[32,15709,15710],{},"STAT_ROWS",[32,15712,15713],{},"SERIES",")에서 파생. 기간 프리셋(오늘\u002F최근 7·30일) 선택 시 날짜 자동 설정 + 역방향 동기화. 헤더는 발송 페이지와 동일한 ",[32,15716,5631],{},"로 통일.",[84,15719,15720,47,15725,15727,15728,15730,15731,15733,15734,15736,15737,15739,15740,15742,15743,15745,15746,15748],{},[21,15721,15722,15724],{},[32,15723,14634],{}," 전역 스케일 폐기",[32,15726,15224],{},"(1.15)가 좌표계 어긋남으로 팝오버(",[32,15729,15588],{},")·정렬 버그를 유발 → ",[32,15732,13504],{}," 토큰·",[32,15735,8145],{}," 보정 calc·",[32,15738,7575],{},"의 zoom 보정 모두 제거. 네이티브 100% 렌더로 전환. (",[32,15741,6243],{},"도 같은 원인이라 ",[32,15744,15588],{},"→CSS ",[32,15747,15593],{}," 앵커로 사전 수정.)",[84,15750,15751,15754,15755,15757,15758,15761,15762,15765,15766,15769,15770,15773,15774,4361,15777,15780,15781,15784],{},[21,15752,15753],{},"폰트 타입 스케일 토큰화",": zoom 없이 전역 폰트 확대를 위해 ",[32,15756,14638],{}," 단일 노브 + ",[32,15759,15760],{},"--fz-2xs~5xl"," 토큰 도입(",[32,15763,15764],{},"calc(기준px × --fz-scale)","). 하드코딩 ",[32,15767,15768],{},"font-size: Npx","·인라인 ",[32,15771,15772],{},"text-[Npx]"," 약 460곳을 ",[32,15775,15776],{},"var(--fz-*)",[32,15778,15779],{},"text-[length:var(--fz-*)]","로 일괄 치환(sed). ",[32,15782,15783],{},"--fz-scale: 1.15","로 전역 +15% 적용 — 이후 스케일 조정은 한 줄.",[84,15786,15787,15789],{},[21,15788,6243],{},": 미사용이던 컴포넌트를 정비해 요청 일시 날짜 범위에 적용(24시간제·1분 단위). 발송 조회·통계 필터 공용.",[84,15791,15792,15793,15796,15797,15800,15801,14174,15804,5606],{},"잡정리: 발송 조회 페이지 ",[32,15794,15795],{},"CSV 다운로드"," 버튼 제거, ",[32,15798,15799],{},".table th"," 폰트 크기 상향(",[32,15802,15803],{},"--fz-2xs",[32,15805,15806],{},"--fz-sm",[84,15808,13436,15809,15811,15812,4536,15815,4536,15818,15269,15820,13923,15823,15676],{},[32,15810,13896],{}," — 배포 #20. 프로덕션 ",[32,15813,15814],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fhistory\u002Fstats",[32,15816,15817],{},"\u002Fhistory\u002Fsms",[32,15819,11278],{},[32,15821,15822],{},"https:\u002F\u002F95f36a35.malgn-noti.pages.dev",[32,15824,15825],{},"fz-scale",[84,15827,15276,15828,15831,15832,15356],{},[32,15829,15830],{},"6bc05c6 통계 페이지 재구성 + 폰트 토큰화 + zoom 스케일 제거"," (53 files, +664 −637) → ",[32,15833,12610],{},[76,15835,15837],{"id":15836},"_15-발송-페이지-템플릿-토글-동작-개선-15-배포-21","15. 발송 페이지 템플릿 토글 동작 개선 (§15, 배포 #21)",[81,15839,15840,15852,15872,15885,15902,15914,15919,15934],{},[84,15841,15842,15847,15848,15851],{},[21,15843,15844,15846],{},[32,15845,14642],{}," composable 신규",": 발송 페이지의 \"템플릿 사용유무\" 토글 동작을 한 곳에 정의. off→on(사용)은 현재 off 모드 메시지 설정을 스냅샷으로 보관하고 메시지+템플릿만 초기화, on→off(사용 안 함)는 템플릿을 해제하고 보관해 둔 off 모드 설정을 복원. ",[32,15849,15850],{},"setSilently","로 전체 초기화 시 watch 억제.",[84,15853,15854,15857,15858,15860,15861,4361,15864,15867,15868,15871],{},[21,15855,15856],{},"수신자 목록 항상 유지",": 기존 sms·rcs는 토글 시 ",[32,15859,14807],{},"로 수신자(",[32,15862,15863],{},"recipients",[32,15865,15866],{},"selectedRcpt",")까지 전부 날렸음 → ",[32,15869,15870],{},"resetMessage()","(메시지+템플릿만)로 분리. 수신자는 어느 페이지·어느 전환에서도 초기화하지 않음.",[84,15873,15874,47,15877,15880,15881,15884],{},[21,15875,15876],{},"치환자 표시\u002F숨김만",[32,15878,15879],{},"commonVars"," 값은 보존하고 표시 여부만 computed(",[32,15882,15883],{},"showSubst"," 등)가 제어. 템플릿으로 치환자가 추가되면 칸이 나타나고 빠지면 숨겨지되 값은 살아 있음.",[84,15886,15887,15890,15891,15893,15894,15897,15898,15901],{},[21,15888,15889],{},"email·push",": 토글 watch가 아예 없던 상태 → ",[32,15892,14642],{}," 신설. ",[32,15895,15896],{},"handleReset","도 정식 구현(기존엔 ",[32,15899,15900],{},"recipients=[]","만 비웠음).",[84,15903,15904,47,15906,15909,15910,15913],{},[21,15905,12977],{},[32,15907,15908],{},"watch(flowName)","에서 수신자·치환자 초기화 라인 제거 — 플로우를 바꿔도 수신자 유지. 기존 타입 에러(",[32,15911,15912],{},"nodes[0]"," undefined 접근) 4건도 함께 정리.",[84,15915,15916,15918],{},[21,15917,12961],{},": \"템플릿 사용유무\" 토글이 없는 구조(항상 사전 승인 템플릿 기반)라 변경 없음.",[84,15920,13436,15921,6685,15923,15926,15927,15930,15931,13932],{},[32,15922,13896],{},[32,15924,15925],{},"--commit-message \"send pages: keep recipients on template toggle, stash and restore message settings\"",") — 배포 #21. 프로덕션 ",[32,15928,15929],{},"\u002Fsend\u002F{sms,kakao,rcs,email,push,flow}"," 전부 200, alias ",[32,15932,15933],{},"https:\u002F\u002Fe1b4d7da.malgn-noti.pages.dev",[84,15935,15276,15936,15939,15940,15356],{},[32,15937,15938],{},"93411ae 발송 페이지 템플릿 토글 동작 개선 — 수신자 유지 + 메시지 stash\u002F복원"," (6 files, +345 −46) → ",[32,15941,12610],{},[76,15943,15945],{"id":15944},"_16-주소록-관리-페이지-강화-16-배포-22","16. 주소록 관리 페이지 강화 (§16, 배포 #22)",[81,15947,15948,15953,15972,15988,16001,16007,16017,16031,16047],{},[84,15949,15950,15952],{},[21,15951,10137],{},": '연락처 관리' → '주소록 관리'.",[84,15954,15955,15960,15961,15963,15964,15967,15968,15971],{},[21,15956,15957,15958,4343],{},"주소록 페이지(",[32,15959,14142],{},": 타이틀 '주소록 그룹\u002F연락처' → '주소록 관리', 발송 페이지와 동일한 ",[32,15962,5631],{},"로 통일. 테이블 하단 페이지네이션 추가, 'CSV 가져오기' 제거, ",[21,15965,15966],{},"토큰 컬럼","(있음 \u002F ",[32,15969,15970],{},"-",") 추가.",[84,15973,15974,15977,15978,13746,15981,15984,15985,15987],{},[21,15975,15976],{},"그룹 이동 모달",": 선택한 연락처를 다른 그룹으로 일괄 이동. ",[32,15979,15980],{},"DATA",[32,15982,15983],{},"reactive","로, 그룹 카운트를 ",[32,15986,8657],{},"로 전환해 이동 즉시 좌측 인원수 갱신.",[84,15989,15990,15993,15994,15997,15998,16000],{},[21,15991,15992],{},"선택 발송 → 5채널 드롭다운",": 선택 발송 클릭 시 문자메시지\u002F알림톡·친구톡\u002FRCS\u002F이메일\u002FPUSH 드롭다운, 채널 선택 시 선택 연락처를 ",[32,15995,15996],{},"useState('sendRecipients')","로 인계해 해당 채널 발송 페이지로 이동. 발송 6채널이 ",[32,15999,8663],{},"에서 인계 수신자를 반영(sms는 §15 이전 적용, kakao\u002Frcs\u002Femail\u002Fpush 추가).",[84,16002,16003,16006],{},[21,16004,16005],{},"AppContactFormDialog","(신규): 주소록 등록\u002F수정 겸용 모달 — 별칭(64자)·휴대폰·이메일, 토큰 입력 패널(푸시 유형·국가\u002F언어 코드·시간대·수신 거부 3종·디바이스 ID, 칩으로 다중), 그룹 다중 선택(최대 16). 이름 클릭 시 수정 모드로 오픈.",[84,16008,16009,16012,16013,16016],{},[21,16010,16011],{},"AppContactBulkDialog","(신규): 주소록 일괄 등록 모달 — 템플릿 다운로드 + ",[32,16014,16015],{},".xlsx"," 파일 업로드(최대 1MB).",[84,16018,16019,16022,16023,16026,16027,16030],{},[21,16020,16021],{},"배포 범위 분리",": 배포 시점 working tree에 별개의 '발신번호 등록' 진행 중 작업이 섞여 있어, 사용자 확인 후 해당 변경을 ",[32,16024,16025],{},"git stash","로 임시 분리하고 ",[21,16028,16029],{},"주소록 작업만"," 빌드·배포·커밋한 뒤 stash 복원.",[84,16032,13436,16033,6685,16035,16038,16039,4536,16042,15269,16044,13932],{},[32,16034,13896],{},[32,16036,16037],{},"--commit-message \"address book: register\u002Fbulk dialogs, group move, channel send dropdown\"",") — 배포 #22. 프로덕션 ",[32,16040,16041],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fcontacts\u002Flist",[32,16043,11278],{},[32,16045,16046],{},"https:\u002F\u002F57bab931.malgn-noti.pages.dev",[84,16048,15276,16049,16052,16053,15356],{},[32,16050,16051],{},"547dd61 주소록 관리 페이지 강화 — 등록·일괄등록·그룹이동·채널 발송"," (5 files, +839 −41) → ",[32,16054,12610],{},[76,16056,16058],{"id":16057},"_17-발신-번호-관리-페이지-등록-마법사-17-배포-23","17. 발신 번호 관리 페이지 + 등록 마법사 (§17, 배포 #23)",[81,16060,16061,16072,16078,16092,16105,16143,16159],{},[84,16062,16063,16068,16069,16071],{},[21,16064,16065,16066,4343],{},"발신 정보 · 발신 번호 페이지(",[32,16067,6412],{},": 시안 IA를 Relay-inspired 디자인에 맞춰 신규 구성 — 명의자 인증 안내 박스, ",[32,16070,6441],{},"(페이지 헤더 우측 배치)·삭제·등록 안내 툴바, 표(발신 번호 유형·번호·승인 상태 배지·요청\u002F승인 일시), 페이지네이션.",[84,16073,16074,16077],{},[21,16075,16076],{},"등록 안내 모달",": 발신 번호 유형별 필요 서류를 표로 안내(대표자·임직원·타사·타인 번호).",[84,16079,16080,16083,16084,16087,16088,16091],{},[21,16081,16082],{},"AppSenderRegisterDialog","(신규): 3단계 등록 마법사. ① 개인정보 수집 이용 동의(체크 후 진행) → ② 등록 방식 선택(직접 등록·서류 인증 \u002F 휴대폰 본인인증) → ③ 분기 — 서류 인증(유형 select·번호·필요 서류 파일 업로드 → ",[32,16085,16086],{},"심사 중"," 등록) 또는 휴대폰 본인인증(통신사·이름·주민번호·내외국인·휴대폰, 인증번호 3분 카운트다운 → ",[32,16089,16090],{},"승인"," 즉시 등록). 단계 인디케이터·검증별 버튼 활성화 포함.",[84,16093,16094,16097,16098,4536,16101,16104],{},[21,16095,16096],{},"types\u002Fsender.ts","(신규): ",[32,16099,16100],{},"SenderNumber",[32,16102,16103],{},"SenderRegisterResult"," 공용 타입 분리.",[84,16106,16107,16110,16111,4536,16114,4536,16117,16120,16121,16123,16124,16127,16128,16131,16132,6100,16134,6078,16136,16139,16140,16142],{},[21,16108,16109],{},"타입 에러 정리",": 누적 타입 에러 8건 해소 — 템플릿 다이얼로그 4종의 배열 인덱스 접근 가드(",[32,16112,16113],{},"?? null",[32,16115,16116],{},"?? ''",[32,16118,16119],{},"?? []","), ",[32,16122,7350],{},"의 수신자 ",[32,16125,16126],{},"vars"," 타입 명시, ",[32,16129,16130],{},"KakaoMessageBody","를 재작성된 ",[32,16133,4521],{},[32,16135,14975],{},[32,16137,16138],{},"modelValue"," API)에 정합. ",[32,16141,10425],{}," 클린.",[84,16144,13436,16145,6685,16147,16150,16151,4536,16154,15269,16156,13932],{},[32,16146,13896],{},[32,16148,16149],{},"--commit-message \"sender numbers page: 3-step register wizard ...\"",") — 배포 #23. 프로덕션 ",[32,16152,16153],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsender\u002Fnumbers",[32,16155,12271],{},[32,16157,16158],{},"https:\u002F\u002F3c26af5f.malgn-noti.pages.dev",[84,16160,15276,16161,16164,16165,15356],{},[32,16162,16163],{},"fe58e2d 발신 번호 관리 페이지 — 등록 마법사 3단계 + 타입 에러 정리"," (9 files, +1182 −12) → ",[32,16166,12610],{},[76,16168,16170],{"id":16169},"_18-그룹-관리-페이지-주소록그룹-툴바-통일-18-배포-24","18. 그룹 관리 페이지 + 주소록·그룹 툴바 통일 (§18, 배포 #24)",[81,16172,16173,16182,16199,16211,16222,16249,16268],{},[84,16174,16175,16181],{},[21,16176,16177,16178,4343],{},"그룹 관리 페이지(",[32,16179,16180],{},"contacts\u002Fgroups.vue",": placeholder → 신규 구성. 표(그룹 이름·그룹 아이디·주소록 수·등록 일시·메시지 발송), 그룹 이름 검색, 페이지네이션.",[84,16183,16184,47,16187,16190,16191,16194,16195,16198],{},[21,16185,16186],{},"그룹 등록\u002F수정",[32,16188,16189],{},"그룹 등록"," 버튼(헤더 우측)·그룹 이름 클릭 → ",[32,16192,16193],{},"AppGroupFormDialog","(신규, 등록·수정 겸용 — ",[32,16196,16197],{},"edit"," prop으로 제목·문구 분기). 등록 시 목록 상단 추가, 수정 시 이름 갱신.",[84,16200,16201,47,16204,16207,16208,16210],{},[21,16202,16203],{},"행별 메시지 발송 드롭다운",[32,16205,16206],{},"메시지 발송"," 컬럼의 버튼 클릭 → 5채널(문자메시지\u002F알림톡·친구톡\u002FRCS\u002F이메일\u002FPUSH) 드롭다운. 채널 선택 시 그룹의 주소록 수만큼 목업 수신자를 생성해 ",[32,16209,15996],{},"로 인계 → 해당 발송 페이지 수신자 설정에 표시. 주소록 수 0이면 안내 토스트.",[84,16212,16213,16216,16217,16219,16220,5606],{},[21,16214,16215],{},"그룹 삭제",": 선택 → ",[32,16218,6117],{},"(위험) → 일괄 삭제(",[32,16221,15983],{},[84,16223,16224,16227,16228,4339,16231,16233,16234,16237,16238,16241,16242,16245,16246,16248],{},[21,16225,16226],{},"툴바 통일",": 주소록 관리·그룹 관리 툴바를 동일 형식으로 — 검색 입력이 바를 채우고, 선택 시 ",[32,16229,16230],{},"N개\u002F명 선택됨",[32,16232,6437],{}," 노출, ",[32,16235,16236],{},"새로고침","(ghost) + ",[32,16239,16240],{},"총 N개\u002F명",". 주소록 관리의 '삭제'→'선택 삭제', '연락처 수'→'주소록 수' 문구 정리, ",[32,16243,16244],{},"새 그룹"," 버튼 → ",[32,16247,16193],{}," 연결.",[84,16250,13436,16251,6685,16253,16256,16257,4536,16260,4536,16263,15269,16265,13932],{},[32,16252,13896],{},[32,16254,16255],{},"--commit-message \"contacts: group management page, group register\u002Fedit dialog, toolbar polish\"",") — 배포 #24. 프로덕션 ",[32,16258,16259],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fcontacts\u002Fgroups",[32,16261,16262],{},"\u002Fcontacts\u002Flist",[32,16264,11278],{},[32,16266,16267],{},"https:\u002F\u002F8a830019.malgn-noti.pages.dev",[84,16269,15276,16270,16273,16274,15356],{},[32,16271,16272],{},"4e88e1f 주소록·그룹 관리 페이지 보강"," (3 files, +525 −7) → ",[32,16275,12610],{},[76,16277,16279],{"id":16278},"_19-rcs-브랜드-관리-페이지-19-배포-25","19. RCS 브랜드 관리 페이지 (§19, 배포 #25)",[81,16281,16282,16291,16297,16308,16326,16343],{},[84,16283,16284,16290],{},[21,16285,16286,16287,4343],{},"발신 정보 · RCS 브랜드 페이지(",[32,16288,16289],{},"sender\u002Fbrands.vue",": placeholder → 신규 구성. 발신 번호 관리 페이지와 동일한 구조(페이지 헤더 우측 액션 버튼 + 안내 박스 + 리스트 카드).",[84,16292,16293,16296],{},[21,16294,16295],{},"안내 박스",": RCS Biz Center 가입·대행사 지정·사업자등록번호 기준 연동·정보 변경 시 재연동 4-bullet. \"RCS Biz Center\"는 외부 링크.",[84,16298,16299,47,16302,16304,16305,16307],{},[21,16300,16301],{},"브랜드 연동",[32,16303,16301],{}," 버튼(헤더 우측) → ",[32,16306,6117],{},"(사업자등록번호 기준 연동 안내) → 확인 시 연동 일시 갱신 + 토스트. 직접 등록 폼이 아니라 RCS Biz Center sync 흐름.",[84,16309,16310,16313,16314,4420,16317,4420,16319,16321,16322,16325],{},[21,16311,16312],{},"리스트 카드",": 툴바(",[32,16315,16316],{},"업체명",[32,16318,16236],{},[32,16320,6487],{},") + 표(브랜드 이름·아이디·승인 상태 배지·승인\u002F연동 일시) + 하단 번호형 페이지바(",[32,16323,16324],{},"« ‹ 1 › »",") — 발신 번호 관리 페이지와 동일 포맷. 검색은 미포함.",[84,16327,13436,16328,6685,16330,16333,16334,4536,16337,15269,16340,13932],{},[32,16329,13896],{},[32,16331,16332],{},"--commit-message \"rcs brand management page: sync flow, brand list table, bottom pager, refresh\"",") — 배포 #25. 프로덕션 ",[32,16335,16336],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsender\u002Fbrands",[32,16338,16339],{},"\u002Fsender\u002Fnumbers",[32,16341,16342],{},"https:\u002F\u002F6f271361.malgn-noti.pages.dev",[84,16344,15276,16345,16348,16349,15356],{},[32,16346,16347],{},"e3e7a02 RCS 브랜드 관리 페이지 — 연동 흐름 + 브랜드 목록"," (1 file, +277 −5) → ",[32,16350,12610],{},[76,16352,16354],{"id":16353},"_20-이메일-도메인-관리-페이지-20-배포-26","20. 이메일 도메인 관리 페이지 (§20, 배포 #26)",[81,16356,16357,16369,16374,16389,16402,16419,16435],{},[84,16358,16359,16364,16365,16368],{},[21,16360,16361,16362,4343],{},"발신 정보 · 이메일 도메인 페이지(",[32,16363,6470],{},": placeholder → 신규 구성. 발신 번호·RCS 브랜드 관리 페이지와 동일 구조(헤더 우측 ",[32,16366,16367],{},"도메인 등록"," + 안내 박스 + 리스트 카드 + 번호형 페이지바).",[84,16370,16371,16373],{},[21,16372,16295],{},": 도메인 소유권 인증 \u002F 인증 후 SPF·DMARC·DKIM 설정 가능 2-bullet.",[84,16375,16376,16313,16378,16381,16382,4420,16384,4420,16386,16388],{},[21,16377,16312],{},[32,16379,16380],{},"DKIM 설정","(검색 앞, 1개 선택 시 활성) · 검색란 · 선택 시 ",[32,16383,6437],{},[32,16385,16236],{},[32,16387,6487],{},") + 표(도메인·소유 인증 상태·인증 일시). 그룹 관리 페이지 툴바 패턴(검색 채움 + 선택 시 액션) 적용.",[84,16390,16391,16394,16395,16397,16398,16401],{},[21,16392,16393],{},"AppDomainRegisterDialog","(신규): 루트 도메인 입력 + ",[32,16396,7844],{}," 버튼(형식 검증) → 검증 전 ",[32,16399,16400],{},"확인"," 비활성, \"사용 가능한 도메인입니다.\" 안내.",[84,16403,16404,16407,16408,16120,16411,16414,16415,16418],{},[21,16405,16406],{},"AppDkimSettingsDialog","(신규): DKIM 레코드 인증 카드 — 절차 안내, DNS 호스트 이름·TXT 레코드 값(읽기전용 + ",[32,16409,16410],{},"복사",[32,16412,16413],{},"인증"," 버튼 + 성공 표시, ",[32,16416,16417],{},"DKIM 사용 설정"," 토글.",[84,16420,13436,16421,6685,16423,16426,16427,4536,16430,15269,16432,13932],{},[32,16422,13896],{},[32,16424,16425],{},"--commit-message \"email domain management page with domain register and DKIM dialogs\"",") — 배포 #26. 프로덕션 ",[32,16428,16429],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsender\u002Fdomains",[32,16431,11278],{},[32,16433,16434],{},"https:\u002F\u002F298c40ec.malgn-noti.pages.dev",[84,16436,15276,16437,16440,16441,15356],{},[32,16438,16439],{},"46f18f8 이메일 도메인 관리 페이지 신규 구성"," (3 files, +711 −6) → ",[32,16442,12610],{},[76,16444,10916],{"id":10916},[237,16446,16448],{"id":16447},"신규-10","신규 (10)",[81,16450,16451,16457,16463,16469,16475,16481,16487,16492,16499,16506],{},[84,16452,16453],{},[26,16454,16456],{"href":16455},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppFlowCreateDialog.vue","app\u002Fcomponents\u002FAppFlowCreateDialog.vue",[84,16458,16459],{},[26,16460,16462],{"href":16461},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppFlowTemplatePickerDialog.vue","app\u002Fcomponents\u002FAppFlowTemplatePickerDialog.vue",[84,16464,16465],{},[26,16466,16468],{"href":16467},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppPushButtonDialog.vue","app\u002Fcomponents\u002FAppPushButtonDialog.vue",[84,16470,16471],{},[26,16472,16474],{"href":16473},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppPushGroupDialog.vue","app\u002Fcomponents\u002FAppPushGroupDialog.vue",[84,16476,16477],{},[26,16478,16480],{"href":16479},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppPushMediaDialog.vue","app\u002Fcomponents\u002FAppPushMediaDialog.vue",[84,16482,16483],{},[26,16484,16486],{"href":16485},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppPushRecipientDialog.vue","app\u002Fcomponents\u002FAppPushRecipientDialog.vue",[84,16488,16489],{},[26,16490,15212],{"href":16491},"..\u002F..\u002Fapp\u002Futils\u002FscrollLock.ts",[84,16493,16494,16498],{},[26,16495,16497],{"href":16496},"..\u002F..\u002Fapp\u002Fcomposables\u002FuseTemplateToggle.ts","app\u002Fcomposables\u002FuseTemplateToggle.ts"," — §15",[84,16500,16501,16505],{},[26,16502,16504],{"href":16503},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppSenderRegisterDialog.vue","app\u002Fcomponents\u002FAppSenderRegisterDialog.vue"," — §17",[84,16507,16508,16505],{},[26,16509,16511],{"href":16510},"..\u002F..\u002Fapp\u002Ftypes\u002Fsender.ts","app\u002Ftypes\u002Fsender.ts",[237,16513,16515],{"id":16514},"수정-20","수정 (20)",[81,16517,16518,16524,16556,16561,16567,16581],{},[84,16519,16520,16521,4343],{},"6개 발송 페이지(",[32,16522,16523],{},"app\u002Fpages\u002Fsend\u002F{sms,kakao,rcs,email,push,flow}.vue",[84,16525,16526,4473,16529,4473,16532,4473,16535,4473,16538,4473,16541,4473,16543,4473,16546,4473,16549,4473,16551,4473,16553],{},[32,16527,16528],{},"app\u002Fcomponents\u002FAppAddressBookDialog.vue",[32,16530,16531],{},"AppEmailPreview.vue",[32,16533,16534],{},"AppEmailTemplateDialog.vue",[32,16536,16537],{},"AppFlowManageDialog.vue",[32,16539,16540],{},"AppModal.vue",[32,16542,12224],{},[32,16544,16545],{},"AppPushPreview.vue",[32,16547,16548],{},"AppRcsTemplateDialog.vue",[32,16550,14105],{},[32,16552,12095],{},[32,16554,16555],{},"AppSmsTemplateDialog.vue",[84,16557,16558,16560],{},[32,16559,12983],{},"(EmailTpl·RcsTpl 추가)",[84,16562,16563,16566],{},[32,16564,16565],{},"app\u002Fpages\u002Fsender\u002Fnumbers.vue","(§17 발신 번호 관리 페이지 전면 구성)",[84,16568,16569,16570,4473,16573,4473,16576,4473,16579],{},"§17 타입 에러 정리: ",[32,16571,16572],{},"AppKakaoTemplateDialog.vue",[32,16574,16575],{},"AppPushRecipientDialog.vue",[32,16577,16578],{},"AppPushTemplateDialog.vue",[32,16580,12167],{},[84,16582,16583,16586],{},[32,16584,16585],{},"app\u002Fpages\u002Fsender\u002Fbrands.vue","(§19 RCS 브랜드 관리 페이지 전면 구성)",[237,16588,7855],{"id":7855},[81,16590,16591,16602,16609,16615,16622,16628,16634,16640,16646,16652,16658,16664],{},[84,16592,16593,16594,16597,16598],{},"#15 — 프로덕션: ",[26,16595,10317],{"href":10317,"rel":16596},[30]," \u002F Alias: ",[26,16599,16600],{"href":16600,"rel":16601},"https:\u002F\u002Fc4b53baf.malgn-noti.pages.dev",[30],[84,16603,16604,16605],{},"#16 — 문구 정리 \u002F Alias: ",[26,16606,16607],{"href":16607,"rel":16608},"https:\u002F\u002Fe22f7472.malgn-noti.pages.dev",[30],[84,16610,16611,16612],{},"#17 — 문서·가이드 현행화 \u002F Alias: ",[26,16613,15422],{"href":15422,"rel":16614},[30],[84,16616,16617,16618],{},"#18 — 문서 stale 매핑 정정 \u002F Alias: ",[26,16619,16620],{"href":16620,"rel":16621},"https:\u002F\u002F3f68045a.malgn-noti.pages.dev",[30],[84,16623,16624,16625],{},"#19 — 발송 조회 페이지 전면 재작업 + btn-sky 제거 \u002F Alias: ",[26,16626,15672],{"href":15672,"rel":16627},[30],[84,16629,16630,16631],{},"#20 — 통계 페이지 재구성 + 폰트 토큰화 + zoom 제거 \u002F Alias: ",[26,16632,15822],{"href":15822,"rel":16633},[30],[84,16635,16636,16637],{},"#21 — 발송 페이지 템플릿 토글 동작 개선 \u002F Alias: ",[26,16638,15933],{"href":15933,"rel":16639},[30],[84,16641,16642,16643],{},"#22 — 주소록 관리 페이지 강화 \u002F Alias: ",[26,16644,16046],{"href":16046,"rel":16645},[30],[84,16647,16648,16649],{},"#23 — 발신 번호 관리 페이지 + 등록 마법사 \u002F Alias: ",[26,16650,16158],{"href":16158,"rel":16651},[30],[84,16653,16654,16655],{},"#24 — 그룹 관리 페이지 + 주소록·그룹 툴바 통일 \u002F Alias: ",[26,16656,16267],{"href":16267,"rel":16657},[30],[84,16659,16660,16661],{},"#25 — RCS 브랜드 관리 페이지 \u002F Alias: ",[26,16662,16342],{"href":16342,"rel":16663},[30],[84,16665,16666,16667],{},"#26 — 이메일 도메인 관리 페이지 \u002F Alias: ",[26,16668,16434],{"href":16434,"rel":16669},[30],[237,16671,16672],{"id":16672},"커밋",[81,16674,16675,16681,16687,16693,16699,16705,16711,16717,16723,16729,16735,16741,16747],{},[84,16676,16677,16680],{},[32,16678,16679],{},"bd7e07e"," 발송 페이지 UX 폴리시 2차 + PUSH 부가항목·플로우 관리 완성",[84,16682,16683,16686],{},[32,16684,16685],{},"428eeca"," history: 2026-05-20 작업 이력 추가 (배포 #15)",[84,16688,16689,16692],{},[32,16690,16691],{},"704a1b4"," 문구 정리: 발송 설정 라벨 변경 + 띄어쓰기 + 푸터 이메일 오타 (§10, 배포 #16)",[84,16694,16695,16698],{},[32,16696,16697],{},"75ab98c"," 문서·디자인 가이드 현행화 (2026-05-18~20 반영) (§11, 배포 #17)",[84,16700,16701,16704],{},[32,16702,16703],{},"f81424b"," 문서 정정: USlideover 매핑·구 토큰 예시 현행화 (§12, 배포 #18)",[84,16706,16707,16710],{},[32,16708,16709],{},"d0efe8c"," 발송 조회 페이지 목록·검색 필터·다이얼로그 전면 작업 + btn-sky 정리 (§13, 배포 #19)",[84,16712,16713,16716],{},[32,16714,16715],{},"6bc05c6"," 통계 페이지 재구성 + 폰트 토큰화 + zoom 스케일 제거 (§14, 배포 #20)",[84,16718,16719,16722],{},[32,16720,16721],{},"93411ae"," 발송 페이지 템플릿 토글 동작 개선 — 수신자 유지 + 메시지 stash\u002F복원 (§15, 배포 #21)",[84,16724,16725,16728],{},[32,16726,16727],{},"547dd61"," 주소록 관리 페이지 강화 — 등록·일괄등록·그룹이동·채널 발송 (§16, 배포 #22)",[84,16730,16731,16734],{},[32,16732,16733],{},"fe58e2d"," 발신 번호 관리 페이지 — 등록 마법사 3단계 + 타입 에러 정리 (§17, 배포 #23)",[84,16736,16737,16740],{},[32,16738,16739],{},"4e88e1f"," 주소록·그룹 관리 페이지 보강 (§18, 배포 #24)",[84,16742,16743,16746],{},[32,16744,16745],{},"e3e7a02"," RCS 브랜드 관리 페이지 — 연동 흐름 + 브랜드 목록 (§19, 배포 #25)",[84,16748,16749,16752],{},[32,16750,16751],{},"46f18f8"," 이메일 도메인 관리 페이지 신규 구성 (§20, 배포 #26)",[76,16754,16756],{"id":16755},"다음-단계-한계","다음 단계 \u002F 한계",[81,16758,16759,16765,16771,16777,16783],{},[84,16760,16761,16764],{},[21,16762,16763],{},"발신정보·메시지 관리·캠페인·계정\u002F문의·시스템 페이지"," — 디자인 핸드오프 미반영 영역. IA만 있고 핸드오프 기반 디자인 미적용.",[84,16766,16767,16770],{},[21,16768,16769],{},"수정 모드에서 푸시 유형 재선택 UI"," — 현재 수정 다이얼로그에는 토큰 행 안에서 유형 셀렉트가 노출되지 않음(추가가 막혀 있어 그 안에서 유형만 갈아끼우려면 삭제→재추가 흐름이 필요). 한 칸짜리 인라인 유형 셀렉트로 보강 여지 있음.",[84,16772,16773,16776],{},[21,16774,16775],{},"백엔드 연동 부재"," — 모든 다이얼로그 시드 데이터는 목업. NHN API 연동 전이라 저장 후 새로고침하면 휘발됨.",[84,16778,16779,16782],{},[21,16780,16781],{},"드래그 핸들 키보드 접근성"," — AppFlowCreateDialog의 행 순서 변경은 마우스 드래그만 지원. ↑\u002F↓ 화살표 키보드 보조가 필요할 수 있음.",[84,16784,16785,16788],{},[21,16786,16787],{},"AppFlowCreateDialog의 placeholder 채널"," — 새 행 추가 시 ch=\"\"로 시작하지만 선택 버튼 클릭 시 토스트로 가드만 함. 필드 자체에 빨간 외곽선 등 시각 검증을 더할 수 있음.",{"title":4208,"searchDepth":4209,"depth":4209,"links":16790},[16791,16792,16793,16794,16795,16796,16797,16798,16799,16800,16801,16802,16803,16804,16805,16806,16807,16808,16809,16810,16811,16812,16818],{"id":10635,"depth":4212,"text":10636},{"id":14646,"depth":4212,"text":14647},{"id":14755,"depth":4212,"text":14756},{"id":14824,"depth":4212,"text":14825},{"id":14872,"depth":4212,"text":14873},{"id":14894,"depth":4212,"text":14895},{"id":14960,"depth":4212,"text":14961},{"id":15081,"depth":4212,"text":15082},{"id":15198,"depth":4212,"text":15199},{"id":15248,"depth":4212,"text":15249},{"id":15289,"depth":4212,"text":15290},{"id":15359,"depth":4212,"text":15360},{"id":15433,"depth":4212,"text":15434},{"id":15527,"depth":4212,"text":15528},{"id":15687,"depth":4212,"text":15688},{"id":15836,"depth":4212,"text":15837},{"id":15944,"depth":4212,"text":15945},{"id":16057,"depth":4212,"text":16058},{"id":16169,"depth":4212,"text":16170},{"id":16278,"depth":4212,"text":16279},{"id":16353,"depth":4212,"text":16354},{"id":10916,"depth":4212,"text":10916,"children":16813},[16814,16815,16816,16817],{"id":16447,"depth":4209,"text":16448},{"id":16514,"depth":4209,"text":16515},{"id":7855,"depth":4209,"text":7855},{"id":16672,"depth":4209,"text":16672},{"id":16755,"depth":4212,"text":16756},{},"\u002Fhistory\u002Fhistory.20260520",{"title":14609,"description":4208},"history\u002Fhistory.20260520","fiHnIgdOlBNMxSzyNtS08GQzzpwCRsgeQvWr3tHL4Mw",{"id":16825,"title":16826,"body":16827,"description":4208,"extension":4257,"meta":19146,"navigation":4259,"path":19147,"seo":19148,"stem":19149,"__hash__":19150},"docs\u002Fhistory\u002Fhistory.20260521.md","2026-05-21 — 발신 정보 페이지 마무리 + 테이블 스타일 A\u002FB\u002FC 정의",{"type":8,"value":16828,"toc":19109},[16829,16832,16834,16903,16907,16961,16965,16979,16983,16991,16995,17002,17006,17038,17042,17187,17191,17226,17230,17361,17365,17410,17414,17523,17527,17590,17594,17694,17698,17750,17754,17833,17837,17894,17898,17965,17969,18031,18035,18096,18100,18215,18219,18280,18284,18330,18334,18417,18421,18512,18516,18651,18655,18712,18714,18718,18738,18742,18773,18777,18803,18807,18838,18840,18967,18969,19099,19101],[11,16830,16826],{"id":16831},"_2026-05-21-발신-정보-페이지-마무리-테이블-스타일-abc-정의",[76,16833,10636],{"id":10635},[18,16835,16836,16837,16840,16841,16844,16845,16848,16849,16852,16853,16856,16857,16860,16861,16864,16865,16868,16869,16872,16873,16876,16877,16884,16885,16888,16889,16892,16893,16902],{},"발신 정보 카테고리의 남은 페이지 3종을 신규 구성 — 카카오 발신 프로필 관리(등록 마법사·그룹 관리 모달), PUSH 인증 관리(FCM·APNs 인증 설정 섹션), 080 수신 거부 번호 관리(번호 신청·이용 해지) — 하고, 그룹 관리 페이지의 그룹 아이디 컬럼을 정리하여 Cloudflare Pages에 배포 (#27). 이후 ",[21,16838,16839],{},"목록 테이블 스타일을 A\u002FB\u002FC로 정의","하고(별도 필터영역\u002F검색없음\u002F인라인검색), 발송 조회 툴바 재배치 + 발신정보·주소록·그룹 관리 목록을 해당 스타일로 일괄 정리하여 재배포 (#28). GNB 캠페인 메뉴 삭제 (#29). ",[21,16842,16843],{},"수신 거부 관리 페이지 3종","(휴대폰·이메일·토큰)을 신규 구성 — C 테이블 스타일, 등록\u002F일괄 등록 모달 — 하여 배포 (#30). GNB 드롭다운 중복 메뉴명 삭제 (#31). ",[21,16846,16847],{},"메시지 관리 — 문자메시지·알림톡 템플릿 페이지","를 신규 구성 — 카테고리 트리·등록\u002F수정 폼·샘플\u002FAI 모달 — 하여 배포 (#32). 알림톡·RCS·PUSH 미리보기 시각을 현재 시각으로 변경 (#33). ",[21,16850,16851],{},"로그인 페이지를 시안 IA로 재구성","(아이디 라벨·아이디 기억하기·비밀번호 재설정 링크·회원가입 안내 카드)하여 배포 (#34). 비밀번호 재설정 이메일 입력란 너비 보정 (#35). ",[21,16854,16855],{},"메시지 관리 — 이메일 템플릿 페이지","(카테고리 트리·등록\u002F수정 폼·샘플\u002FAI 모달·이메일 미리보기)를 신규 구성하여 배포 (#36). 보안 인증 페이지 안내 문구 수정 + 인증코드 입력란 너비 보정 (#37). ",[21,16858,16859],{},"메시지 관리 — RCS 템플릿 페이지","(카테고리 트리·등록\u002F수정 폼·버튼 8종·샘플\u002FAI 모달)와 사이트맵 페이지를 배포 (#38). 사이트맵 보강 + 캠페인 페이지 삭제 (#39). ",[21,16862,16863],{},"메시지 관리 — PUSH 템플릿 페이지","(카테고리 트리·등록\u002F수정 폼·기본\u002FJSON 입력·샘플\u002FAI 모달·Android\u002FiOS 미리보기)를 신규 구성하여 배포 (#40) — 메시지 관리 5채널 템플릿 페이지 완성. ",[21,16866,16867],{},"회원가입 페이지를 5단계 마법사로 신규 구성","(회원 가입 안내·정보 확인·아이디 등록 및 약관 동의·휴대폰 본인 인증·가입 완료)하여 배포 (#41). ",[21,16870,16871],{},"메시지 관리 — 상세 설정 페이지","(5탭·접이식 설정 섹션·대체 문자 모달)를 신규 구성하여 배포 (#42). 새 비밀번호 설정 페이지 입력란 너비·검증 메시지 보정 (#43). ",[21,16874,16875],{},"메시지 관리 — 랜딩페이지 만들기","(목록·기본형\u002F확장형 등록 폼·미리보기 모달)를 신규 구성하여 배포 (#44). ",[21,16878,16879,16880,16883],{},"문의하기 페이지를 ",[32,16881,16882],{},"\u002Faccount\u002Finquiry"," 경로로 이동","(폼·완료 페이지 + GNB·푸터·사이트맵 링크 갱신)하여 배포 (#45). ",[21,16886,16887],{},"나의 페이지 섹션","(공통 셸 + 좌측 메뉴 9종 라우트·회원 정보 변경·결제 카드 관리·이메일\u002F휴대폰 인증 모달)과 ",[21,16890,16891],{},"크레딧 충전 플로우","(충전 페이지 재구성·결제 컨펌·충전 결과 화면)를 신규 구성하여 배포 (#46). ",[21,16894,16895,16896,14030,16898,16901],{},"malgn-noti-api 루트(",[32,16897,4361],{},[32,16899,16900],{},"\u002Fdoc"," 리다이렉트"," — placeholder JSON 응답 대신 Scalar API 문서로 302 이동시키도록 변경하여 Workers 배포.",[76,16904,16906],{"id":16905},"_1-발신-프로필-관리-페이지","1. 발신 프로필 관리 페이지",[81,16908,16909,16937,16955],{},[84,16910,16911,16914,16915],{},[21,16912,16913],{},"sender\u002Fprofiles.vue",": placeholder → 신규 구성. 발신 번호·RCS 브랜드 관리와 동일한 리스트 구조(헤더 우측 등록 버튼 + 안내 박스 + list-card + 하단 번호형 페이지바).\n",[81,16916,16917,16920,16934],{},[84,16918,16919],{},"안내: 카카오톡 채널 생성 CTA, 토큰 인증 절차, 알림톡 일별 최대 발송량 — 중첩 bullet.",[84,16921,16922,16923,4420,16926,16929,16930,4420,16932,4703],{},"툴바: ",[32,16924,16925],{},"발신 프로필 삭제",[32,16927,16928],{},"발신 프로필 그룹 관리"," · 검색(아이디) \u002F ",[32,16931,16236],{},[32,16933,6487],{},[84,16935,16936],{},"표: 체크박스 · 발신 프로필 아이디 · 발신 키 · 등록 일시 · 토큰 인증 상태(배지) · 발신 프로필 상태(배지).",[84,16938,16939,16942,16943,16946,16947,16950,16951,16954],{},[21,16940,16941],{},"AppProfileRegisterDialog","(신규): 발신 프로필 등록 모달. 아이디(0\u002F16) · 관리자 휴대폰(0\u002F11) · 카테고리 3단 select(대\u002F중\u002F소분류 종속, 21개 대분류 + 종속 트리) · ",[32,16944,16945],{},"토큰 요청"," → 6자리 입력\u002F",[32,16948,16949],{},"토큰 재요청"," 흐름. 모든 필드 + 토큰 6자리 충족 시 ",[32,16952,16953],{},"저장"," 활성.",[84,16956,16957,16960],{},[21,16958,16959],{},"AppProfileGroupDialog","(신규): 발신 프로필 그룹 관리 모달. 그룹 이름 추가, 표(이름·프로필 수·등록 일시·삭제), 빈 상태.",[76,16962,16964],{"id":16963},"_2-push-인증-관리-페이지","2. PUSH 인증 관리 페이지",[81,16966,16967,16973],{},[84,16968,16969,16972],{},[21,16970,16971],{},"sender\u002Fpush-cert.vue",": PUSH 인증 관리 페이지 신규 구성 — FCM · APNs 인증 설정 섹션(서비스 계정 키 등록 등).",[84,16974,16975,16978],{},[21,16976,16977],{},"AppPushCertSection","(신규): 인증 설정 섹션 공용 컴포넌트.",[76,16980,16982],{"id":16981},"_3-080-수신-거부-번호-관리-페이지","3. 080 수신 거부 번호 관리 페이지",[81,16984,16985],{},[84,16986,16987,16990],{},[21,16988,16989],{},"sender\u002Foptout-080.vue",": 080 수신 거부 번호 관리 페이지 신규 구성 — 번호 신청, 이용 해지. 정보통신망법 관련 안내 포함.",[76,16992,16994],{"id":16993},"_4-그룹-관리-페이지-정리","4. 그룹 관리 페이지 정리",[81,16996,16997],{},[84,16998,16999,17001],{},[21,17000,16180],{},": 표에서 그룹 아이디 컬럼 제거(컬럼 정리, colspan 보정).",[76,17003,17005],{"id":17004},"_5-배포커밋","5. 배포·커밋",[81,17007,17008,17016,17030],{},[84,17009,17010,6219,17012,17015],{},[32,17011,11195],{},[32,17013,17014],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"sender info pages: kakao sender profile management, push cert, 080 opt-out\""," — 배포 #27.",[84,17017,15262,17018,4536,17021,4536,17024,15269,17027,13932],{},[32,17019,17020],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsender\u002Fprofiles",[32,17022,17023],{},"\u002Fsender\u002Fpush-cert",[32,17025,17026],{},"\u002Fsender\u002Foptout-080",[32,17028,17029],{},"https:\u002F\u002Fd1c4e2eb.malgn-noti.pages.dev",[84,17031,15276,17032,17035,17036,15356],{},[32,17033,17034],{},"e30da5c 발신 정보 페이지 신규 구성 — 발신 프로필·PUSH 인증·080 수신 거부"," (7 files, +1516 −23) → ",[32,17037,12610],{},[76,17039,17041],{"id":17040},"_6-테이블-스타일-abc-정의-발송-조회-툴바-재배치-6-배포-28","6. 테이블 스타일 A\u002FB\u002FC 정의 + 발송 조회 툴바 재배치 (§6, 배포 #28)",[81,17043,17044,17089,17138,17144,17161,17179],{},[84,17045,17046,6685,17049,17052,17053,17057,17058],{},[21,17047,17048],{},"발송 조회 툴바 정리",[26,17050,6167],{"href":17051},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppHistoryView.vue","·5채널 공용 + ",[26,17054,17056],{"href":17055},"..\u002F..\u002Fapp\u002Fpages\u002Fhistory\u002Fstats.vue","stats.vue","):\n",[81,17059,17060,17067,17077],{},[84,17061,17062,6219,17064,17066],{},[32,17063,15574],{},[32,17065,6272],{},"(아이콘 포함), 통계 페이지 검색 버튼도 아이콘 통일.",[84,17068,17069,17070,17073,17074,4703],{},"액션 영역 좌측 = ",[32,17071,17072],{},"총 N건 · | · 새로고침","(테두리 없는 텍스트형), 우측 = ",[32,17075,17076],{},"목록 다운로드 요청 · 다운로드 요청 목록 · 조회 필드 추가 · 일괄 취소 · 선택 취소",[84,17078,17079,17080,14174,17083,4473,17085,14174,17087,4703],{},"필터 바 날짜칸 폭을 통계 페이지와 동일(190)하게 맞춰 시·분 표시. ",[32,17081,17082],{},"검색 결과 다운로드 요청",[32,17084,6334],{},[32,17086,15557],{},[32,17088,6340],{},[84,17090,17091,6685,17094,17097,17098,17057,17102],{},[21,17092,17093],{},"테이블 스타일 A\u002FB\u002FC 정의",[26,17095,11891],{"href":17096},"..\u002F..\u002Fdoc\u002FDESIGN"," §6.5 + ",[26,17099,17101],{"href":17100},"..\u002F..\u002Fapp\u002Fpages\u002Fguide.vue","\u002Fguide §11",[81,17103,17104,17115,17123,17131],{},[84,17105,17106,17109,17110,17112,17113,4703],{},[21,17107,17108],{},"A"," = 별도 검색(필터) 영역 ",[32,17111,6190],{}," + 액션 영역 — 다중 조건 검색(조회·이력). ref ",[32,17114,4555],{},[84,17116,17117,17120,17121,4703],{},[21,17118,17119],{},"B"," = 별도 필터 영역 없음, 액션 영역만, 검색란 없음 — 소규모 목록. ref ",[32,17122,6594],{},[84,17124,17125,17128,17129,4703],{},[21,17126,17127],{},"C"," = 별도 필터 영역 없음, 액션 영역 안에 인라인 검색란(260px·아이콘 우측·28px) — 단일 검색어 목록. ref ",[32,17130,6599],{},[84,17132,17133,17134,17137],{},"각 테이블에 ",[32,17135,17136],{},"data-table-style=\"a|b|c\""," 마커, 가이드 §11에 A·B·C 시각 예시 목업 추가.",[84,17139,17140,17143],{},[21,17141,17142],{},"목록 페이지 일괄 정리",": 발신 번호(B)·RCS 브랜드(B)·이메일 도메인(C)·발신 프로필(C)·080 수신 거부(C)·주소록 관리(C)·그룹 관리(C)의 테이블 상단을 해당 스타일로 재배치.",[84,17145,17146,17149,17150,17152,17153,17156,17157,17160],{},[21,17147,17148],{},"주소록 관리",": 가입일 우측에 ",[32,17151,16206],{}," 컬럼 추가(행별 채널 드롭다운 — 그룹 관리와 동일, 해당 연락처를 ",[32,17154,17155],{},"sendRecipients","로 인계), 맨 오른쪽 더보기(",[32,17158,17159],{},"⋮",") 컬럼 제거.",[84,17162,17163,17164,6685,17166,15415,17169,4536,17171,4536,17173,15269,17176,13932],{},"배포 #28: ",[32,17165,13896],{},[32,17167,17168],{},"--commit-message \"table styles A\u002FB\u002FC + history toolbar restructure\"",[32,17170,4457],{},[32,17172,15817],{},[32,17174,17175],{},"\u002Fcontacts\u002Fgroups",[32,17177,17178],{},"https:\u002F\u002Fec51b8d0.malgn-noti.pages.dev",[84,17180,15276,17181,17184,17185,15356],{},[32,17182,17183],{},"74943e8 테이블 스타일 A\u002FB\u002FC 정의 + 발송 조회 툴바 재배치"," (11 files, +746 −172) → ",[32,17186,12610],{},[76,17188,17190],{"id":17189},"_7-gnb-캠페인-메뉴-삭제-7-배포-29","7. GNB 캠페인 메뉴 삭제 (§7, 배포 #29)",[81,17192,17193,17206,17219],{},[84,17194,17195,17198,17199,6100,17202,17205],{},[26,17196,8210],{"href":17197},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppGnb.vue"," 상단 메뉴에서 ",[32,17200,17201],{},"캠페인",[32,17203,17204],{},"\u002Fcampaign",") 항목 제거 → 메뉴: 서비스·메시지 발송·발송 조회\u002F통계·주소록·발신 정보·메시지 관리·운영가이드.",[84,17207,17208,17209,6685,17211,15415,17214,15269,17216,13932],{},"배포 #29: ",[32,17210,13896],{},[32,17212,17213],{},"--commit-message \"remove campaign menu from GNB\"",[32,17215,11278],{},[32,17217,17218],{},"https:\u002F\u002Fe23f4a20.malgn-noti.pages.dev",[84,17220,15276,17221,6219,17224,15356],{},[32,17222,17223],{},"d0802e6 GNB에서 캠페인 메뉴 삭제",[32,17225,12610],{},[76,17227,17229],{"id":17228},"_8-수신-거부-관리-페이지-3종-8-배포-30","8. 수신 거부 관리 페이지 3종 (§8, 배포 #30)",[81,17231,17232,17254,17302,17308,17318,17326,17336,17353],{},[84,17233,17234,17237,17238,17242,17243,4420,17247,5439,17251,17253],{},[21,17235,17236],{},"페이지 분리",": 수신 거부 관리를 탭 없이 채널별 독립 페이지 3종으로 구성 — ",[26,17239,17241],{"href":17240},"..\u002F..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout.vue","\u002Fcontacts\u002Foptout","(휴대폰) · ",[26,17244,17246],{"href":17245},"..\u002F..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout-email.vue","\u002Fcontacts\u002Foptout-email",[26,17248,17250],{"href":17249},"..\u002F..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout-token.vue","\u002Fcontacts\u002Foptout-token",[26,17252,11316],{"href":17197}," 주소록 메뉴에 3개 항목 추가.",[84,17255,17256,16097,17259,17262,17263,4339,17265,17267,17268,17271,17272],{},[21,17257,17258],{},"AppOptoutManager",[32,17260,17261],{},"kind","(phone\u002Femail\u002Ftoken) prop으로 단일 화면을 렌더링하는 공용 컴포넌트. C 테이블 스타일(",[32,17264,6298],{},[32,17266,6293],{}," border-bottom + ",[32,17269,17270],{},".list-pager"," border-top).\n",[81,17273,17274,17288,17299],{},[84,17275,17276,17277,17280,17281,4536,17284,17287],{},"안내(",[32,17278,17279],{},".notice",") + 페이지 헤더 우측 ",[32,17282,17283],{},"일괄 등록",[32,17285,17286],{},"등록"," 버튼(토큰은 없음).",[84,17289,17290,17291,17294,17295,17298],{},"툴바 좌: ",[32,17292,17293],{},"총 N개 · | · 080 번호\u002F도메인 선택 · 인라인 검색(260px)"," \u002F 우: ",[32,17296,17297],{},"목록 다운로드 요청 · 다운로드 요청 목록 · 해지","(맨 오른쪽).",[84,17300,17301],{},"표: 휴대폰·이메일 = 체크박스·수신 거부 번호\u002F이메일·등록 일시 \u002F 토큰 = 수신 거부 토큰·수신 거부 항목·등록 일시(체크박스 없음).",[84,17303,17304,17307],{},[21,17305,17306],{},"AppOptoutAddDialog","(신규): 수신 거부 번호\u002F이메일 등록 모달 — 080 번호·도메인 select + 직접 입력(최대 10건, 추가\u002F삭제 서브 테이블).",[84,17309,17310,17313,17314,17317],{},[21,17311,17312],{},"AppOptoutBulkDialog","(신규): 일괄 등록 모달 — 080 번호·도메인 select + ",[32,17315,17316],{},"양식 다운로드","(흰 버튼) + .xlsx 업로드(최대 1MB).",[84,17319,17320,47,17322,17325],{},[21,17321,15611],{},[32,17323,17324],{},"jobs"," prop 추가(기본값은 기존 목업, 수신 거부 페이지는 빈 목록 전달).",[84,17327,17328,47,17330,6219,17333,17335],{},[21,17329,16011],{},[32,17331,17332],{},"템플릿 다운로드",[32,17334,17316],{}," 문구 변경.",[84,17337,17338,17339,6685,17341,15415,17344,4536,17346,4536,17348,15269,17350,4703],{},"배포 #30: ",[32,17340,13896],{},[32,17342,17343],{},"--commit-message \"Add opt-out management pages (phone\u002Femail\u002Ftoken)\"",[32,17345,17241],{},[32,17347,17246],{},[32,17349,17250],{},[32,17351,17352],{},"https:\u002F\u002F81348384.malgn-noti.pages.dev",[84,17354,15276,17355,17358,17359,15356],{},[32,17356,17357],{},"5f4cb47 수신 거부 관리 페이지 3종 신규 구성 (휴대폰·이메일·토큰)"," (9 files, +931 −15) → ",[32,17360,12610],{},[76,17362,17364],{"id":17363},"_9-gnb-드롭다운-중복-메뉴명-삭제-9-배포-31","9. GNB 드롭다운 중복 메뉴명 삭제 (§9, 배포 #31)",[81,17366,17367,17379,17389,17402],{},[84,17368,17369,17371,17372,12760,17375,17378],{},[26,17370,8210],{"href":17197}," 하위 메뉴(드롭다운) 상단에 작은 글씨로 한 번 더 표시되던 GNB 메뉴명(",[32,17373,17374],{},".gnb-dropdown-title",[32,17376,17377],{},"item.title",") 제거 → 드롭다운은 하위 항목만 노출.",[84,17380,17381,17382,17384,17385,17388],{},"진행 중인 메시지 관리 작업분은 ",[32,17383,16025],{},"로 격리하고 ",[21,17386,17387],{},"AppGnb 변경만"," 빌드·배포.",[84,17390,17391,17392,6685,17394,15415,17397,15269,17399,13932],{},"배포 #31: ",[32,17393,13896],{},[32,17395,17396],{},"--commit-message \"GNB dropdown: remove duplicated menu title\"",[32,17398,11278],{},[32,17400,17401],{},"https:\u002F\u002Fb11d8703.malgn-noti.pages.dev",[84,17403,15276,17404,17407,17408,15356],{},[32,17405,17406],{},"0f0d9cc GNB 드롭다운에서 중복 메뉴명 표시 삭제"," (1 file, −3) → ",[32,17409,12610],{},[76,17411,17413],{"id":17412},"_10-메시지-관리-문자메시지알림톡-템플릿-페이지-10-배포-32","10. 메시지 관리 — 문자메시지·알림톡 템플릿 페이지 (§10, 배포 #32)",[81,17415,17416,17442,17460,17484,17500,17515],{},[84,17417,17418,6685,17421,17425,17426,4536,17429,4536,17432,4536,17435,17438,17439,17441],{},[21,17419,17420],{},"문자메시지 템플릿",[26,17422,17424],{"href":17423},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Fsms.vue","\u002Fmanage\u002Fsms","): placeholder → 신규 구성. 카테고리 트리(폴더\u002F파일, 펼침·검색) + 툴바(",[32,17427,17428],{},"카테고리 등록\u002F수정",[32,17430,17431],{},"템플릿 등록\u002F수정",[32,17433,17434],{},"삭제",[32,17436,17437],{},"샘플 템플릿 보기",") + 우측 상세(기본 정보 + 폰 미리보기). ",[32,17440,17431],{},"은 폼 뷰로 전환 — 이름·발신 번호·발송 목적·발송 유형·내용(바이트 카운터).",[84,17443,17444,6685,17447,17451,17452,17455,17456,17459],{},[21,17445,17446],{},"알림톡 템플릿",[26,17448,17450],{"href":17449},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Fkakao.vue","\u002Fmanage\u002Fkakao","): 문자 레이아웃 기반 신규 구성. 트리 + 필터 바(발신 프로필 종류·프로필·템플릿 상태) + 2탭 상세(",[32,17453,17454],{},"기본 정보"," + 카카오 미리보기 \u002F ",[32,17457,17458],{},"카카오톡 템플릿(생성 이력)"," 표). 폼 뷰 — 템플릿\u002F카카오 코드, 발신 프로필(일반·그룹), 메시지 유형 4종, 강조 유형 4종, 내용(0\u002F1300)·부가정보, 보안 템플릿, 카테고리 대\u002F중분류, 대표 링크·버튼 관리.",[84,17461,17462,47,17465,17468,17469,4536,17472,17475,17476,17479,17480,17483],{},[21,17463,17464],{},"신규 컴포넌트",[32,17466,17467],{},"AppTemplateCategoryDialog","(카테고리 등록\u002F수정), ",[32,17470,17471],{},"AppAiTemplateDialog",[32,17473,17474],{},"AppKakaoAiDialog","(채널별 AI 템플릿 생성), ",[32,17477,17478],{},"AppKakaoSampleDialog","(알림톡 샘플 — 유형 탭·강조 칩·카드 그리드), ",[32,17481,17482],{},"AppKakaoButtonDialog","(버튼 추가\u002F수정).",[84,17485,17486,47,17489,51,17491,17493,17494,17496,17497,17499],{},[21,17487,17488],{},"공용 변경",[32,17490,4485],{},[32,17492,7082],{}," prop 추가(샘플 보기\u002F선택 공용), ",[32,17495,7358],{}," 미리보기 시각을 현재 시각으로, ",[32,17498,4503],{}," 폰 높이를 내용에 맞춰 자동(버튼 잘림 해소).",[84,17501,17502,17503,6685,17505,15415,17508,4536,17510,15269,17512,4703],{},"배포 #32: ",[32,17504,13896],{},[32,17506,17507],{},"--commit-message \"Add SMS\u002FKakao message template pages\"",[32,17509,17424],{},[32,17511,17450],{},[32,17513,17514],{},"https:\u002F\u002F84b6df73.malgn-noti.pages.dev",[84,17516,15276,17517,17520,17521,15356],{},[32,17518,17519],{},"b52b9fa 문자메시지·알림톡 템플릿 관리 페이지 신규 구성"," (10 files, +3630 −19) → ",[32,17522,12610],{},[76,17524,17526],{"id":17525},"_11-미리보기-시각-현재화-알림톡rcspush-11-배포-33","11. 미리보기 시각 현재화 — 알림톡·RCS·PUSH (§11, 배포 #33)",[81,17528,17529,17550,17563,17582],{},[84,17530,17531,17534,17535,6121,17539,17541,17542,17545,17546,17549],{},[32,17532,17533],{},"usePreviewClock"," 컴포저블 신규(",[26,17536,17538],{"href":17537},"..\u002F..\u002Fapp\u002Fcomposables\u002FusePreviewClock.ts","app\u002Fcomposables\u002FusePreviewClock.ts",[32,17540,8663],{},"(클라이언트)에서 현재 시각 ",[32,17543,17544],{},"H:MM","·날짜 ",[32,17547,17548],{},"M월 D일 요일"," 계산(SSR hydration 불일치 방지).",[84,17551,17552,4536,17554,17556,17557,17559,17560,17562],{},[32,17553,4503],{},[32,17555,4506],{}," 상태바 시간, ",[32,17558,4512],{}," 상태바 + 잠금화면 시계(Android·iOS) + 날짜를 현재 값으로 표시. (문자 ",[32,17561,7358],{},"는 §10에서 이미 적용)",[84,17564,17565,17566,6685,17568,15415,17571,4536,17573,4536,17576,15269,17579,4703],{},"배포 #33: ",[32,17567,13896],{},[32,17569,17570],{},"--commit-message \"Preview clock: live time for kakao\u002FRCS\u002FPUSH\"",[32,17572,17450],{},[32,17574,17575],{},"\u002Fsend\u002Frcs",[32,17577,17578],{},"\u002Fsend\u002Fpush",[32,17580,17581],{},"https:\u002F\u002F34a5b3b0.malgn-noti.pages.dev",[84,17583,15276,17584,17587,17588,15356],{},[32,17585,17586],{},"985905f 미리보기 시각을 현재 시각으로 — 알림톡·RCS·PUSH"," (4 files, +31 −8) → ",[32,17589,12610],{},[76,17591,17593],{"id":17592},"_12-로그인-페이지-시안-ia-재구성-12-배포-34","12. 로그인 페이지 시안 IA 재구성 (§12, 배포 #34)",[81,17595,17596,17658,17673,17686],{},[84,17597,17598,17602,17603],{},[26,17599,17601],{"href":17600},"..\u002F..\u002Fapp\u002Fpages\u002Flogin\u002Findex.vue","app\u002Fpages\u002Flogin\u002Findex.vue",": 시안 캡처 기준 재구성.\n",[81,17604,17605,17615,17624,17642],{},[84,17606,17607,17608,6219,17611,17614],{},"라벨 ",[32,17609,17610],{},"아이디 (이메일)",[32,17612,17613],{},"아이디",", 비밀번호 입력란에 안내 placeholder + eye 토글 세로 중앙 정렬 보정.",[84,17616,17617,6219,17620,17623],{},[32,17618,17619],{},"로그인 상태 유지",[32,17621,17622],{},"아이디 기억하기"," 체크박스(기본 체크).",[84,17625,17626,17629,17630,17633,17634,17637,17638,17641],{},[32,17627,17628],{},"비밀번호 재설정"," 링크를 ",[32,17631,17632],{},"로그인 하기"," 버튼 아래 별도 줄로 분리, 회원가입 안내를 테두리 카드(",[32,17635,17636],{},"회원가입하기"," 링크 → ",[32,17639,17640],{},"\u002Fsignup",")로 변경.",[84,17643,17644,17645,17648,17649,17651,17652,17654,17655,17657],{},"로그인 후 ",[32,17646,17647],{},"?redirect="," 쿼리 경로 또는 ",[32,17650,11278],{},"으로 이동(",[32,17653,11029],{}," 호환). 아이디·비밀번호 미입력 시 ",[32,17656,17632],{}," 비활성.",[84,17659,17660,17661,4536,17664,17667,17668,17384,17670,17388],{},"진행 중인 메시지 관리 작업분(",[32,17662,17663],{},"manage\u002Femail.vue",[32,17665,17666],{},"manage\u002Frcs.vue",")은 ",[32,17669,16025],{},[21,17671,17672],{},"로그인 변경만",[84,17674,17675,17676,6685,17678,15415,17681,15269,17683,13932],{},"배포 #34: ",[32,17677,13896],{},[32,17679,17680],{},"--commit-message \"login page: id field, remember-id, reset-password link, signup card\"",[32,17682,9335],{},[32,17684,17685],{},"https:\u002F\u002F7ee50da3.malgn-noti.pages.dev",[84,17687,15276,17688,17691,17692,15356],{},[32,17689,17690],{},"efa6d4a 로그인 페이지 재구성 — 시안 IA 반영"," (1 file, +113 −33) → ",[32,17693,12610],{},[76,17695,17697],{"id":17696},"_13-비밀번호-재설정-이메일-입력란-너비-보정-13-배포-35","13. 비밀번호 재설정 — 이메일 입력란 너비 보정 (§13, 배포 #35)",[81,17699,17700,17717,17728,17742],{},[84,17701,17702,47,17706,17708,17709,17712,17713,17716],{},[26,17703,17705],{"href":17704},"..\u002F..\u002Fapp\u002Fpages\u002Freset-password\u002Findex.vue","app\u002Fpages\u002Freset-password\u002Findex.vue",[32,17707,6084],{},"이 내용 너비만 차지해 입력 박스가 짧게 보이던 문제 → ",[32,17710,17711],{},"class=\"w-full\""," 추가로 폼 너비(",[32,17714,17715],{},"재설정 메일 발송"," 버튼)와 정렬.",[84,17718,17660,17719,4536,17721,17667,17723,17384,17725,17388],{},[32,17720,17663],{},[32,17722,16531],{},[32,17724,16025],{},[21,17726,17727],{},"비밀번호 재설정 변경만",[84,17729,17730,17731,6685,17733,15415,17736,15269,17739,13932],{},"배포 #35: ",[32,17732,13896],{},[32,17734,17735],{},"--commit-message \"reset-password: full-width email input\"",[32,17737,17738],{},"\u002Freset-password",[32,17740,17741],{},"https:\u002F\u002Fe479fadc.malgn-noti.pages.dev",[84,17743,15276,17744,17747,17748,15356],{},[32,17745,17746],{},"489ce30 비밀번호 재설정 — 이메일 입력란 전체 너비 적용"," (1 file, +1 −1) → ",[32,17749,12610],{},[76,17751,17753],{"id":17752},"_14-메시지-관리-이메일-템플릿-페이지-14-배포-36","14. 메시지 관리 — 이메일 템플릿 페이지 (§14, 배포 #36)",[81,17755,17756,17776,17788,17799,17811,17825],{},[84,17757,17758,6685,17761,17765,17766,17768,17769,17772,17773,5606],{},[21,17759,17760],{},"이메일 템플릿",[26,17762,17764],{"href":17763},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Femail.vue","\u002Fmanage\u002Femail","): placeholder → 문자 템플릿 레이아웃 기반 신규 구성. 카테고리 트리 + 툴바(카테고리\u002F템플릿 등록·수정·삭제·샘플 보기) + ",[32,17767,17454],{}," 상세(템플릿 이름·발송 목적·발신 메일·등록\u002F수정 일시 + ",[21,17770,17771],{},"이메일 미리보기"," 460px). 등록\u002F수정 폼 — 템플릿 이름(0\u002F50)·발송 목적(일반·인증·광고)·발신 메일·제목(0\u002F1000)·내용·첨부파일(.jpg\u002F.jpeg, 최대 3개·300KB·합산 800KB), 미리보기 너비는 발송 페이지와 동일(",[32,17774,17775],{},"1fr 460px",[84,17777,17778,47,17780,17783,17784,17787],{},[21,17779,17464],{},[32,17781,17782],{},"AppEmailSampleDialog","(샘플 — 검색 + 카드 그리드 + 이메일 미리보기), ",[32,17785,17786],{},"AppEmailAiDialog","(AI — 프롬프트 → 제목+본문 생성).",[84,17789,17790,47,17792,17794,17795,17798],{},[21,17791,17488],{},[32,17793,4509],{}," 하단 ",[32,17796,17797],{},"텍스트\u002FHTML"," 토글 제거.",[84,17800,17801,17802,4536,17804,17807,17808,17388],{},"진행 중인 RCS 템플릿 작업분(",[32,17803,17666],{},[32,17805,17806],{},"AppRcs*",")은 격리하고 ",[21,17809,17810],{},"이메일 템플릿 변경만",[84,17812,17813,17814,6685,17816,15415,17819,17821,17822,4703],{},"배포 #36: ",[32,17815,13896],{},[32,17817,17818],{},"--commit-message \"Add email message template page\"",[32,17820,17764],{}," 200(실 콘텐츠 확인), alias ",[32,17823,17824],{},"https:\u002F\u002Ff184634e.malgn-noti.pages.dev",[84,17826,15276,17827,17830,17831,15356],{},[32,17828,17829],{},"3ca0531 이메일 메시지 템플릿 관리 페이지 신규 구성"," (4 files, +1403 −19) → ",[32,17832,12610],{},[76,17834,17836],{"id":17835},"_15-보안-인증-페이지-문구입력란-보정-15-배포-37","15. 보안 인증 페이지 — 문구·입력란 보정 (§15, 배포 #37)",[81,17838,17839,17859,17871,17886],{},[84,17840,17841,17845,17846,6219,17849,17852,17853,5069,17855,17712,17857,17716],{},[26,17842,17844],{"href":17843},"..\u002F..\u002Fapp\u002Fpages\u002Flogin\u002Fsecurity.vue","app\u002Fpages\u002Flogin\u002Fsecurity.vue",": 안내 문구 ",[32,17847,17848],{},"등록된 이메일\u002FOTP로 발송된 인증코드를 입력하세요.",[32,17850,17851],{},"등록된 이메일\u002F휴대전화로 발송된 인증코드를 입력하세요.",". 인증코드 ",[32,17854,6084],{},[32,17856,17711],{},[32,17858,16400],{},[84,17860,17801,17861,4536,17863,4536,17865,17807,17868,17388],{},[32,17862,17666],{},[32,17864,17806],{},[32,17866,17867],{},"sitemap.vue",[21,17869,17870],{},"보안 인증 변경만",[84,17872,17873,17874,6685,17876,15415,17879,17882,17883,4703],{},"배포 #37: ",[32,17875,13896],{},[32,17877,17878],{},"--commit-message \"login security: full-width code input, copy fix\"",[32,17880,17881],{},"\u002Flogin\u002Fsecurity"," 200(문구 확인), alias ",[32,17884,17885],{},"https:\u002F\u002F3eccd509.malgn-noti.pages.dev",[84,17887,15276,17888,17891,17892,15356],{},[32,17889,17890],{},"d59cb13 보안 인증 페이지 — 문구 수정 + 인증코드 입력란 전체 너비"," (1 file, +2 −2) → ",[32,17893,12610],{},[76,17895,17897],{"id":17896},"_16-메시지-관리-rcs-템플릿-페이지-사이트맵-페이지-16-배포-38","16. 메시지 관리 — RCS 템플릿 페이지 + 사이트맵 페이지 (§16, 배포 #38)",[81,17899,17900,17913,17929,17942,17957],{},[84,17901,17902,6685,17905,17909,17910,17912],{},[21,17903,17904],{},"RCS 템플릿",[26,17906,17908],{"href":17907},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Frcs.vue","\u002Fmanage\u002Frcs","): placeholder → 문자 템플릿 레이아웃 기반 신규 구성. 카테고리 트리 + ",[32,17911,17454],{}," 상세(RCS Bizcenter 템플릿 아이디·발신 브랜드·발송 목적·템플릿 상태·승인 일시 + RCS 미리보기). 등록\u002F수정 폼 — 발신 브랜드(브랜드·대화방)·발송 목적·발송 유형(SMS\u002FLMS\u002FMMS + 스탠드얼론\u002F대화형 + 대체발송 SMS\u002F통합)·내용·버튼 관리(순서 이동).",[84,17914,17915,47,17917,17920,17921,17924,17925,17928],{},[21,17916,17464],{},[32,17918,17919],{},"AppRcsButtonDialog","(버튼 8종 — URL 연결·전화 걸기·복사하기·지도 보여주기\u002F검색하기·현재 위치 공유·일정 등록·대화방 열기, 유형별 조건 필드), ",[32,17922,17923],{},"AppRcsSampleDialog","(SMS\u002FLMS\u002FMMS 탭 + 카드 그리드 + RCS 미리보기), ",[32,17926,17927],{},"AppRcsAiDialog","(발송 유형·발송 방식 + RCS 미리보기).",[84,17930,17931,6100,17934,17937,17938,17941],{},[21,17932,17933],{},"사이트맵 페이지",[32,17935,17936],{},"\u002Fsitemap",", 커밋 ",[32,17939,17940],{},"d56b338",")가 미배포 상태(404)였어 함께 배포.",[84,17943,17944,17945,6685,17947,15415,17950,4536,17952,17821,17954,4703],{},"배포 #38: ",[32,17946,13896],{},[32,17948,17949],{},"--commit-message \"Add RCS message template page and sitemap page\"",[32,17951,17908],{},[32,17953,17936],{},[32,17955,17956],{},"https:\u002F\u002Fefb7e52a.malgn-noti.pages.dev",[84,17958,15276,17959,17962,17963,15356],{},[32,17960,17961],{},"30921e0 RCS 메시지 템플릿 관리 페이지 신규 구성"," (4 files, +1845 −5) → ",[32,17964,12610],{},[76,17966,17968],{"id":17967},"_17-사이트맵-보강-캠페인-페이지-삭제-17-배포-39","17. 사이트맵 보강 + 캠페인 페이지 삭제 (§17, 배포 #39)",[81,17970,17971,17987,18004,18021],{},[84,17972,17973,6100,17976,17979,17980,4361,17983,17986],{},[21,17974,17975],{},"사이트맵 보강",[26,17977,17936],{"href":17978},"..\u002F..\u002Fapp\u002Fpages\u002Fsitemap.vue","): 항목별 전체 순번·한 줄 설명·",[32,17981,17982],{},"페이지",[32,17984,17985],{},"팝업"," 구분 배지 추가, 새 창 링크·한 행 1항목. 라우트 페이지 + 주요 팝업(모달)을 카테고리별로 수록.",[84,17988,17989,47,17992,4536,17995,17998,17999,4536,18001,18003],{},[21,17990,17991],{},"캠페인 페이지 삭제",[32,17993,17994],{},"app\u002Fpages\u002Fcampaign\u002Findex.vue",[32,17996,17997],{},"campaign3\u002Findex.vue"," 제거(라우트 ",[32,18000,17204],{},[32,18002,11618],{}," 삭제), 사이트맵 캠페인 카테고리 그룹 제거. (GNB 캠페인 메뉴는 §7에서 이미 삭제)",[84,18005,18006,18007,6685,18009,15415,18012,18014,18015,18017,18018,4703],{},"배포 #39: ",[32,18008,13896],{},[32,18010,18011],{},"--commit-message \"sitemap page + remove campaign pages\"",[32,18013,17936],{}," 200·",[32,18016,17204],{}," 404 확인, alias ",[32,18019,18020],{},"https:\u002F\u002F6f1f15f2.malgn-noti.pages.dev",[84,18022,15276,18023,4420,18026,6219,18029,15356],{},[32,18024,18025],{},"d56b338 사이트맵 페이지 신규 구성",[32,18027,18028],{},"a1f0969 사이트맵에서 캠페인 제거 + 캠페인 페이지 파일 삭제",[32,18030,12610],{},[76,18032,18034],{"id":18033},"_18-메시지-관리-push-템플릿-페이지-18-배포-40","18. 메시지 관리 — PUSH 템플릿 페이지 (§18, 배포 #40)",[81,18036,18037,18050,18066,18075,18088],{},[84,18038,18039,6685,18042,18046,18047,18049],{},[21,18040,18041],{},"PUSH 템플릿",[26,18043,18045],{"href":18044},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Fpush.vue","\u002Fmanage\u002Fpush","): placeholder → 템플릿 페이지 레이아웃 기반 신규 구성. 카테고리 트리 + ",[32,18048,17454],{}," 상세(템플릿 이름·발송 목적·입력 유형·등록\u002F수정 일시 + Android·iOS 잠금화면 미리보기 2종). 등록\u002F수정 폼 — 발송 목적(일반\u002F광고)·입력 유형(기본\u002FJSON). 기본: HTML스타일·제목·내용·배지 + 버튼·미디어·Android\u002FiOS 미디어·Android 큰 아이콘·그룹 추가 \u002F JSON: 페이로드 textarea.",[84,18051,18052,47,18054,18057,18058,18061,18062,18065],{},[21,18053,17464],{},[32,18055,18056],{},"AppPushAddDialog","(버튼·미디어·그룹 추가 공용 모달), ",[32,18059,18060],{},"AppPushSampleDialog","(샘플 — 카드 그리드 + PUSH 미리보기), ",[32,18063,18064],{},"AppPushAiDialog","(AI — 제목+내용 생성).",[84,18067,18068,18069,18071,18072,17388],{},"진행 중인 회원가입(",[32,18070,14163],{},") 작업분은 격리하고 ",[21,18073,18074],{},"PUSH 템플릿 변경만",[84,18076,18077,18078,6685,18080,15415,18083,17821,18085,4703],{},"배포 #40: ",[32,18079,13896],{},[32,18081,18082],{},"--commit-message \"Add PUSH message template page\"",[32,18084,18045],{},[32,18086,18087],{},"https:\u002F\u002Fb21d275f.malgn-noti.pages.dev",[84,18089,15276,18090,18093,18094,15356],{},[32,18091,18092],{},"f7838f6 PUSH 메시지 템플릿 관리 페이지 신규 구성"," (4 files, +1659 −5) → ",[32,18095,12610],{},[76,18097,18099],{"id":18098},"_19-회원가입-페이지-5단계-마법사-19-배포-41","19. 회원가입 페이지 5단계 마법사 (§19, 배포 #41)",[81,18101,18102,18174,18184,18194,18207],{},[84,18103,18104,18108,18109,18111,18112,18114,18115,18118,18119],{},[26,18105,18107],{"href":18106},"..\u002F..\u002Fapp\u002Fpages\u002Fsignup.vue","app\u002Fpages\u002Fsignup.vue",": 기존 좁은 ",[32,18110,10164],{}," 카드 3단계 → 넓은 ",[32,18113,5713],{}," 레이아웃(1140px) 5단계 마법사로 전면 재구성. 상단 로고 + ",[32,18116,18117],{},"회원가입"," 타이틀 + 5단계 인디케이터(현재=ink 다크·완료=중간·이후=연회색).\n",[81,18120,18121,18138,18144,18153,18159,18168],{},[84,18122,18123,47,18126,18129,18130,18133,18134,18137],{},[21,18124,18125],{},"Step 1 회원 가입 안내",[32,18127,18128],{},"서비스 이용 절차","(4카드·화살표 연결) + ",[32,18131,18132],{},"서비스 신청 서류","(3행 표) + ",[21,18135,18136],{},"회원 유형 선택","(법인사업자·개인사업자·개인 카드 — 선택해야 다음 진행).",[84,18139,18140,18143],{},[21,18141,18142],{},"Step 2 정보 확인",": 회원 유형은 읽기 전용 표시, 사업자(사업자번호 3분할·회사명·대표자명) \u002F 개인(이름·주소)으로 폼 분기.",[84,18145,18146,18149,18150,5606],{},[21,18147,18148],{},"Step 3 아이디 등록 및 약관 동의",": 이메일 + 인증코드 6칸(자동 이동) + 비밀번호·확인, 약관 동의 카드(전체 동의 + 4종 행 — 필수\u002F선택 배지·",[32,18151,18152],{},"약관보기",[84,18154,18155,18158],{},[21,18156,18157],{},"Step 4 휴대폰 본인 인증",": 통신사·이름·주민등록번호(앞 6자리+성별)·내외국인·휴대폰 번호 + 인증번호 발송\u002F확인.",[84,18160,18161,18164,18165,4703],{},[21,18162,18163],{},"Step 5 가입 완료",": 승인 안내 + ",[32,18166,18167],{},"로그인 하러 가기",[84,18169,18170,18171,16954],{},"단계별 필수값 검증 통과 시에만 ",[32,18172,18173],{},"다음",[84,18175,18176,18179,18180,18183],{},[21,18177,18178],{},"AppSignupTermsDialog","(신규): 약관 전문 모달 — 이용약관·스팸메시지 이용약관·개인정보 수집 및 이용동의·광고성 정보 수신 동의 4종 조항 본문, ",[32,18181,18182],{},"동의하기"," 시 해당 약관 체크.",[84,18185,17660,18186,17667,18189,17384,18191,17388],{},[32,18187,18188],{},"manage\u002Fsettings.vue",[32,18190,16025],{},[21,18192,18193],{},"회원가입 변경만",[84,18195,18196,18197,6685,18199,15415,18202,17821,18204,4703],{},"배포 #41: ",[32,18198,13896],{},[32,18200,18201],{},"--commit-message \"signup: 5-step wizard (info, verify, id+terms, phone auth, done)\"",[32,18203,17640],{},[32,18205,18206],{},"https:\u002F\u002F28324dbc.malgn-noti.pages.dev",[84,18208,15276,18209,18212,18213,15356],{},[32,18210,18211],{},"a836fdf 회원가입 페이지 5단계 마법사 신규 구성"," (2 files, +1252 −93) → ",[32,18214,12610],{},[76,18216,18218],{"id":18217},"_20-메시지-관리-상세-설정-페이지-20-배포-42","20. 메시지 관리 — 상세 설정 페이지 (§20, 배포 #42)",[81,18220,18221,18239,18258,18272],{},[84,18222,18223,6685,18226,18230,18231],{},[21,18224,18225],{},"상세 설정",[26,18227,18229],{"href":18228},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Fsettings.vue","\u002Fmanage\u002Fsettings","): placeholder → 신규 구성. 5개 탭(문자 메시지·RCS·PUSH·웹훅·백업), 탭마다 좌측 그룹 라벨 + 접이식 설정 섹션 카드.\n",[81,18232,18233,18236],{},[84,18234,18235],{},"문자 메시지: 국제 SMS 발송(사용 여부·월 발송 건수·발송 허용 국가 태그)·메시지 중복 발송 차단·대체 문자 설정(모달)·광고성 메시지 발송 시간 제한.",[84,18237,18238],{},"RCS: 광고성 메시지 발송 시간 제한. PUSH: 토큰 만료 기간·앱 유형·중복 발송 차단·수신\u002F확인 수집·광고 표시 문구 위치·수신 동의 자동 발송. 웹훅: URL·재시도 횟수. 백업: 발송 데이터 백업.",[84,18240,18241,18244,18245,18248,18249,4361,18251,5439,18254,18257],{},[21,18242,18243],{},"AppSettingsSection","(신규): 제목·설명·",[32,18246,18247],{},"설정 변경"," 토글 + 본문 박스 + 섹션별 ",[32,18250,16953],{},[32,18252,18253],{},"취소",[32,18255,18256],{},"modal"," 모드 시 펼침 대신 모달 오픈 이벤트 emit(대체 문자 설정에 사용).",[84,18259,18260,18261,6685,18263,15415,18266,18268,18269,4703],{},"배포 #42: ",[32,18262,13896],{},[32,18264,18265],{},"--commit-message \"Add message detail settings page\"",[32,18267,18229],{}," 200(실 콘텐츠 확인 — 첫 배포는 stale dist라 재빌드 후 재배포), alias ",[32,18270,18271],{},"https:\u002F\u002F448a1130.malgn-noti.pages.dev",[84,18273,15276,18274,18277,18278,15356],{},[32,18275,18276],{},"07e78b3 메시지 상세 설정 페이지 신규 구성"," (2 files, +939 −6) → ",[32,18279,12610],{},[76,18281,18283],{"id":18282},"_21-새-비밀번호-설정-페이지-입력란검증-보정-21-배포-43","21. 새 비밀번호 설정 페이지 — 입력란·검증 보정 (§21, 배포 #43)",[81,18285,18286,18298,18308,18322],{},[84,18287,18288,18292,18293,5069,18295,18297],{},[26,18289,18291],{"href":18290},"..\u002F..\u002Fapp\u002Fpages\u002Freset-password\u002Fnew.vue","app\u002Fpages\u002Freset-password\u002Fnew.vue",": 비밀번호·확인 ",[32,18294,6084],{},[32,18296,17711],{}," 적용(폼 너비 정렬), placeholder 추가.",[84,18299,18300,18301,18304,18305,5606],{},"검증 메시지 추가 — 새 비밀번호: ",[32,18302,18303],{},"help","로 \"영문·숫자·특수문자 조합 8자 이상\" 안내 + 8자 미만 시 오류, 새 비밀번호 확인: 불일치 시 \"비밀번호가 일치하지 않습니다.\" (",[32,18306,18307],{},"UFormField :error",[84,18309,18310,18311,6685,18313,15415,18316,15269,18319,13932],{},"배포 #43: ",[32,18312,13896],{},[32,18314,18315],{},"--commit-message \"reset-password\u002Fnew: full-width inputs, validation messages\"",[32,18317,18318],{},"\u002Freset-password\u002Fnew",[32,18320,18321],{},"https:\u002F\u002F1fe8363d.malgn-noti.pages.dev",[84,18323,15276,18324,18327,18328,15356],{},[32,18325,18326],{},"54b53c5 새 비밀번호 설정 — 입력란 전체 너비 + 검증 메시지"," (1 file, +34 −4) → ",[32,18329,12610],{},[76,18331,18333],{"id":18332},"_22-메시지-관리-랜딩페이지-만들기-22-배포-44","22. 메시지 관리 — 랜딩페이지 만들기 (§22, 배포 #44)",[81,18335,18336,18350,18370,18380,18386,18392,18409],{},[84,18337,18338,18340,18341,18343,18344,6100,18347,15971],{},[21,18339,10137],{},": 메시지 관리 메뉴 ",[32,18342,18225],{}," 위에 ",[32,18345,18346],{},"랜딩페이지 만들기",[32,18348,18349],{},"\u002Fmanage\u002Flanding",[84,18351,18352,6100,18355,18358,18359,18361,18362,18365,18366,18369],{},[21,18353,18354],{},"목록",[26,18356,18349],{"href":18357},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Flanding.vue","): C 테이블 스타일 — 공개여부 필터·이름 검색·선택 복사\u002F삭제, 행별 ",[32,18360,7244],{},"(공개·비공개 모두)·",[32,18363,18364],{},"URL 복사","(공개만). ",[32,18367,18368],{},"페이지 등록"," 버튼은 기본형\u002F확장형 드롭다운.",[84,18371,18372,18375,18376,18379],{},[21,18373,18374],{},"AppLandingForm","(신규): 랜딩페이지 등록\u002F수정 폼(폼 뷰 전환). 공개 여부 토글·랜딩페이지명·설명·URL \u002F 메인 타이틀(헤드 이미지·헤드라인·서브·확장형 텍스트 정렬) \u002F (확장형)비주얼 이미지 \u002F 콘텐츠 영역(리치 에디터 목업) \u002F (확장형)CTA 버튼(텍스트·이동 링크·색상). 하단 액션은 발송 페이지 공용 ",[32,18377,18378],{},".send-actions"," 스타일. 이름 클릭 시 수정 모드 진입.",[84,18381,18382,18385],{},[21,18383,18384],{},"AppLandingPreviewDialog","(신규): 미리보기 모달 — \"LIVELY SHOP 빅세일\" 샘플 랜딩 렌더(헤로·콘텐츠·CTA), width 960·min-height 74vh.",[84,18387,18388,18391],{},[21,18389,18390],{},"AppLandingUrlDialog","(신규): 랜딩페이지 URL 복사 완료 모달(그린 체크 + 숏 URL + 복사 버튼).",[84,18393,18394,18395,6685,18397,15415,18400,15269,18402,18405,18406,18408],{},"배포 #44: ",[32,18396,13896],{},[32,18398,18399],{},"--commit-message \"Add landing page builder (list, basic\u002Fextended form, preview)\"",[32,18401,18349],{},[32,18403,18404],{},"https:\u002F\u002F184b0fe1.malgn-noti.pages.dev"," 200. 동시 진행 중인 '나의 페이지'·충전 작업분은 ",[32,18407,16025],{},"로 격리하고 랜딩페이지 변경만 배포.",[84,18410,15276,18411,18414,18415,15356],{},[32,18412,18413],{},"265395a 랜딩페이지 만들기 — 목록·등록\u002F수정 폼·미리보기 신규 구성"," (5 files, +1444 −2) → ",[32,18416,12610],{},[76,18418,18420],{"id":18419},"_23-문의하기-페이지-accountinquiry-경로-이동-23-배포-45","23. 문의하기 페이지 — \u002Faccount\u002Finquiry 경로 이동 (§23, 배포 #45)",[81,18422,18423,18441,18458,18477,18503],{},[84,18424,18425,18426,12760,18428,4473,18431,12760,18434,4473,18437,18440],{},"문의 관련 라우트를 최종 정리: ",[32,18427,16882],{},[21,18429,18430],{},"문의하기 폼",[32,18432,18433],{},"\u002Faccount\u002Finquiry\u002Fcomplete",[21,18435,18436],{},"접수 완료",[32,18438,18439],{},"\u002Faccount\u002Finquiries"," = 나의 문의 목록(동시 진행 중인 '나의 페이지' 작업 소관).",[84,18442,18443,18444,4536,18447,6219,18450,18453,18454,18457],{},"파일 이동: ",[32,18445,18446],{},"app\u002Fpages\u002Finquiry\u002Findex.vue",[32,18448,18449],{},"inquiry\u002Fcomplete.vue",[32,18451,18452],{},"app\u002Fpages\u002Faccount\u002Finquiry\u002F"," 하위로. 중복되던 별도 문의 목록 페이지(",[32,18455,18456],{},"inquiry\u002Findex.vue",")는 삭제.",[84,18459,18460,18461,18463,18464,18467,18468,18470,18471,17629,18474,18476],{},"링크 갱신: ",[26,18462,11316],{"href":17197},"(데스크톱 '문의' 필 + 모바일 드로어 '문의하기'), ",[26,18465,13094],{"href":18466},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppFooter.vue","(고객센터), ",[26,18469,17867],{"href":17978},"(1:1 문의 작성·완료)의 ",[32,18472,18473],{},"\u002Finquiry",[32,18475,16882],{},"로.",[84,18478,18479,18480,18483,18484,6100,18487,18489,18490,18493,18494,4536,18496,13923,18498,18017,18500,4703],{},"배포 #45: 동시 진행 중인 '나의 페이지'·충전 작업분이 working tree에 섞여 있어, 문의 커밋(",[32,18481,18482],{},"a021e2b",")에서 ",[21,18485,18486],{},"임시 git worktree",[32,18488,18482],{}," 체크아웃)를 만들어 문의 변경만 격리 빌드 후 배포 (",[32,18491,18492],{},"--commit-message \"Move inquiry pages to account inquiry route\"","). 프로덕션 ",[32,18495,16882],{},[32,18497,18433],{},[32,18499,18473],{},[32,18501,18502],{},"https:\u002F\u002Faa4503b7.malgn-noti.pages.dev",[84,18504,15276,18505,18508,18509,18511],{},[32,18506,18507],{},"a021e2b 문의하기 페이지를 \u002Faccount\u002Finquiry 경로로 이동"," (6 files, +310 −17) → ",[32,18510,12610],{}," 푸시. (sitemap.vue의 '계정 관리'→'나의 페이지' 라벨 변경은 동시 작업분이 같은 파일에 섞여 함께 커밋됨.)",[76,18513,18515],{"id":18514},"_24-나의-페이지-섹션-크레딧-충전-플로우-24-배포-46","24. 나의 페이지 섹션 + 크레딧 충전 플로우 (§24, 배포 #46)",[81,18517,18518,18568,18574,18584,18601,18623,18643],{},[84,18519,18520,18523,18524,18526,18527,16097,18529,18532,18533,18535,18536,18539,18540,18543,18544,18547,18548,18551,18552,18555,18556,18559,18560,18563,18564,18567],{},[21,18521,18522],{},"나의 페이지"," — 계정 관리를 ",[32,18525,18522],{},"로 개편. ",[21,18528,5836],{},[32,18530,18531],{},"MY PAGE"," 헤더 + 좌측 메뉴 9종(라우트 링크) 공통 셸. 라우트 9개 — ",[32,18534,3991],{},"(회원 정보 변경)·",[32,18537,18538],{},"cards","(결제 카드 관리)·",[32,18541,18542],{},"password","(비밀번호 변경)·",[32,18545,18546],{},"security","(보안로그인 설정)·",[32,18549,18550],{},"multi","(멀티 계정 추가)·",[32,18553,18554],{},"contract","(계약 관리)·",[32,18557,18558],{},"credit","(크레딧 관리)·",[32,18561,18562],{},"billing","(결제 내역)·",[32,18565,18566],{},"inquiries","(나의 문의).",[84,18569,18570,18573],{},[21,18571,18572],{},"AppMemberInfoPanel","(신규): 회원 정보 변경 — 가입 정보(읽기 전용 + 광고성 메일 수신 컨펌 토글)·서비스 담당자(이메일 변경·휴대폰 인증)·결제 이메일·저장하기\u002F회원 탈퇴. 사업자등록증 변경 → 계약 관리 이동.",[84,18575,18576,18579,18580,18583],{},[21,18577,18578],{},"AppCardListPanel","(신규): 결제 카드 관리 — 카드 목록·기본 카드 라디오·",[32,18581,18582],{},"저장하기","로 기본 카드 저장·카드 삭제.",[84,18585,18586,47,18589,18592,18593,18596,18597,18600],{},[21,18587,18588],{},"신규 모달",[32,18590,18591],{},"AppEmailChangeDialog","(이메일\u002F결제 이메일 변경 — 인증코드 6칸), ",[32,18594,18595],{},"AppPhoneVerifyDialog","(휴대폰 본인 인증), ",[32,18598,18599],{},"AppCardAddDialog","(카드 추가).",[84,18602,18603,6685,18606,18610,18611,18613,18614,18617,18618,18622],{},[21,18604,18605],{},"크레딧 충전",[26,18607,18609],{"href":18608},"..\u002F..\u002Fapp\u002Fpages\u002Fcharge\u002Findex.vue","\u002Fcharge","): 시안 기반 재구성 — 충전 금액 선택(보너스)·결제 카드 등록(",[32,18612,18599],{}," 연동)·결제 및 환불안내·동의. ",[32,18615,18616],{},"결제하기"," → 진행 컨펌 모달 → ",[26,18619,18621],{"href":18620},"..\u002F..\u002Fapp\u002Fpages\u002Fcharge\u002Fresult.vue","\u002Fcharge\u002Fresult"," 충전 완료 화면(주문 정보·결제 전\u002F후 크레딧).",[84,18624,18625,18626,6685,18628,15415,18631,4536,18633,4536,18636,4536,18638,15269,18640,4703],{},"배포 #46: ",[32,18627,13896],{},[32,18629,18630],{},"--commit-message \"My Page section + credit charge flow\"",[32,18632,3991],{},[32,18634,18635],{},"\u002Faccount\u002Fcards",[32,18637,18609],{},[32,18639,18621],{},[32,18641,18642],{},"https:\u002F\u002Ffcb87146.malgn-noti.pages.dev",[84,18644,15276,18645,18648,18649,15356],{},[32,18646,18647],{},"83c4c37 나의 페이지 섹션 + 크레딧 충전 플로우 신규 구성"," (17 files, +2189 −205) → ",[32,18650,12610],{},[76,18652,18654],{"id":18653},"_25-malgn-noti-api-루트-doc-리다이렉트-25-api-배포","25. malgn-noti-api 루트(\u002F) → \u002Fdoc 리다이렉트 (§25, API 배포)",[81,18656,18657,18668,18690,18704],{},[84,18658,18659,18663,18664,18667],{},[26,18660,18662],{"href":18661},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Findex.ts","src\u002Findex.ts:55"," 의 placeholder JSON 핸들러를 ",[32,18665,18666],{},"c.redirect('\u002Fdoc')"," 한 줄로 교체 — 워커 도메인 루트 접속이 곧장 Scalar API 문서로 이동.",[84,18669,13321,18670,89,18673,51,18676,18679,18680,18683,18684,4473,18687,18689],{},[32,18671,18672],{},"pnpm run deploy",[32,18674,18675],{},"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002F",[32,18677,18678],{},"GET"," 응답이 ",[32,18681,18682],{},"302 Location: \u002Fdoc","로 변경. Version ID ",[32,18685,18686],{},"f3fd3eb4-c594-471c-949a-f61ba1b30db1",[32,18688,11248],{}," 200 production.",[84,18691,18692,18693,18695,18696,18699,18700,18703],{},"격리: 동시 진행 중인 API 작업분(NHN webhook·send·schema·dispatch worker·flow-definitions\u002Fexport-jobs 신규 라우트)이 working tree에 섞여 있어 — 배포는 working tree 기준이라 함께 라이브에 올라갔으나(typecheck 통과·",[32,18694,11248],{}," 정상), 커밋은 임시 ",[32,18697,18698],{},"git checkout HEAD -- src\u002Findex.ts","로 베이스라인 복원 → 리다이렉트만 재적용 → stage·commit 후 WIP 복원 방식으로 ",[21,18701,18702],{},"리다이렉트 한 줄만"," 격리하여 기록.",[84,18705,15276,18706,18709,18710,15356],{},[32,18707,18708],{},"malgn-noti-api: 677dffa 루트(\u002F) 요청을 API 문서(\u002Fdoc)로 302 리다이렉트"," (1 file, +1 −8) → ",[32,18711,12610],{},[76,18713,10916],{"id":10916},[237,18715,18717],{"id":18716},"신규-3","신규 (3)",[81,18719,18720,18726,18732],{},[84,18721,18722],{},[26,18723,18725],{"href":18724},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppProfileRegisterDialog.vue","app\u002Fcomponents\u002FAppProfileRegisterDialog.vue",[84,18727,18728],{},[26,18729,18731],{"href":18730},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppProfileGroupDialog.vue","app\u002Fcomponents\u002FAppProfileGroupDialog.vue",[84,18733,18734],{},[26,18735,18737],{"href":18736},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppPushCertSection.vue","app\u002Fcomponents\u002FAppPushCertSection.vue",[237,18739,18741],{"id":18740},"신규-6-8-수신-거부-관리","신규 (6 — §8 수신 거부 관리)",[81,18743,18744,18750,18756,18762],{},[84,18745,18746],{},[26,18747,18749],{"href":18748},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppOptoutManager.vue","app\u002Fcomponents\u002FAppOptoutManager.vue",[84,18751,18752],{},[26,18753,18755],{"href":18754},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppOptoutAddDialog.vue","app\u002Fcomponents\u002FAppOptoutAddDialog.vue",[84,18757,18758],{},[26,18759,18761],{"href":18760},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppOptoutBulkDialog.vue","app\u002Fcomponents\u002FAppOptoutBulkDialog.vue",[84,18763,18764,4473,18767,4473,18770],{},[26,18765,18766],{"href":17240},"app\u002Fpages\u002Fcontacts\u002Foptout.vue",[32,18768,18769],{},"optout-email.vue",[32,18771,18772],{},"optout-token.vue",[237,18774,18776],{"id":18775},"수정-4-15","수정 (4 — §1~5)",[81,18778,18779,18785,18791,18797],{},[84,18780,18781,18784],{},[32,18782,18783],{},"app\u002Fpages\u002Fsender\u002Fprofiles.vue","(발신 프로필 관리 페이지 전면 구성)",[84,18786,18787,18790],{},[32,18788,18789],{},"app\u002Fpages\u002Fsender\u002Fpush-cert.vue","(PUSH 인증 관리 페이지 전면 구성)",[84,18792,18793,18796],{},[32,18794,18795],{},"app\u002Fpages\u002Fsender\u002Foptout-080.vue","(080 수신 거부 번호 관리 페이지 전면 구성)",[84,18798,18799,18802],{},[32,18800,18801],{},"app\u002Fpages\u002Fcontacts\u002Fgroups.vue","(그룹 아이디 컬럼 제거)",[237,18804,18806],{"id":18805},"수정-11-6-테이블-스타일-abc","수정 (11 — §6 테이블 스타일 A\u002FB\u002FC)",[81,18808,18809,18818,18824,18830],{},[84,18810,18811,4473,18814,18817],{},[32,18812,18813],{},"app\u002Fcomponents\u002FAppHistoryView.vue",[32,18815,18816],{},"app\u002Fpages\u002Fhistory\u002Fstats.vue","(발송 조회 툴바)",[84,18819,18820,18823],{},[32,18821,18822],{},"app\u002Fpages\u002Fsender\u002F{numbers,brands,domains,profiles,optout-080}.vue","(B·C 스타일 적용)",[84,18825,18826,18829],{},[32,18827,18828],{},"app\u002Fpages\u002Fcontacts\u002F{list,groups}.vue","(C 스타일 + 주소록 메시지 발송 컬럼)",[84,18831,18832,18834,18835,18837],{},[32,18833,7929],{},"(§11 A·B·C 예시), ",[32,18836,11891],{},"(§6.5 A·B·C 정의)",[237,18839,7855],{"id":7855},[81,18841,18842,18848,18854,18860,18866,18872,18878,18884,18890,18896,18902,18908,18914,18920,18926,18932,18938,18944,18950,18956,18962],{},[84,18843,18844,18845],{},"#27 — 발신 정보 페이지 (발신 프로필·PUSH 인증·080 수신 거부) \u002F Alias: ",[26,18846,17029],{"href":17029,"rel":18847},[30],[84,18849,18850,18851],{},"#28 — 테이블 스타일 A\u002FB\u002FC + 발송 조회 툴바 재배치 \u002F Alias: ",[26,18852,17178],{"href":17178,"rel":18853},[30],[84,18855,18856,18857],{},"#29 — GNB 캠페인 메뉴 삭제 \u002F Alias: ",[26,18858,17218],{"href":17218,"rel":18859},[30],[84,18861,18862,18863],{},"#30 — 수신 거부 관리 페이지 3종 (휴대폰·이메일·토큰) \u002F Alias: ",[26,18864,17352],{"href":17352,"rel":18865},[30],[84,18867,18868,18869],{},"#31 — GNB 드롭다운 중복 메뉴명 삭제 \u002F Alias: ",[26,18870,17401],{"href":17401,"rel":18871},[30],[84,18873,18874,18875],{},"#32 — 문자메시지·알림톡 템플릿 관리 페이지 \u002F Alias: ",[26,18876,17514],{"href":17514,"rel":18877},[30],[84,18879,18880,18881],{},"#33 — 알림톡·RCS·PUSH 미리보기 시각 현재화 \u002F Alias: ",[26,18882,17581],{"href":17581,"rel":18883},[30],[84,18885,18886,18887],{},"#34 — 로그인 페이지 시안 IA 재구성 \u002F Alias: ",[26,18888,17685],{"href":17685,"rel":18889},[30],[84,18891,18892,18893],{},"#35 — 비밀번호 재설정 이메일 입력란 너비 보정 \u002F Alias: ",[26,18894,17741],{"href":17741,"rel":18895},[30],[84,18897,18898,18899],{},"#36 — 이메일 메시지 템플릿 페이지 \u002F Alias: ",[26,18900,17824],{"href":17824,"rel":18901},[30],[84,18903,18904,18905],{},"#37 — 보안 인증 페이지 문구·입력란 보정 \u002F Alias: ",[26,18906,17885],{"href":17885,"rel":18907},[30],[84,18909,18910,18911],{},"#38 — RCS 메시지 템플릿 페이지 + 사이트맵 페이지 \u002F Alias: ",[26,18912,17956],{"href":17956,"rel":18913},[30],[84,18915,18916,18917],{},"#39 — 사이트맵 보강 + 캠페인 페이지 삭제 \u002F Alias: ",[26,18918,18020],{"href":18020,"rel":18919},[30],[84,18921,18922,18923],{},"#40 — PUSH 메시지 템플릿 페이지 \u002F Alias: ",[26,18924,18087],{"href":18087,"rel":18925},[30],[84,18927,18928,18929],{},"#41 — 회원가입 페이지 5단계 마법사 \u002F Alias: ",[26,18930,18206],{"href":18206,"rel":18931},[30],[84,18933,18934,18935],{},"#42 — 메시지 관리 상세 설정 페이지 \u002F Alias: ",[26,18936,18271],{"href":18271,"rel":18937},[30],[84,18939,18940,18941],{},"#43 — 새 비밀번호 설정 페이지 입력란·검증 보정 \u002F Alias: ",[26,18942,18321],{"href":18321,"rel":18943},[30],[84,18945,18946,18947],{},"#44 — 메시지 관리 랜딩페이지 만들기 \u002F Alias: ",[26,18948,18404],{"href":18404,"rel":18949},[30],[84,18951,18952,18953],{},"#45 — 문의하기 페이지 \u002Faccount\u002Finquiry 경로 이동 \u002F Alias: ",[26,18954,18502],{"href":18502,"rel":18955},[30],[84,18957,18958,18959],{},"#46 — 나의 페이지 섹션 + 크레딧 충전 플로우 \u002F Alias: ",[26,18960,18642],{"href":18642,"rel":18961},[30],[84,18963,18964,18965],{},"(API) malgn-noti-api 루트 → \u002Fdoc 리다이렉트 \u002F Version ",[32,18966,18686],{},[237,18968,16672],{"id":16672},[81,18970,18971,18977,18983,18989,18995,19001,19007,19013,19019,19025,19031,19037,19043,19052,19058,19064,19070,19076,19082,19087,19093],{},[84,18972,18973,18976],{},[32,18974,18975],{},"e30da5c"," 발신 정보 페이지 신규 구성 — 발신 프로필·PUSH 인증·080 수신 거부 (§5, 배포 #27)",[84,18978,18979,18982],{},[32,18980,18981],{},"74943e8"," 테이블 스타일 A\u002FB\u002FC 정의 + 발송 조회 툴바 재배치 (§6, 배포 #28)",[84,18984,18985,18988],{},[32,18986,18987],{},"d0802e6"," GNB에서 캠페인 메뉴 삭제 (§7, 배포 #29)",[84,18990,18991,18994],{},[32,18992,18993],{},"5f4cb47"," 수신 거부 관리 페이지 3종 신규 구성 (휴대폰·이메일·토큰) (§8, 배포 #30)",[84,18996,18997,19000],{},[32,18998,18999],{},"0f0d9cc"," GNB 드롭다운에서 중복 메뉴명 표시 삭제 (§9, 배포 #31)",[84,19002,19003,19006],{},[32,19004,19005],{},"b52b9fa"," 문자메시지·알림톡 템플릿 관리 페이지 신규 구성 (§10, 배포 #32)",[84,19008,19009,19012],{},[32,19010,19011],{},"985905f"," 미리보기 시각을 현재 시각으로 — 알림톡·RCS·PUSH (§11, 배포 #33)",[84,19014,19015,19018],{},[32,19016,19017],{},"efa6d4a"," 로그인 페이지 재구성 — 시안 IA 반영 (§12, 배포 #34)",[84,19020,19021,19024],{},[32,19022,19023],{},"489ce30"," 비밀번호 재설정 — 이메일 입력란 전체 너비 적용 (§13, 배포 #35)",[84,19026,19027,19030],{},[32,19028,19029],{},"3ca0531"," 이메일 메시지 템플릿 관리 페이지 신규 구성 (§14, 배포 #36)",[84,19032,19033,19036],{},[32,19034,19035],{},"30921e0"," RCS 메시지 템플릿 관리 페이지 신규 구성 (§16, 배포 #38)",[84,19038,19039,19042],{},[32,19040,19041],{},"d59cb13"," 보안 인증 페이지 — 문구 수정 + 인증코드 입력란 전체 너비 (§15, 배포 #37)",[84,19044,19045,19047,19048,19051],{},[32,19046,17940],{}," 사이트맵 페이지 신규 구성 · ",[32,19049,19050],{},"a1f0969"," 사이트맵에서 캠페인 제거 + 캠페인 페이지 파일 삭제 (§17, 배포 #39)",[84,19053,19054,19057],{},[32,19055,19056],{},"f7838f6"," PUSH 메시지 템플릿 관리 페이지 신규 구성 (§18, 배포 #40)",[84,19059,19060,19063],{},[32,19061,19062],{},"a836fdf"," 회원가입 페이지 5단계 마법사 신규 구성 (§19, 배포 #41)",[84,19065,19066,19069],{},[32,19067,19068],{},"07e78b3"," 메시지 상세 설정 페이지 신규 구성 (§20, 배포 #42)",[84,19071,19072,19075],{},[32,19073,19074],{},"54b53c5"," 새 비밀번호 설정 — 입력란 전체 너비 + 검증 메시지 (§21, 배포 #43)",[84,19077,19078,19081],{},[32,19079,19080],{},"265395a"," 랜딩페이지 만들기 — 목록·등록\u002F수정 폼·미리보기 신규 구성 (§22, 배포 #44)",[84,19083,19084,19086],{},[32,19085,18482],{}," 문의하기 페이지를 \u002Faccount\u002Finquiry 경로로 이동 (§23, 배포 #45)",[84,19088,19089,19092],{},[32,19090,19091],{},"83c4c37"," 나의 페이지 섹션 + 크레딧 충전 플로우 신규 구성 (§24, 배포 #46)",[84,19094,19095,19098],{},[32,19096,19097],{},"malgn-noti-api: 677dffa"," 루트(\u002F) 요청을 API 문서(\u002Fdoc)로 302 리다이렉트 (§25, API 배포)",[76,19100,16756],{"id":16755},[81,19102,19103,19106],{},[84,19104,19105],{},"발신 정보 카테고리 6개 페이지(발신 번호·RCS 브랜드·이메일 도메인·PUSH 인증·발신 프로필·080 수신 거부) 모두 구성 완료. 메시지 관리·캠페인·계정\u002F문의·시스템 페이지가 핸드오프 디자인 미반영 영역으로 남음.",[84,19107,19108],{},"모든 다이얼로그 시드 데이터는 목업. 백엔드(malgn-noti-api) 연동 전이라 저장 후 새로고침하면 휘발됨.",{"title":4208,"searchDepth":4209,"depth":4209,"links":19110},[19111,19112,19113,19114,19115,19116,19117,19118,19119,19120,19121,19122,19123,19124,19125,19126,19127,19128,19129,19130,19131,19132,19133,19134,19135,19136,19137,19145],{"id":10635,"depth":4212,"text":10636},{"id":16905,"depth":4212,"text":16906},{"id":16963,"depth":4212,"text":16964},{"id":16981,"depth":4212,"text":16982},{"id":16993,"depth":4212,"text":16994},{"id":17004,"depth":4212,"text":17005},{"id":17040,"depth":4212,"text":17041},{"id":17189,"depth":4212,"text":17190},{"id":17228,"depth":4212,"text":17229},{"id":17363,"depth":4212,"text":17364},{"id":17412,"depth":4212,"text":17413},{"id":17525,"depth":4212,"text":17526},{"id":17592,"depth":4212,"text":17593},{"id":17696,"depth":4212,"text":17697},{"id":17752,"depth":4212,"text":17753},{"id":17835,"depth":4212,"text":17836},{"id":17896,"depth":4212,"text":17897},{"id":17967,"depth":4212,"text":17968},{"id":18033,"depth":4212,"text":18034},{"id":18098,"depth":4212,"text":18099},{"id":18217,"depth":4212,"text":18218},{"id":18282,"depth":4212,"text":18283},{"id":18332,"depth":4212,"text":18333},{"id":18419,"depth":4212,"text":18420},{"id":18514,"depth":4212,"text":18515},{"id":18653,"depth":4212,"text":18654},{"id":10916,"depth":4212,"text":10916,"children":19138},[19139,19140,19141,19142,19143,19144],{"id":18716,"depth":4209,"text":18717},{"id":18740,"depth":4209,"text":18741},{"id":18775,"depth":4209,"text":18776},{"id":18805,"depth":4209,"text":18806},{"id":7855,"depth":4209,"text":7855},{"id":16672,"depth":4209,"text":16672},{"id":16755,"depth":4212,"text":16756},{},"\u002Fhistory\u002Fhistory.20260521",{"title":16826,"description":4208},"history\u002Fhistory.20260521","MOYi646vq8O49BBw-EIu5FpFFY0nQ5KEEuA3fPAGzQo",{"id":19152,"title":19153,"body":19154,"description":4208,"extension":4257,"meta":20042,"navigation":4259,"path":20043,"seo":20044,"stem":20045,"__hash__":20046},"docs\u002Fhistory\u002Fhistory.20260522.md","2026-05-22 — 나의 페이지 화면 신규 구성 (비밀번호·보안로그인·멀티 계정·계약 관리·크레딧·문의)",{"type":8,"value":19155,"toc":20023},[19156,19159,19161,19231,19235,19273,19277,19300,19304,19328,19332,19420,19424,19431,19435,19469,19473,19537,19541,19611,19615,19646,19650,19706,19710,19737,19741,19788,19792,19840,19844,19918,19920,20003,20005],[11,19157,19153],{"id":19158},"_2026-05-22-나의-페이지-화면-신규-구성-비밀번호보안로그인멀티-계정계약-관리크레딧문의",[76,19160,10636],{"id":10635},[18,19162,19163,19164,19167,19168,19171,19172,19175,19176,19179,19180,4536,19183,19186,19187,4536,19190,19193,19194,19197,19198,19201,19202,19205,19206,19209,19210,19213,19214,19217,19218,6100,19221,19223,19224,19226,19227,19230],{},"나의 페이지 좌측 메뉴 중 placeholder 상태였던 4개 라우트를 신규 구성 — ",[21,19165,19166],{},"비밀번호 변경","(8자+특수문자 검증·표시 토글), ",[21,19169,19170],{},"보안로그인 설정","(사용 토글 + 이메일\u002F휴대전화 인증 방식), ",[21,19173,19174],{},"멀티 계정 추가","(본인 인증 안내 표·인증 내역·휴대폰 본인 인증 모달 연동), ",[21,19177,19178],{},"계약 관리","(상태별 계약 카드 4종·가입서류 첨부 3종) — 하고, 계약 관리에는 ",[21,19181,19182],{},"계약서 확인 모달",[21,19184,19185],{},"3스텝 계약 체결 위저드","(약관 열람 + 캔버스 전자서명)·",[21,19188,19189],{},"업로드 안내 모달",[21,19191,19192],{},"첨부 서류 미리보기 모달","(업로드 PDF iframe 렌더링)을 추가, 갱신 계약 체결 시 기존 계약 만료 처리까지 구현하여 Cloudflare Pages에 배포 (#47). 이어서 ",[21,19195,19196],{},"크레딧 관리","(보유 크레딧 카드·A형 검색 필터·내역 테이블·기간 프리셋 자동 날짜·페이지바·영수증 모달)와 ",[21,19199,19200],{},"나의 문의","(상태 집계·목록·더보기·⋮ 삭제 메뉴·문의 상세 댓글 스레드)를 신규 구성하고, 문의하기 페이지에 나의 페이지 LNB를 추가하여 재배포 (#48). 또한 멀티 계정 추가의 휴대폰 본인 인증을 ",[21,19203,19204],{},"서비스 담당자 초대 플로우","(초대 모달 + ",[32,19207,19208],{},"\u002Finvite"," 담당자 등록 페이지)로 전환하고, ",[21,19211,19212],{},"문의 등록 완료 페이지","를 구현하여 재배포 (#49). 이어 ",[21,19215,19216],{},"사이트맵을 현행화","(나의 페이지 라우트 분리·랜딩페이지·서비스 담당자 등록 반영)하여 재배포 (#50). 이어서 화면별 사용법을 담은 ",[21,19219,19220],{},"운영 가이드 페이지",[32,19222,5744],{},")를 신규 구성하고 GNB에 연결하여 재배포 (#51). 마지막으로 ",[32,19225,4361],{}," 경로를 ",[21,19228,19229],{},"비로그인 공개 랜딩 페이지","(히어로·5채널·장점·채널 단가 비교·CTA)로 교체하여 재배포 (#52).",[76,19232,19234],{"id":19233},"_1-비밀번호-변경-페이지","1. 비밀번호 변경 페이지",[81,19236,19237,19248],{},[84,19238,19239,19242,19243,4339,19245,4703],{},[21,19240,19241],{},"account\u002Fpassword.vue",": placeholder → ",[32,19244,5836],{},[32,19246,19247],{},"AppPasswordChangePanel",[84,19249,19250,19252,19253,19256,19257],{},[21,19251,19247],{},"(신규): 회원 정보 변경 페이지와 동일한 한 줄 폼(",[32,19254,19255],{},".ms-row"," 그리드, 라벨 150px).\n",[81,19258,19259,19265,19268],{},[84,19260,19261,19262,19264],{},"현재 비밀번호(라벨 아래 ",[32,19263,17628],{}," 링크) · 새 비밀번호 · 새 비밀번호 확인 — 모두 표시\u002F숨김 토글.",[84,19266,19267],{},"검증: 8자 이상 + 특수문자 포함, 현재 비밀번호와 동일 금지, 확인 불일치 시 \"비밀번호가 일치하지 않습니다.\".",[84,19269,19270,19271,16954],{},"검증 통과 시에만 ",[32,19272,18582],{},[76,19274,19276],{"id":19275},"_2-보안로그인-설정-페이지","2. 보안로그인 설정 페이지",[81,19278,19279,19289],{},[84,19280,19281,19242,19284,4339,19286,4703],{},[21,19282,19283],{},"account\u002Fsecurity.vue",[32,19285,5836],{},[32,19287,19288],{},"AppSecurityLoginPanel",[84,19290,19291,19293,19294,19297,19298,16954],{},[21,19292,19288],{},"(신규): 보안로그인 사용 여부 ",[32,19295,19296],{},".seg"," 토글(사용안함\u002F사용), 사용 시 인증 방식 카드 선택(이메일 인증 \u002F 휴대전화 인증). 변경 사항이 있을 때만 ",[32,19299,18582],{},[76,19301,19303],{"id":19302},"_3-멀티-계정-추가-페이지","3. 멀티 계정 추가 페이지",[81,19305,19306,19316],{},[84,19307,19308,19242,19311,4339,19313,4703],{},[21,19309,19310],{},"account\u002Fmulti.vue",[32,19312,5836],{},[32,19314,19315],{},"AppMultiAccountPanel",[84,19317,19318,19320,19321,19324,19325,19327],{},[21,19319,19315],{},"(신규): 본인 인증 안내 섹션(안내 불릿 + 인증 방법 표 — \"사업자 회원\" rowspan 병합) + 본인 인증 내역 표(상태 배지). \"본인 인증 안내\" 헤더 우측에 ",[32,19322,19323],{},"휴대폰 본인 인증(필요 서류 첨부)"," 버튼 — ",[32,19326,18595],{}," 연동, 인증 완료 시 내역에 \"승인 대기\" 행 추가.",[76,19329,19331],{"id":19330},"_4-계약-관리-페이지","4. 계약 관리 페이지",[81,19333,19334,19344,19388,19394,19404,19410],{},[84,19335,19336,19242,19339,4339,19341,4703],{},[21,19337,19338],{},"account\u002Fcontract.vue",[32,19340,5836],{},[32,19342,19343],{},"AppContractPanel",[84,19345,19346,19348,19349],{},[21,19347,19343],{},"(신규):\n",[81,19350,19351,19373,19383],{},[84,19352,19353,19356,19357,19360,19361,19364,19365,19368,19369,19372],{},[21,19354,19355],{},"이용계약 체결",": 상태별 계약 카드 — 최초계약(",[32,19358,19359],{},"info",")·체결완료(",[32,19362,19363],{},"success",")·계약갱신(",[32,19366,19367],{},"warning",")·만료(",[32,19370,19371],{},"expired",", 회색). 카드 배경을 시맨틱 토큰으로 구분. 계약서 확인 \u002F 계약체결하기 버튼.",[84,19374,19375,19378,19379,19382],{},[21,19376,19377],{},"가입서류 첨부",": 사업자등록증(필수) · 대부업등록증 · 지급이행보증보험증권(해당업체 체크박스로 인터페이스 활성). 업로드 버튼은 헤더 행에 배치, ",[32,19380,19381],{},"\u003Cinput type=\"file\">"," PDF·10MB 검증.",[84,19384,19385,4703],{},[21,19386,19387],{},"갱신 계약 체결 시 기존 계약 일괄 만료 처리",[84,19389,19390,19393],{},[21,19391,19392],{},"AppContractViewDialog","(신규): 계약서 확인 모달 — 회색 배경 위 흰색 계약서 카드, 제1~3조 미리보기 + 회사\u002F이용자 서명란.",[84,19395,19396,19399,19400,19403],{},[21,19397,19398],{},"AppContractSignDialog","(신규): 계약 체결 위저드 — 3스텝 인디케이터, STEP 1·2 약관 전문 열람(끝까지 스크롤해야 \"확인하였음\" 활성), STEP 3 전자 서명\u002F공인인증서 탭 + ",[32,19401,19402],{},"\u003Ccanvas>"," 서명 패드, 진행률 바, 서명 완료 화면.",[84,19405,19406,19409],{},[21,19407,19408],{},"AppUploadGuideDialog","(신규): 업로드 안내 모달 — 확인 시 숨겨진 파일 입력을 프로그래밍 클릭.",[84,19411,19412,19415,19416,19419],{},[21,19413,19414],{},"AppFilePreviewDialog","(신규): 첨부 서류 미리보기 모달 — 업로드한 PDF는 ",[32,19417,19418],{},"\u003Ciframe>"," blob URL로 원본 렌더링, 모달 폭 900px·고정 높이 880px.",[76,19421,19423],{"id":19422},"_5-기타","5. 기타",[81,19425,19426],{},[84,19427,19428,19430],{},[21,19429,18578],{},": 결제 카드 관리 패널 상단에 다른 패널과 동일한 섹션 헤더(\"결제 카드 관리\" + hairline) 추가.",[76,19432,19434],{"id":19433},"_6-배포커밋","6. 배포·커밋",[81,19436,19437,19445,19463],{},[84,19438,19439,6219,19441,19444],{},[32,19440,11195],{},[32,19442,19443],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"My Page: password, security, multi-account, contract management\""," — 배포 #47.",[84,19446,15262,19447,4536,19450,4536,19453,4536,19456,19459,19460,4703],{},[32,19448,19449],{},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Faccount\u002Fpassword",[32,19451,19452],{},"\u002Faccount\u002Fsecurity",[32,19454,19455],{},"\u002Faccount\u002Fmulti",[32,19457,19458],{},"\u002Faccount\u002Fcontract"," 모두 200, alias ",[32,19461,19462],{},"https:\u002F\u002F688484ab.malgn-noti.pages.dev",[84,19464,15276,19465,19468],{},[32,19466,19467],{},"33e0804 나의 페이지 4개 화면 구현 — 비밀번호 변경·보안로그인·멀티 계정·계약 관리"," (13 files, +2738 −4).",[76,19470,19472],{"id":19471},"_7-크레딧-관리-페이지-7-배포-48","7. 크레딧 관리 페이지 (§7, 배포 #48)",[81,19474,19475,19485,19531],{},[84,19476,19477,19242,19480,4339,19482,4703],{},[21,19478,19479],{},"account\u002Fcredit.vue",[32,19481,5836],{},[32,19483,19484],{},"AppCreditPanel",[84,19486,19487,19348,19489],{},[21,19488,19484],{},[81,19490,19491,19500,19517,19523],{},[84,19492,19493,19496,19497,19499],{},[21,19494,19495],{},"보유 크레딧"," — 회색 카드(아바타·보유 크레딧·크레딧 충전 버튼) + 통계 3종(총 충전·보너스·이번 달 사용). 크레딧 단위 코인(",[32,19498,17127],{},") 마크.",[84,19501,19502,19505,19506,19508,19509,6100,19511,19513,19514,19516],{},[21,19503,19504],{},"A 테이블 스타일"," — 전역 ",[32,19507,6190],{},"(기간 프리셋·날짜 범위·구분 select·내용 검색·초기화\u002F검색하기) + ",[32,19510,6298],{},[32,19512,6293],{}," 총 N건·새로고침 \u002F 페이지 크기) + ",[32,19515,6171],{}," 테이블.",[84,19518,19519,19522],{},[21,19520,19521],{},"기간 프리셋","(오늘\u002F1주일\u002F이번 달\u002F3개월) 클릭 시 시작·마감일 자동 설정.",[84,19524,19525,6100,19528,19530],{},[21,19526,19527],{},"페이지바",[32,19529,17270],{},") + 충전\u002F차감 +\u002F- 표시·영수증 배지.",[84,19532,19533,19536],{},[21,19534,19535],{},"AppReceiptDialog","(신규): 크레딧 충전 영수증 모달 — 거래 정보·결제 금액(공급가액·부가세 역산)·공급자 정보.",[76,19538,19540],{"id":19539},"_8-나의-문의-목록상세-8-배포-48","8. 나의 문의 — 목록·상세 (§8, 배포 #48)",[81,19542,19543,19553,19570,19580,19589,19603],{},[84,19544,19545,19242,19548,4339,19550,4703],{},[21,19546,19547],{},"account\u002Finquiries\u002Findex.vue",[32,19549,5836],{},[32,19551,19552],{},"AppInquiryListPanel",[84,19554,19555,19557,19558,19561,19562,19564,19565,6100,19567,19569],{},[21,19556,19552],{},"(신규): 상태 집계 카드 3종(답변대기·답변중·답변완료, 연한 톤 칩), 제목·내용 검색, 문의 카드 목록(상태 배지·채널·본문 2줄 말줄임·메타), 더보기, 헤더 우측 ",[32,19559,19560],{},"문의하기"," 버튼. ",[32,19563,17159],{}," 드롭다운 — ",[32,19566,17434],{},[32,19568,6117],{}," 확인 후 제거).",[84,19571,19572,19242,19575,4339,19577,4703],{},[21,19573,19574],{},"account\u002Finquiries\u002Fdetail.vue",[32,19576,5836],{},[32,19578,19579],{},"AppInquiryDetailPanel",[84,19581,19582,19584,19585,19588],{},[21,19583,19579],{},"(신규): 문의 헤더(상태·채널·제목·메타), 문의 내용 박스, 첨부파일, 댓글\u002F대댓글 스레드(",[32,19586,19587],{},"@멘션"," 강조).",[84,19590,19591,19594,19595,19597,19598,15217,19601,16954],{},[21,19592,19593],{},"account\u002Finquiry\u002Findex.vue","(문의하기): ",[32,19596,5836],{},"로 감싸 LNB 추가, ",[32,19599,19600],{},"active-path=\"\u002Faccount\u002Finquiries\"",[32,19602,19200],{},[84,19604,19605,47,19607,19610],{},[21,19606,5836],{},[32,19608,19609],{},"activePath"," prop 추가 — 라우트와 메뉴 경로가 다른 페이지에서 활성 메뉴 지정.",[76,19612,19614],{"id":19613},"_9-배포커밋-48","9. 배포·커밋 (#48)",[81,19616,19617,19625,19640],{},[84,19618,19619,6219,19621,19624],{},[32,19620,11195],{},[32,19622,19623],{},"npx wrangler@4 pages deploy dist ... --commit-message \"My Page: credit management + inquiry list\u002Fdetail\""," — 배포 #48.",[84,19626,15262,19627,4536,19630,4536,19632,4536,19635,19459,19637,4703],{},[32,19628,19629],{},"\u002Faccount\u002Fcredit",[32,19631,18439],{},[32,19633,19634],{},"\u002Faccount\u002Finquiries\u002Fdetail",[32,19636,16882],{},[32,19638,19639],{},"https:\u002F\u002Fca4dd0f4.malgn-noti.pages.dev",[84,19641,15276,19642,19645],{},[32,19643,19644],{},"867e1a3 나의 페이지 — 크레딧 관리·나의 문의 화면 신규 구성"," (9 files, +1704 −31).",[76,19647,19649],{"id":19648},"_10-서비스-담당자-초대-플로우-문의-등록-완료-10-배포-49","10. 서비스 담당자 초대 플로우 + 문의 등록 완료 (§10, 배포 #49)",[81,19651,19652,19664,19670,19696],{},[84,19653,19654,47,19656,16245,19658,19663],{},[21,19655,19315],{},[32,19657,19323],{},[21,19659,19660],{},[32,19661,19662],{},"서비스 담당자 초대하기",". 초대 발송 시 내역에 \"초대 발송\" 상태 행 추가.",[84,19665,19666,19669],{},[21,19667,19668],{},"AppManagerInviteDialog","(신규): 담당자 이름·이메일 입력(이메일 형식 검증) → 입력 이메일로 등록 안내 메일 발송. \"이 이메일이 로그인 아이디로 사용\" 안내.",[84,19671,19672,19675,19676,19678,19679,19682,19683,4536,19686,19689,19690,19692,19693,4703],{},[21,19673,19674],{},"invite.vue","(신규, ",[32,19677,19208],{},"): 초대받은 담당자의 ",[21,19680,19681],{},"서비스 담당자 등록 페이지"," — 초대 메일 링크 진입(",[32,19684,19685],{},"?email=",[32,19687,19688],{},"?inviter=","). 아이디(이메일) 고정, 비밀번호+확인 검증, 휴대폰 본인 인증(",[32,19691,18595],{},"), 약관 동의 → 가입 완료 화면. ",[32,19694,19695],{},"layout: blank",[84,19697,19698,19701,19702,19705],{},[21,19699,19700],{},"account\u002Finquiry\u002Fcomplete.vue",": placeholder → 문의 등록 완료 화면. 접수 결과(그린 체크) + 문의 내용 요약(유형·제목·내용) + 홈 바로가기. 작성 폼(",[32,19703,19704],{},"onSubmit",")에서 query로 데이터 전달.",[76,19707,19709],{"id":19708},"_11-배포커밋-49","11. 배포·커밋 (#49)",[81,19711,19712,19720,19731],{},[84,19713,19714,6219,19716,19719],{},[32,19715,11195],{},[32,19717,19718],{},"wrangler pages deploy dist ... --commit-message \"Service manager invite flow + inquiry complete page\""," — 배포 #49.",[84,19721,15262,19722,4536,19724,4536,19726,19459,19728,4703],{},[32,19723,19455],{},[32,19725,19208],{},[32,19727,18433],{},[32,19729,19730],{},"https:\u002F\u002F63417583.malgn-noti.pages.dev",[84,19732,15276,19733,19736],{},[32,19734,19735],{},"22bb51f 서비스 담당자 초대 플로우 + 문의 등록 완료 페이지 구현"," (5 files, +693 −21).",[76,19738,19740],{"id":19739},"_12-사이트맵-현행화-12-배포-50","12. 사이트맵 현행화 (§12, 배포 #50)",[81,19742,19743,19769,19782],{},[84,19744,19745,19747,19748,19750,19751],{},[21,19746,17867],{},": 실제 ",[32,19749,4589],{}," 라우트와 대조하여 누락분 반영.\n",[81,19752,19753,19756],{},[84,19754,19755],{},"\"계정 \u002F 문의\" → \"나의 페이지 \u002F 문의\" 그룹 개편 — 나의 페이지 좌측 메뉴 9종(회원 정보·결제 카드·비밀번호·보안로그인·멀티 계정·계약·크레딧·결제 내역·나의 문의)을 개별 라우트로 분리.",[84,19757,19758,19759,6100,19761,19763,19764,6100,19767,15971],{},"메시지 관리에 ",[32,19760,18346],{},[32,19762,18349],{},"), 인증에 ",[32,19765,19766],{},"서비스 담당자 등록",[32,19768,19208],{},[84,19770,19771,19772,6685,19774,15415,19777,15269,19779,4703],{},"배포 #50: ",[32,19773,13896],{},[32,19775,19776],{},"--commit-message \"Update sitemap with My Page routes and invite page\"",[32,19778,17936],{},[32,19780,19781],{},"https:\u002F\u002F60070560.malgn-noti.pages.dev",[84,19783,15276,19784,19787],{},[32,19785,19786],{},"7eac09c 사이트맵 현행화 — 나의 페이지 라우트·랜딩페이지·서비스 담당자 등록 반영"," (1 file, +20 −7).",[76,19789,19791],{"id":19790},"_13-운영-가이드-페이지-13-배포-51","13. 운영 가이드 페이지 (§13, 배포 #51)",[81,19793,19794,19801,19813,19821,19834],{},[84,19795,19796,19675,19798,19800],{},[21,19797,5741],{},[32,19799,5744],{},"): 화면별 사용법 운영 가이드 — sticky 목차 + 9개 섹션(시작하기·메시지 발송·발송 조회\u002F통계·주소록·발신 정보·메시지 관리·크레딧 충전·나의 페이지·문의). 섹션별 관련 화면 칩(해당 화면 링크)·번호별 단계 안내·TIP 콜아웃.",[84,19802,19803,19805,19806,19808,19809,6219,19811,16248],{},[21,19804,11316],{},": 상단 메뉴 ",[32,19807,1387],{}," 항목을 ",[32,19810,3846],{},[32,19812,5744],{},[84,19814,19815,19817,19818,14770],{},[21,19816,17867],{},": 마지막 그룹 \"디자인\" → \"가이드 \u002F 레퍼런스\"로 개편, ",[32,19819,19820],{},"운영 가이드",[84,19822,19823,19824,6685,19826,15415,19829,15269,19831,4703],{},"배포 #51: ",[32,19825,13896],{},[32,19827,19828],{},"--commit-message \"Add operations guide page and link from GNB\"",[32,19830,5744],{},[32,19832,19833],{},"https:\u002F\u002F9379d4c0.malgn-noti.pages.dev",[84,19835,15276,19836,19839],{},[32,19837,19838],{},"8d6cc76 운영 가이드 페이지 신규 + GNB 연결"," (3 files, +446 −3).",[76,19841,19843],{"id":19842},"_14-비로그인-메인-랜딩-페이지-14-배포-52","14. 비로그인 메인 랜딩 페이지 (§14, 배포 #52)",[81,19845,19846,19874,19882,19897,19912],{},[84,19847,19848,6100,19851,19853,19854,19856,19857,4473,19859,19862,19863,19866,19867,19870,19871,19873],{},[21,19849,19850],{},"index.vue",[32,19852,4361],{},"): 기존 ",[32,19855,11278],{}," 리다이렉트 → 공개 마케팅 랜딩 페이지로 교체(",[32,19858,19695],{},[32,19860,19861],{},"auth: false","). 마케팅 헤더(로고 + 로그인\u002F무료 시작) · 히어로 · 5채널 소개 · ",[21,19864,19865],{},"맑은 메시징의 장점","(4종) · ",[21,19868,19869],{},"채널별 단가 비교","(타사 vs 맑은 메시징, 경쟁사 콘셉트를 ink\u002Faccent 토큰으로 재구성) · 마무리 CTA · 푸터. 로그인 시 ",[32,19872,11278],{}," 이동은 실제 인증 연동 시 활성화(주석).",[84,19875,19876,19878,19879,19881],{},[21,19877,5741],{},": 운영 가이드를 ",[32,19880,19695],{},"로 — 앱 GNB 제거, 로고만 있는 sticky 상단 바 + 간단 푸터. \"운영 가이드\" 제목을 헤더 바로 이동, 단일 루트 래핑으로 sticky 헤더·목차 고정 보정.",[84,19883,19884,47,19886,19888,19889,19892,19893,19896],{},[21,19885,11316],{},[32,19887,1387],{}," 메뉴를 새 창(",[32,19890,19891],{},"target=\"_blank\"",")으로 열도록 ",[32,19894,19895],{},"newWindow"," 옵션 추가.",[84,19898,19899,19900,6685,19902,15415,19905,4536,19907,15269,19909,4703],{},"배포 #52: ",[32,19901,13896],{},[32,19903,19904],{},"--commit-message \"Public landing page with benefits and price comparison\"",[32,19906,4361],{},[32,19908,5744],{},[32,19910,19911],{},"https:\u002F\u002Fa36c7026.malgn-noti.pages.dev",[84,19913,15276,19914,19917],{},[32,19915,19916],{},"8120ad0 비로그인 메인 랜딩 페이지 신규 + 운영 가이드 페이지 정리"," (3 files, +793 −50).",[76,19919,10916],{"id":10916},[81,19921,19922,19951,19964,19987,19998],{},[84,19923,14446,19924,4536,19926,4536,19928,4536,19930,4536,19932,4536,19934,4536,19936,4536,19938,4536,19940,4536,19942,4536,19944,4536,19946,4536,19948,19950],{},[32,19925,19247],{},[32,19927,19288],{},[32,19929,19315],{},[32,19931,19343],{},[32,19933,19392],{},[32,19935,19398],{},[32,19937,19408],{},[32,19939,19414],{},[32,19941,19484],{},[32,19943,19535],{},[32,19945,19552],{},[32,19947,19579],{},[32,19949,19668],{}," (13종).",[84,19952,19953,19954,6100,19956,19958,19959,6100,19961,19963],{},"신규 페이지: ",[32,19955,19674],{},[32,19957,19208],{}," 서비스 담당자 등록), ",[32,19960,5741],{},[32,19962,5744],{}," 운영 가이드).",[84,19965,13651,19966,4536,19969,4536,19972,19975,19976,4473,19978,19980,19981,19983,19984,19986],{},[32,19967,19968],{},"account\u002F{password,security,multi,contract,credit}.vue",[32,19970,19971],{},"account\u002Finquiries\u002F{index,detail}.vue",[32,19973,19974],{},"account\u002Finquiry\u002F{index,complete}.vue","(placeholder → 화면 구현), ",[32,19977,18578],{},[32,19979,5836],{},"(activePath), ",[32,19982,17867],{},"(현행화), ",[32,19985,11316],{},"(운영가이드 연결).",[84,19988,19989,19990,6100,19992,19994,19995,19997],{},"신규 페이지(추가): ",[32,19991,19850],{},[32,19993,4361],{}," 공개 랜딩) — 기존 ",[32,19996,11278],{}," 리다이렉트 대체.",[84,19999,20000,20001,4703],{},"배포 #47~#52, 최종 alias ",[32,20002,19911],{},[76,20004,12598],{"id":13153},[81,20006,20007,20014,20020],{},[84,20008,20009,20010,20013],{},"나의 페이지 남은 placeholder: ",[32,20011,20012],{},"\u002Faccount\u002Fbilling","(결제 내역) — 화면 미구성.",[84,20015,20016,20017,20019],{},"계약서 본문·인증 안내 문구·크레딧 내역·문의 데이터·초대 메일 발송 등은 목업 — 백엔드(",[32,20018,50],{},") 연동 시 교체.",[84,20021,20022],{},"전자서명 캔버스는 클라이언트 드로잉만 — 서명 이미지 저장\u002F전송 미구현.",{"title":4208,"searchDepth":4209,"depth":4209,"links":20024},[20025,20026,20027,20028,20029,20030,20031,20032,20033,20034,20035,20036,20037,20038,20039,20040,20041],{"id":10635,"depth":4212,"text":10636},{"id":19233,"depth":4212,"text":19234},{"id":19275,"depth":4212,"text":19276},{"id":19302,"depth":4212,"text":19303},{"id":19330,"depth":4212,"text":19331},{"id":19422,"depth":4212,"text":19423},{"id":19433,"depth":4212,"text":19434},{"id":19471,"depth":4212,"text":19472},{"id":19539,"depth":4212,"text":19540},{"id":19613,"depth":4212,"text":19614},{"id":19648,"depth":4212,"text":19649},{"id":19708,"depth":4212,"text":19709},{"id":19739,"depth":4212,"text":19740},{"id":19790,"depth":4212,"text":19791},{"id":19842,"depth":4212,"text":19843},{"id":10916,"depth":4212,"text":10916},{"id":13153,"depth":4212,"text":12598},{},"\u002Fhistory\u002Fhistory.20260522",{"title":19153,"description":4208},"history\u002Fhistory.20260522","ibopKDzkEHipuhTOsETmVaOEnYpxVO_qdgqUFKLURjg",{"id":20048,"title":20049,"body":20050,"description":4208,"extension":4257,"meta":24450,"navigation":4259,"path":24451,"seo":24452,"stem":24453,"__hash__":24454},"docs\u002Fhistory\u002Fhistory.20260526.md","2026-05-26 — malgn-noti-api 데이터 모델·초기 DDL·Hyperdrive 연결·첫 프로덕션 배포 + 운영 컨벤션 명문화 + malgn-noti 배포 #53 + Aurora DDL 적용 + 기본 CRUD API 골격 + \u002Fdoc + 두 번째 프로덕션 배포",{"type":8,"value":20051,"toc":24326},[20052,20055,20057,20142,20149,20315,20322,20356,20363,20375,20479,20486,20548,20556,20649,20653,20695,20699,20739,20743,20750,20790,20794,20822,20826,20846,20852,20858,20871,20934,20944,20948,20963,21036,21040,21047,21058,21143,21147,21154,21237,21240,21297,21301,21342,21346,21373,21381,21388,21395,21404,21507,21560,21570,21573,21600,21610,21621,21625,21656,21669,21676,21682,21713,21719,21736,21742,21747,21809,21814,21855,21862,21886,21893,21993,21996,22043,22047,22050,22077,22080,22105,22115,22119,22133,22140,22255,22259,22282,22286,22292,22300,22304,22311,22318,22324,22328,22344,22351,22447,22450,22454,22463,22470,22473,22479,22482,22544,22548,22737,22741,22770,22774,22777,22806,22810,22846,22859,22862,22868,22874,22920,22926,22929,22932,22976,22979,22989,22992,23005,23008,23028,23031,23039,23045,23048,23051,23073,23079,23116,23119,23137,23140,23143,23154,23158,23161,23165,23182,23186,23205,23209,23326,23331,23335,23342,23346,23357,23364,23368,23404,23408,23430,23434,23458,23470,23474,23484,23509,23513,23516,23572,23576,23582,23586,23599,23606,23722,23726,23737,23741,23747,23751,23768,23772,23788,23811,23817,23824,23827,23831,23903,23907,23940,23944,23977,23981,23993,23997,24006,24010,24049,24053,24115,24125,24129,24141,24165,24168,24224,24228,24235,24239,24323],[11,20053,20049],{"id":20054},"_2026-05-26-malgn-noti-api-데이터-모델초기-ddlhyperdrive-연결첫-프로덕션-배포-운영-컨벤션-명문화-malgn-noti-배포-53-aurora-ddl-적용-기본-crud-api-골격-doc-두-번째-프로덕션-배포",[76,20056,10636],{"id":10635},[18,20058,20059,11686,20061,20064,20065,20068,20069,20072,20073,20076,20077,20080,20081,20084,20085,4361,20088,20090,20091,20094,20095,20098,20099,20102,20103,20109,20110,20113,20114,20117,20118,20121,20122,20127,20128,20130,20131,5069,20134,20137,20138,20141],{},[32,20060,50],{},[21,20062,20063],{},"데이터 모델링부터 첫 프로덕션 배포까지"," 한 흐름으로 진행 — 사용자단 화면·소스·MD를 읽고 49개 테이블 데이터 모델 작성(",[21,20066,20067],{},"TB_"," 접두어, ",[32,20070,20071],{},"company_id"," FK, ",[32,20074,20075],{},"status INT"," 1\u002F0\u002F-1 + ",[32,20078,20079],{},"*_state VARCHAR"," 분리, ",[32,20082,20083],{},"*_yn CHAR(1)"," Y\u002FN, ",[32,20086,20087],{},"loginid",[32,20089,12969],{}," 분리), 시각 ERD를 Mermaid 9종으로 작성, 발송량 시나리오 분석 후 ",[21,20092,20093],{},"월 RANGE 파티셔닝 + Hot\u002FWarm\u002FCold + R2 오프로드"," 확장성 전략을 정본 §13 + 별도 ",[32,20096,20097],{},"SCALABILITY.md","로 정리, 49 테이블 초기 마이그레이션 SQL(파티션 5종 + ",[32,20100,20101],{},"raw_payload_r2_key"," 포함)을 작성, ",[21,20104,20105,20106],{},"Hyperdrive(MySQL) 바인딩 ",[32,20107,20108],{},"a2ba4efe7421464da1d5ff5e620b33a3"," 연결 + ",[32,20111,20112],{},"drizzle-orm\u002Fmysql2"," 셋업 + ",[32,20115,20116],{},"\u002Fhealth\u002Fdb"," 헬스 체크 + ",[32,20119,20120],{},"wrangler dev --remote"," 로 로컬에서도 실제 Aurora MySQL 8.0.42 응답 확인, ",[21,20123,20124,20126],{},[32,20125,11267],{}," 프로덕션 첫 배포"," 완료 (모든 엔드포인트 200, ",[32,20129,20116],{}," mysql_version 8.0.42). 이어 ",[32,20132,20133],{},"malgn-noti-api\u002FCLAUDE.md §8.1",[21,20135,20136],{},"배포·Git·작업 이력 운영 컨벤션을 명문화","하여, 프론트와 동일한 디스플린(typecheck → 배포 → 검증 → 커밋·푸시·history)을 백엔드에서도 강제하도록 정리. 작업 이력은 ",[32,20139,20140],{},"malgn-noti\u002Fdoc\u002Fhistory\u002F","가 3 레포 공통 정본임을 §8.1에 못박음.",[76,20143,20145,20146,4343],{"id":20144},"_1-데이터-모델-malgn-noti-apidocdata-modelmd","1. 데이터 모델 (",[32,20147,20148],{},"malgn-noti-api\u002Fdoc\u002FDATA-MODEL.md",[81,20150,20151,20167,20173,20298,20312],{},[84,20152,20153,20154,20156,20157,20160,20161,4473,20164,4703],{},"입력: ",[32,20155,7664],{},"의 화면 73개·",[32,20158,20159],{},"app\u002Ftypes\u002F*","·목업 데이터, ",[32,20162,20163],{},"malgn-noti-api\u002FCLAUDE.md §5 1차 모델",[32,20165,20166],{},"doc\u002FDESIGN.md §14",[84,20168,20169,20172],{},[21,20170,20171],{},"49개 테이블"," \u002F 9개 도메인. Aurora MySQL 8.0 \u002F Drizzle 대상.",[84,20174,20175,20176],{},"공통 규칙:\n",[81,20177,20178,20188,20201,20211,20230,20250,20275,20285],{},[84,20179,20180,6685,20185,5606],{},[21,20181,20182,20184],{},[32,20183,20067],{}," 접두어 + 대문자 스네이크",[32,20186,20187],{},"TB_DISPATCH_REQUEST",[84,20189,20190,20191,20194,20195,20197,20198,5606],{},"컬럼 ",[32,20192,20193],{},"snake_case",", 고객사 FK는 ",[32,20196,20071],{},". 멀티 테넌트 격리(",[32,20199,20200],{},"§1.2",[84,20202,20203,20204,20207,20208,4703],{},"시간 ",[32,20205,20206],{},"DATETIME"," UTC. PK ",[32,20209,20210],{},"BIGINT UNSIGNED AUTO_INCREMENT",[84,20212,20213,20214,51,20217,4361,20220,20223,20224,6685,20227,5606],{},"불리언 ",[32,20215,20216],{},"CHAR(1)",[32,20218,20219],{},"'Y'",[32,20221,20222],{},"'N'",", 컬럼명 ",[32,20225,20226],{},"*_yn",[32,20228,20229],{},"§1.11",[84,20231,20232,89,20237,20239,20240,20242,20243,20246,20247,5606],{},[21,20233,20234],{},[32,20235,20236],{},"status INT NOT NULL DEFAULT 1",[32,20238,4636],{},"=정상\u002F",[32,20241,5354],{},"=중지\u002F",[32,20244,20245],{},"-1","=삭제 (",[32,20248,20249],{},"§1.12",[84,20251,20252,20253,20258,20259,4361,20262,4361,20265,4361,20268,4361,20271,20274],{},"다단계 업무 상태는 ",[21,20254,20255],{},[32,20256,20257],{},"*_state VARCHAR(20)"," 으로 분리 — ",[32,20260,20261],{},"dispatch_state",[32,20263,20264],{},"review_state",[32,20266,20267],{},"approval_state",[32,20269,20270],{},"pay_state",[32,20272,20273],{},"answer_state"," 등 16개.",[84,20276,20277,20280,20281,20284],{},[32,20278,20279],{},"enum"," 회피 → ",[32,20282,20283],{},"VARCHAR"," + Zod 검증.",[84,20286,20287,20288,4473,20291,4473,20294,20297],{},"JSON 컬럼(",[32,20289,20290],{},"spec",[32,20292,20293],{},"message_spec",[32,20295,20296],{},"nodes",")으로 채널 형상 다양성 흡수.",[84,20299,20300,89,20303,20305,20306,6251,20309,20311],{},[21,20301,20302],{},"TB_USER",[32,20304,20087],{}," (로그인 ID, ",[32,20307,20308],{},"UNIQUE(company_id, loginid)",[32,20310,12969],{}," (알림·영수증 수신) 분리.",[84,20313,20314],{},"도메인별 §3~§10 — 계정\u002F인증, 크레딧\u002F결제, 발신정보, 주소록, 템플릿, 발송·Flow·캠페인, 이력\u002FExport, 문의\u002F시스템.",[76,20316,20318,20319,4343],{"id":20317},"_2-erd-malgn-noti-apidocerdmd","2. ERD (",[32,20320,20321],{},"malgn-noti-api\u002Fdoc\u002FERD.md",[81,20323,20324,20334,20341],{},[84,20325,20326,20327,51,20330,20333],{},"Mermaid ",[32,20328,20329],{},"erDiagram",[21,20331,20332],{},"9종"," — 전체 관계 개관 1 + 도메인 8 (§2~§9).",[84,20335,20336,20337,20340],{},"모든 컬럼에 코멘트 4번째 항목 추가 — 박스 내부에 ",[32,20338,20339],{},"bigint id PK \"고객사 식별자\""," 형태로 렌더링.",[84,20342,20343,20344,20347,20348,20351,20352,20355],{},"카디널리티 ",[32,20345,20346],{},"||--o{","(1:N)·",[32,20349,20350],{},"||--||","(1:1)·",[32,20353,20354],{},"||--o|","(1:0\u002F1)·M:N(junction)·자기참조(트리·중첩답글)·복합 PK 모두 표현.",[76,20357,20359,20360,4343],{"id":20358},"_3-확장성파티셔닝-전략-malgn-noti-apidocscalabilitymd","3. 확장성·파티셔닝 전략 (",[32,20361,20362],{},"malgn-noti-api\u002Fdoc\u002FSCALABILITY.md",[18,20364,20365,20366,20374],{},"볼륨 가정(1년차 100만",[20367,20368,20369,20370,20373],"del",{},"1천만\u002F월 → 5년차 1억+\u002F월)에서 13억 행 Aurora 누적 시나리오를 분석하고, ",[21,20371,20372],{},"DDL 첫 작성 시점부터"," 적용해야 사후 마이그레이션 비용을 회피할 수 있는 결정 사항을 §1","§7로 정리.",[81,20376,20377,20408,20420,20433,20448,20454,20467],{},[84,20378,20379,89,20382,4339,20385,4339,20388,20391,20392,20395,20396,20399,20400,20403,20404,20407],{},[21,20380,20381],{},"§1 월 RANGE 파티셔닝",[32,20383,20384],{},"TB_DISPATCH_REQUEST\u002FITEM\u002FEVENT",[32,20386,20387],{},"TB_CREDIT_LEDGER",[32,20389,20390],{},"TB_AUDIT_LOG"," 5개. PK 복합 ",[32,20393,20394],{},"(id, created_at)"," (또는 ",[32,20397,20398],{},"received_at",") — MySQL 8 파티셔닝 제약. ",[32,20401,20402],{},"DROP PARTITION","으로 회수, ",[32,20405,20406],{},"DELETE"," 금지.",[84,20409,20410,20413,20414,5439,20417,20419],{},[21,20411,20412],{},"§2 Hot\u002FWarm\u002FCold"," — Hot(Aurora 90일) → Warm(Aurora 콜드 13개월) → ",[21,20415,20416],{},"Cold(R2 Parquet)",[32,20418,20402],{}," 직전 Parquet 덤프 Worker Cron.",[84,20421,20422,20429,20430,20432],{},[21,20423,20424,20425,20428],{},"§3 ",[32,20426,20427],{},"raw_payload"," R2 오프로드"," — 1 KB 미만은 인라인, 초과분은 ",[32,20431,20101],{},"로 분리. 평균 DB 부담 1\u002F10 수준.",[84,20434,20435,14164,20438,20441,20442,20447],{},[21,20436,20437],{},"§4 사전 집계",[32,20439,20440],{},"TB_DISPATCH_STAT_DAILY"," 옆에 ",[21,20443,20444],{},[32,20445,20446],{},"TB_DISPATCH_STAT_HOURLY"," 추가 (5분 주기). 시간 범위별 출처 계층화.",[84,20449,20450,20453],{},[21,20451,20452],{},"§5 인덱스·쿼리 가드"," — OFFSET 금지·커서 페이징·30일 기본 윈도우·JSON generated column.",[84,20455,20456,20459,20460,4361,20463,20466],{},[21,20457,20458],{},"§6 Aurora 토폴로지"," — Writer\u002FReader 분리, Hyperdrive 바인딩 2개(",[32,20461,20462],{},"_W",[32,20464,20465],{},"_R","), Limitless 전환 기준.",[84,20468,20469,89,20472,4473,20475,20478],{},[21,20470,20471],{},"§7 운영 트리거",[32,20473,20474],{},"DISPATCH_ITEM > 5억 행 → ClickHouse PoC",[32,20476,20477],{},"Writer CPU 60%+ → Reader 추가"," 등 임계 룰북.",[76,20480,20482,20483,4343],{"id":20481},"_4-초기-ddl-malgn-noti-apisrcdbmigrations0000_initialsql","4. 초기 DDL (",[32,20484,20485],{},"malgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0000_initial.sql",[81,20487,20488,20503,20520,20529,20532],{},[84,20489,20490,20492,20493,4473,20496,20499,20500,4703],{},[21,20491,20171],{}," — MySQL 8.0 \u002F Aurora MySQL 3 호환, ",[32,20494,20495],{},"utf8mb4_0900_ai_ci",[32,20497,20498],{},"ENGINE=InnoDB",", 모든 컬럼·테이블에 ",[32,20501,20502],{},"COMMENT",[84,20504,20505,20508,20509,20512,20513,20516,20517,20519],{},[21,20506,20507],{},"파티션 적용"," — 5개 테이블, 2026-05~2027-06 (14개월) + ",[32,20510,20511],{},"pmax",". 매월 25일 ",[32,20514,20515],{},"REORGANIZE",", 매월 1일 ",[32,20518,20402],{},"(외부 Cron Worker).",[84,20521,20522,89,20525,20528],{},[21,20523,20524],{},"§13.3 R2 오프로드 컬럼",[32,20526,20527],{},"TB_DISPATCH_EVENT.raw_payload_r2_key VARCHAR(255) NULL"," 1차 스키마에 포함.",[84,20530,20531],{},"파티션 테이블은 MySQL 제약상 FK 미사용 → application-level 정합성, 주석 명시.",[84,20533,20534,20537,20538,4361,20541,4361,20544,20547],{},[32,20535,20536],{},"drizzle.config.ts"," 신설 — ",[32,20539,20540],{},"db:introspect",[32,20542,20543],{},"db:generate",[32,20545,20546],{},"db:migrate"," 스크립트.",[76,20549,20551,20552,20555],{"id":20550},"_5-hyperdrive-연결-malgn-noti-apiwranglertoml-신규-코드","5. Hyperdrive 연결 (",[32,20553,20554],{},"malgn-noti-api\u002Fwrangler.toml"," + 신규 코드)",[81,20557,20558,20565,20589,20616,20630],{},[84,20559,20560,20561,4703],{},"사용자 제공 Hyperdrive ID: ",[21,20562,20563],{},[32,20564,20108],{},[84,20566,20567,9194,20569],{},[32,20568,11253],{},[5251,20570,20572],{"className":12038,"code":20571,"language":12040,"meta":4208,"style":4208},"[[hyperdrive]]\nbinding = \"HYPERDRIVE\"\nid = \"a2ba4efe7421464da1d5ff5e620b33a3\"\n",[32,20573,20574,20579,20584],{"__ignoreMap":4208},[7968,20575,20576],{"class":5110,"line":7970},[7968,20577,20578],{},"[[hyperdrive]]\n",[7968,20580,20581],{"class":5110,"line":4212},[7968,20582,20583],{},"binding = \"HYPERDRIVE\"\n",[7968,20585,20586],{"class":5110,"line":4209},[7968,20587,20588],{},"id = \"a2ba4efe7421464da1d5ff5e620b33a3\"\n",[84,20590,20591,89,20596,4339,20598,5439,20601,20604,20605,16120,20608,20611,20612,20615],{},[21,20592,20593],{},[32,20594,20595],{},"src\u002Fdb\u002Fclient.ts",[32,20597,20112],{},[32,20599,20600],{},"mysql2\u002Fpromise.createConnection",[32,20602,20603],{},"getDb(env, ctx)"," 요청 스코프 핸들(",[32,20606,20607],{},"ctx.waitUntil(conn.end())",[32,20609,20610],{},"pingDb(env)"," 헬스. mysql2 mixin 타입 이슈는 ",[32,20613,20614],{},"db.execute(sql\\","...`)`로 우회.",[84,20617,20618,89,20622,20625,20626,20629],{},[21,20619,20620],{},[32,20621,11243],{},[32,20623,20624],{},"GET \u002Fhealth\u002Fdb"," 추가, ",[32,20627,20628],{},"Bindings.HYPERDRIVE: Hyperdrive"," 타입 결합 (cf-typegen).",[84,20631,20632,47,20635,4473,20638,4473,20641,20644,20645,20648],{},[21,20633,20634],{},"의존성",[32,20636,20637],{},"drizzle-orm@0.36.4",[32,20639,20640],{},"mysql2@3.22.3",[32,20642,20643],{},"drizzle-kit@0.28.1",". wrangler ",[32,20646,20647],{},"4.90 → 4.94"," 업그레이드.",[76,20650,20652],{"id":20651},"_6-로컬-개발-실제-hyperdrive","6. 로컬 개발 = 실제 Hyperdrive",[81,20654,20655,20665,20686],{},[84,20656,20657,20658,20661,20662,20664],{},"로컬 ",[32,20659,20660],{},"wrangler dev"," 기본 모드는 Hyperdrive를 로컬 Postgres로 에뮬레이트하려 하므로 실패 → ",[32,20663,20120],{}," 필요.",[84,20666,20667,20668,20671,20672,20674,20675,51,20677,20680,20681,20685],{},"Hyperdrive는 4.94 시점에도 per-binding ",[32,20669,20670],{},"remote = true"," 미지원 (",[32,20673,11253],{},"에 메모만 남김) → ",[32,20676,10928],{},[32,20678,20679],{},"dev"," 스크립트를 ",[21,20682,20683],{},[32,20684,20120],{}," 로 변경.",[84,20687,20688,20690,20691,20694],{},[32,20689,11083],{}," 단독으로 ",[32,20692,20693],{},"http:\u002F\u002Flocalhost:8787"," 기동, 모든 요청이 실제 Cloudflare edge 경유 Hyperdrive → Aurora.",[76,20696,20698],{"id":20697},"_7-프로덕션-배포","7. 프로덕션 배포",[81,20700,20701,20710,20721,20730],{},[84,20702,20703,20705,20706,6685,20708,5606],{},[32,20704,10425],{}," 통과 → ",[32,20707,18672],{},[32,20709,11261],{},[84,20711,20712,20713,20717,20718,4703],{},"산출: ",[21,20714,20715],{},[32,20716,11267],{},", Version ",[32,20719,20720],{},"8b0d8674-57d0-4b00-966e-bdafc4de7a83",[84,20722,20723,20724],{},"검증:\n",[5251,20725,20728],{"className":20726,"code":20727,"language":5256},[5254],"GET \u002F             → 200 {\"name\":\"malgn-noti-api\",\"status\":\"placeholder\",\"env\":\"production\"}\nGET \u002Fhealth       → 200 {\"ok\":true,\"env\":\"production\"}\nGET \u002Fhealth\u002Fdb    → 200 {\"ok\":true,\"mysql_version\":\"8.0.42\"}   ← Aurora 응답\n",[32,20729,20727],{"__ignoreMap":4208},[84,20731,20732,20733,11686,20735,20738],{},"의미 — ",[32,20734,50],{},[21,20736,20737],{},"첫 프로덕션 배포","이자, Cloudflare Workers ↔ Hyperdrive ↔ AWS Aurora MySQL 경로가 살아 있음을 확인한 마일스톤.",[76,20740,20742],{"id":20741},"_8-산출물","8. 산출물",[237,20744,20746,20747,4343],{"id":20745},"신규-파일-malgn-noti-api","신규 파일 (",[32,20748,20749],{},"malgn-noti-api\u002F",[81,20751,20752,20758,20764,20770,20776,20780,20784],{},[84,20753,20754,20757],{},[32,20755,20756],{},"doc\u002FDATA-MODEL.md"," (49 테이블 정본)",[84,20759,20760,20763],{},[32,20761,20762],{},"doc\u002FERD.md"," (Mermaid 9 다이어그램)",[84,20765,20766,20769],{},[32,20767,20768],{},"doc\u002FSCALABILITY.md"," (§13 상세 가이드)",[84,20771,20772,20775],{},[32,20773,20774],{},"src\u002Fdb\u002Fmigrations\u002F0000_initial.sql"," (1049라인)",[84,20777,20778],{},[32,20779,20595],{},[84,20781,20782],{},[32,20783,20536],{},[84,20785,20786,20789],{},[32,20787,20788],{},"worker-configuration.d.ts"," (cf-typegen 산출)",[237,20791,20793],{"id":20792},"수정-파일","수정 파일",[81,20795,20796,20801,20808,20817],{},[84,20797,20798,20800],{},[32,20799,11253],{}," — Hyperdrive 바인딩 추가",[84,20802,20803,89,20805,20807],{},[32,20804,11243],{},[32,20806,20116],{}," 라우트 + 타입 확장",[84,20809,20810,20812,20813,20816],{},[32,20811,10928],{}," — drizzle 의존성 + ",[32,20814,20815],{},"dev: wrangler dev --remote"," + db 스크립트",[84,20818,20819],{},[32,20820,20821],{},"pnpm-lock.yaml",[237,20823,20825],{"id":20824},"커밋-malgn-noti-api","커밋 (malgn-noti-api)",[81,20827,20828,20834,20840],{},[84,20829,20830,20833],{},[32,20831,20832],{},"eecf226"," — doc: 데이터 모델 \u002F ERD \u002F 확장성 전략 정리",[84,20835,20836,20839],{},[32,20837,20838],{},"0653472"," — db: 초기 마이그레이션 0000_initial.sql + drizzle-kit 설정",[84,20841,20842,20845],{},[32,20843,20844],{},"7a17504"," — Hyperdrive(MySQL) 연결 + \u002Fhealth\u002Fdb + Drizzle 런타임 셋업",[18,20847,20848,20849,4703],{},"푸시: ",[32,20850,20851],{},"decfaf0..7a17504 → origin\u002Fmain",[76,20853,20855,20856,4343],{"id":20854},"_10-운영-컨벤션-명문화-malgn-noti-apiclaudemd-81","10. 운영 컨벤션 명문화 (",[32,20857,20133],{},[18,20859,20860,20861,20863,20864,20867,20868,20870],{},"배포 직후 사용자가 \"배포 규정은 ",[32,20862,10809],{}," 파일을 참고해 줘\"라고 명확히 짚어 — 이번 흐름이 우연이 아니라 ",[21,20865,20866],{},"명문 규정","으로 박혀야 다음에도 재현됨을 확인. ",[32,20869,10821],{},"에 §8.1을 추가하여 다음을 정리:",[81,20872,20873,20883,20907,20918],{},[84,20874,20875,20878,20879,20882],{},[21,20876,20877],{},"Git"," — 단일 main, 사용자 명시 요청 시에만 커밋·푸시, 한국어 제목 + 본문 불릿 + ",[32,20880,20881],{},"Co-Authored-By: Claude Opus 4.7 (1M context) \u003Cnoreply@anthropic.com>"," trailer, 무관 untracked 파일 끌어들이지 않음.",[84,20884,20885,89,20888,6685,20891,20894,20895,20898,20899,20901,20902,4339,20904,20906],{},[21,20886,20887],{},"배포 (Workers)",[32,20889,20890],{},"pnpm typecheck → pnpm run deploy",[32,20892,20893],{},"pnpm deploy","는 pnpm 워크스페이스 명령과 충돌하므로 ",[32,20896,20897],{},"run"," 명시), 프로덕션 URL ",[32,20900,11267],{},", 검증은 ",[32,20903,11248],{},[32,20905,20116],{},"(mysql_version 반환), DDL\u002F시드는 Worker 배포와 분리 멱등 적용.",[84,20908,20909,89,20912,13739,20914,20917],{},[21,20910,20911],{},"작업 이력",[32,20913,20140],{},[21,20915,20916],{},"3 레포 공통 정본","임을 명문화. API 변경도 같은 폴더의 그날 파일에 기록하며, 산출물 절에 별 레포 커밋 해시까지 함께 표기.",[84,20919,20920,20921,13739,20923,20925,20926,4536,20929,20625,20931,20933],{},"§8 개발 명령어 표 갱신 — ",[32,20922,11083],{},[32,20924,20120],{},"임을 반영, ",[32,20927,20928],{},"cf-typegen",[32,20930,20540],{},[32,20932,18672],{}," 명시.",[18,20935,20936,20937,20940,20941,4703],{},"산출물: ",[32,20938,20939],{},"malgn-noti-api: e09f70e docs: 배포·Git·작업 이력 운영 컨벤션 명문화 (§8.1)",". 푸시 ",[32,20942,20943],{},"7a17504..e09f70e → origin\u002Fmain",[76,20945,20947],{"id":20946},"_11-malgn-noti-프론트-배포-53-새로고침-버튼-일괄-제거","11. malgn-noti 프론트 배포 #53 — 새로고침 버튼 일괄 제거",[18,20949,20950,20951,20954,20955,20958,20959,20962],{},"§7.1 흐름대로 ",[32,20952,20953],{},"pnpm build → npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"Remove refresh buttons from list toolbars + update guide and DESIGN\""," 실행. ",[21,20956,20957],{},"Working tree에 누적된 사용자 작업분","(13개 파일)을 그대로 라이브로 올리고, 직후 ",[32,20960,20961],{},"52f653b"," 커밋으로 main을 라이브와 동기화.",[81,20964,20965,20983,20992,21001,21012,21027],{},[84,20966,20967,20968,20975,20976,4473,20979,20982],{},"변경 패턴: 발송 조회·관리·연락처·발신정보·랜딩 등 ",[21,20969,20970,20971,20974],{},"목록 페이지의 ",[32,20972,20973],{},"list-toolbar"," 새로고침 버튼","과 보조 CSS(",[32,20977,20978],{},"toolbar-sep",[32,20980,20981],{},"toolbar-refresh",")를 일괄 제거. 페이지당 평균 −27~28라인.",[84,20984,20985,20986,20988,20989,20991],{},"동시 변경: ",[32,20987,7929],{}," +162라인(가이드 확장), ",[32,20990,11891],{}," 86라인 갱신.",[84,20993,20994,20995,20997,20998,21000],{},"빌드: Nitro ",[32,20996,13302],{}," 프리셋 → ",[32,20999,10325],{},", 총 2.96 MB \u002F gzip 889 KB.",[84,21002,21003,21004,21007,21008,4703],{},"배포 URL: 프로덕션 ",[26,21005,10317],{"href":10317,"rel":21006},[30],", alias ",[26,21009,21010],{"href":21010,"rel":21011},"https:\u002F\u002F127705c3.malgn-noti.pages.dev",[30],[84,21013,13284,21014,4473,21016,4473,21018,21020,21021,6219,21024,21026],{},[32,21015,4361],{},[32,21017,16339],{},[32,21019,15817],{}," 모두 HTTP 200. 새로고침 버튼 제거 마커 확인 — ",[32,21022,21023],{},"curl -s \u002Fsender\u002Fnumbers | grep -c toolbar-refresh",[32,21025,5354],{}," (제거 확정).",[84,21028,20936,21029,21032,21033,4703],{},[32,21030,21031],{},"malgn-noti: 52f653b list 툴바 새로고침 버튼 일괄 제거 + guide \u002F DESIGN 갱신"," (13 files, +226 −297). 푸시 ",[32,21034,21035],{},"252033d..52f653b → origin\u002Fmain",[76,21037,21039],{"id":21038},"_12-aurora-mysql에-0000_initialsql-적용-49-테이블-75-파티션-라이브","12. Aurora MySQL에 0000_initial.sql 적용 — 49 테이블 + 75 파티션 라이브",[18,21041,21042,21043,21046],{},"Aurora가 SG로 Hyperdrive egress IP만 허용해 로컬 mysql CLI는 차단됨 → ",[21,21044,21045],{},"Worker 경유 마이그레이션"," 인프라를 구축하고 첫 DDL을 적용.",[237,21048,21050,21051,21054,21055,4343],{"id":21049},"_121-admin-라우트-malgn-noti-apisrcroutesadmints","12.1 ",[32,21052,21053],{},"\u002Fadmin\u002F*"," 라우트 (",[32,21056,21057],{},"malgn-noti-api\u002Fsrc\u002Froutes\u002Fadmin.ts",[81,21059,21060,21086,21098,21107],{},[84,21061,21062,21065,21066,6071,21068,21071,21072,21075,21076,21078,21079,21082,21083,4703],{},[21,21063,21064],{},"게이트",": 모든 ",[32,21067,21053],{},[32,21069,21070],{},"X-Migrate-Token"," 헤더 = ",[32,21073,21074],{},"env.MIGRATE_TOKEN"," 일치 필수. 미설정 시 라우트 전체 403. 로컬은 ",[32,21077,12527],{},"(gitignored, ",[32,21080,21081],{},"openssl rand -hex 16","로 생성), 프로덕션은 ",[32,21084,21085],{},"wrangler secret put MIGRATE_TOKEN",[84,21087,21088,89,21091,13735,21094,21097],{},[32,21089,21090],{},"GET \u002Fadmin\u002Ftables",[32,21092,21093],{},"information_schema.TABLES",[32,21095,21096],{},"TB_*"," 목록 조회.",[84,21099,21100,89,21103,21106],{},[32,21101,21102],{},"GET \u002Fadmin\u002Fpartitions",[32,21104,21105],{},"information_schema.PARTITIONS","에서 파티션 명세 조회.",[84,21108,21109,21112,21113,9194,21116],{},[32,21110,21111],{},"POST \u002Fadmin\u002Fmigrate"," — body로 SQL 텍스트를 받아 ",[21,21114,21115],{},"statement 단위로 순차 실행",[81,21117,21118,21128,21134,21137],{},[84,21119,21120,21123,21124,21127],{},[32,21121,21122],{},"--"," 주석 제거 + ",[32,21125,21126],{},";"," 줄바꿈 기준 split.",[84,21129,21130,21131,21133],{},"이미 ",[32,21132,21096],{}," 테이블이 존재하면 409 거부(실수 방지).",[84,21135,21136],{},"첫 에러에서 중단(DDL 부분 적용 위험).",[84,21138,21139,21140,4703],{},"응답: ",[32,21141,21142],{},"{ ok, statements_total, statements_succeeded, duration_ms, errors[] }",[237,21144,21146],{"id":21145},"_122-적용-절차","12.2 적용 절차",[18,21148,21149,6100,21151,21153],{},[32,21150,11083],{},[32,21152,20120],{},")로 로컬 Worker가 실제 Hyperdrive → Aurora에 접근. curl로 SQL을 본문 전달:",[5251,21155,21157],{"className":10197,"code":21156,"language":10199,"meta":4208,"style":4208},"TOKEN=$(cat .dev.vars | cut -d= -f2)\ncurl -X POST http:\u002F\u002Flocalhost:8787\u002Fadmin\u002Fmigrate \\\n  -H \"X-Migrate-Token: $TOKEN\" \\\n  -H \"Content-Type: application\u002Fsql\" \\\n  --data-binary @src\u002Fdb\u002Fmigrations\u002F0000_initial.sql\n",[32,21158,21159,21189,21205,21220,21229],{"__ignoreMap":4208},[7968,21160,21161,21164,21166,21169,21172,21175,21178,21181,21184,21187],{"class":5110,"line":7970},[7968,21162,21163],{"class":7984},"TOKEN",[7968,21165,8254],{"class":7973},[7968,21167,21168],{"class":7984},"$(",[7968,21170,21171],{"class":7980},"cat",[7968,21173,21174],{"class":7993}," .dev.vars",[7968,21176,21177],{"class":7973}," |",[7968,21179,21180],{"class":7980}," cut",[7968,21182,21183],{"class":8892}," -d=",[7968,21185,21186],{"class":8892}," -f2",[7968,21188,9563],{"class":7984},[7968,21190,21191,21194,21197,21200,21203],{"class":5110,"line":4212},[7968,21192,21193],{"class":7980},"curl",[7968,21195,21196],{"class":8892}," -X",[7968,21198,21199],{"class":7993}," POST",[7968,21201,21202],{"class":7993}," http:\u002F\u002Flocalhost:8787\u002Fadmin\u002Fmigrate",[7968,21204,10292],{"class":8892},[7968,21206,21207,21210,21213,21216,21218],{"class":5110,"line":4209},[7968,21208,21209],{"class":8892},"  -H",[7968,21211,21212],{"class":7993}," \"X-Migrate-Token: ",[7968,21214,21215],{"class":7984},"$TOKEN",[7968,21217,9848],{"class":7993},[7968,21219,10292],{"class":8892},[7968,21221,21222,21224,21227],{"class":5110,"line":4219},[7968,21223,21209],{"class":8892},[7968,21225,21226],{"class":7993}," \"Content-Type: application\u002Fsql\"",[7968,21228,10292],{"class":8892},[7968,21230,21231,21234],{"class":5110,"line":8291},[7968,21232,21233],{"class":8892},"  --data-binary",[7968,21235,21236],{"class":7993}," @src\u002Fdb\u002Fmigrations\u002F0000_initial.sql\n",[18,21238,21239],{},"응답:",[5251,21241,21245],{"className":21242,"code":21243,"language":21244,"meta":4208,"style":4208},"language-json shiki shiki-themes github-light github-dark","{ \"ok\": true, \"statements_total\": 52, \"statements_succeeded\": 52, \"duration_ms\": 6684, \"errors\": [] }\n","json",[32,21246,21247],{"__ignoreMap":4208},[7968,21248,21249,21252,21255,21257,21260,21262,21265,21267,21270,21272,21275,21277,21279,21281,21284,21286,21289,21291,21294],{"class":5110,"line":7970},[7968,21250,21251],{"class":7984},"{ ",[7968,21253,21254],{"class":8892},"\"ok\"",[7968,21256,47],{"class":7984},[7968,21258,21259],{"class":8892},"true",[7968,21261,4473],{"class":7984},[7968,21263,21264],{"class":8892},"\"statements_total\"",[7968,21266,47],{"class":7984},[7968,21268,21269],{"class":8892},"52",[7968,21271,4473],{"class":7984},[7968,21273,21274],{"class":8892},"\"statements_succeeded\"",[7968,21276,47],{"class":7984},[7968,21278,21269],{"class":8892},[7968,21280,4473],{"class":7984},[7968,21282,21283],{"class":8892},"\"duration_ms\"",[7968,21285,47],{"class":7984},[7968,21287,21288],{"class":8892},"6684",[7968,21290,4473],{"class":7984},[7968,21292,21293],{"class":8892},"\"errors\"",[7968,21295,21296],{"class":7984},": [] }\n",[237,21298,21300],{"id":21299},"_123-검증","12.3 검증",[81,21302,21303,21314,21339],{},[84,21304,21305,6219,21307,21313],{},[32,21306,21090],{},[21,21308,21309,21310,21312],{},"49개 ",[32,21311,21096],{}," 테이블"," 전부 생성 확인.",[84,21315,21316,6219,21318,6685,21321,4536,21323,4536,21326,4536,21329,4536,21331,21333,21334,4339,21337,5606],{},[32,21317,21102],{},[21,21319,21320],{},"75개 파티션",[32,21322,20187],{},[32,21324,21325],{},"TB_DISPATCH_ITEM",[32,21327,21328],{},"TB_DISPATCH_EVENT",[32,21330,20387],{},[32,21332,20390],{}," × 각 15 파티션 = ",[32,21335,21336],{},"p202605..p202706",[32,21338,20511],{},[84,21340,21341],{},"적용 시간 6.7초.",[237,21343,21345],{"id":21344},"_124-산출물","12.4 산출물",[81,21347,21348,21354,21367],{},[84,21349,21350,21353],{},[32,21351,21352],{},"malgn-noti-api: a390f32 admin: \u002Fadmin\u002Fmigrate · \u002Fadmin\u002Ftables · \u002Fadmin\u002Fpartitions 라우트 추가"," (2 files, +163).",[84,21355,21356,21358,21359,21362,21363,21366],{},[32,21357,12527],{},"는 gitignored — ",[32,21360,21361],{},"MIGRATE_TOKEN","만 로컬 보관. 프로덕션 배포 시에는 별도로 ",[32,21364,21365],{},"wrangler secret put"," 필요(현 시점 미배포 — 라이브 Worker에는 admin 라우트 없음).",[84,21368,21369,21370,4703],{},"푸시 ",[32,21371,21372],{},"e09f70e..a390f32 → origin\u002Fmain",[237,21374,21376,21377,21380],{"id":21375},"_125-결정-admin-라우트는-로컬-전용-선택-a-유지","12.5 결정 — admin 라우트는 ",[21,21378,21379],{},"로컬 전용"," (선택 A 유지)",[18,21382,21383,21384,21387],{},"향후 0001+ 마이그레이션도 동일 방식(",[32,21385,21386],{},"pnpm dev --remote"," + curl localhost)으로 적용. 프로덕션에는 배포하지 않음. 이유: 라우트가 공개 URL에 노출되면 토큰 유출 = DB 전체 권한 탈취 위험. 마이그레이션 빈도가 낮아 로컬 적용 부담이 작음. 잦아지면 그때 별도 admin-worker 분리 또는 GitHub Actions OIDC 등 더 안전한 방식으로 전환.",[237,21389,21391,21392,4343],{"id":21390},"_126-환경-가드-추가-실수로도-프로덕션에-안-뚫리도록-63ba424","12.6 환경 가드 추가 — 실수로도 프로덕션에 안 뚫리도록 (",[32,21393,21394],{},"63ba424",[18,21396,21397,21398,21400,21401,9148],{},"토큰 게이트만으로도 보호되지만, 누군가 ",[32,21399,21085],{},"을 실수로 프로덕션에 등록하면 라우트가 살아남. 이걸 막는 ",[21,21402,21403],{},"이중 안전망",[5251,21405,21407],{"className":7962,"code":21406,"language":7964,"meta":4208,"style":4208},"admin.use('*', async (c, next) => {\n  if (c.env.APP_ENV !== 'local') {\n    return c.json({ code: 'not_found', message: 'Route not found' }, 404)\n  }\n  \u002F\u002F ... 토큰 검사 ...\n})\n",[32,21408,21409,21443,21463,21493,21498,21503],{"__ignoreMap":4208},[7968,21410,21411,21414,21417,21419,21422,21424,21426,21428,21431,21433,21436,21439,21441],{"class":5110,"line":7970},[7968,21412,21413],{"class":7984},"admin.",[7968,21415,21416],{"class":7980},"use",[7968,21418,6100],{"class":7984},[7968,21420,21421],{"class":7993},"'*'",[7968,21423,4473],{"class":7984},[7968,21425,9453],{"class":7973},[7968,21427,6685],{"class":7984},[7968,21429,21430],{"class":8939},"c",[7968,21432,4473],{"class":7984},[7968,21434,21435],{"class":8939},"next",[7968,21437,21438],{"class":7984},") ",[7968,21440,9426],{"class":7973},[7968,21442,8887],{"class":7984},[7968,21444,21445,21448,21451,21454,21457,21460],{"class":5110,"line":4212},[7968,21446,21447],{"class":7973},"  if",[7968,21449,21450],{"class":7984}," (c.env.",[7968,21452,21453],{"class":8892},"APP_ENV",[7968,21455,21456],{"class":7973}," !==",[7968,21458,21459],{"class":7993}," 'local'",[7968,21461,21462],{"class":7984},") {\n",[7968,21464,21465,21468,21471,21473,21476,21479,21482,21485,21488,21491],{"class":5110,"line":4209},[7968,21466,21467],{"class":7973},"    return",[7968,21469,21470],{"class":7984}," c.",[7968,21472,21244],{"class":7980},[7968,21474,21475],{"class":7984},"({ code: ",[7968,21477,21478],{"class":7993},"'not_found'",[7968,21480,21481],{"class":7984},", message: ",[7968,21483,21484],{"class":7993},"'Route not found'",[7968,21486,21487],{"class":7984}," }, ",[7968,21489,21490],{"class":8892},"404",[7968,21492,9563],{"class":7984},[7968,21494,21495],{"class":5110,"line":4219},[7968,21496,21497],{"class":7984},"  }\n",[7968,21499,21500],{"class":5110,"line":8291},[7968,21501,21502],{"class":8398},"  \u002F\u002F ... 토큰 검사 ...\n",[7968,21504,21505],{"class":5110,"line":8301},[7968,21506,8007],{"class":7984},[81,21508,21509,21517,21524,21530],{},[84,21510,20657,21511,5069,21513,21516],{},[32,21512,12527],{},[32,21514,21515],{},"APP_ENV=local"," 추가하여 오버라이드 (gitignored).",[84,21518,21519,21520,21523],{},"프로덕션 ",[32,21521,21522],{},"wrangler.toml [vars] APP_ENV=\"production\""," 유지 → 라우트가 무조건 404.",[84,21525,21526,21527,5606],{},"외부에서 보면 \"라우트가 존재하지 않는 것\"처럼 위장 (",[32,21528,21529],{},"{\"code\":\"not_found\"}",[84,21531,20723,21532],{},[81,21533,21534,21544,21552],{},[84,21535,21536,21539,21540,21543],{},[32,21537,21538],{},"localhost:8787\u002Fadmin\u002Ftables"," + 토큰 → ",[21,21541,21542],{},"200",", 49 tables.",[84,21545,21546,21548,21549,4703],{},[32,21547,21538],{}," 토큰 누락 → ",[21,21550,21551],{},"403",[84,21553,21554,21557,21558,4703],{},[32,21555,21556],{},"malgn-noti-api.malgnsoft.workers.dev\u002Fadmin\u002Ftables"," + 유효 토큰 → ",[21,21559,21490],{},[237,21561,21563,21564,4473,21567,4343],{"id":21562},"_127-pdf-erd를-인쇄용-pdf로-malgn-noti-apidocerdpdf-470b55a","12.7-pdf ERD를 인쇄용 PDF로 (",[32,21565,21566],{},"malgn-noti-api\u002Fdoc\u002FERD.pdf",[32,21568,21569],{},"470b55a",[18,21571,21572],{},"DDL과 동기화된 시각 ERD를 외부 공유·인쇄용 PDF로 생성:",[81,21574,21575,21581,21591,21594],{},[84,21576,21577,21580],{},[32,21578,21579],{},"@mermaid-js\u002Fmermaid-cli"," (mmdc)가 ERD.md의 9개 mermaid 코드블록을 페이지별 PDF로 렌더 (Chromium 1회 다운로드).",[84,21582,21583,21586,21587,21590],{},[32,21584,21585],{},"python3 -m pip install --user pypdf"," 후 ",[32,21588,21589],{},"PdfWriter.append","로 9 페이지를 1 파일(925 KB)로 병합.",[84,21592,21593],{},"각 페이지에서 한국어 텍스트 추출 검증 (테이블·컬럼·관계 라벨 모두 정상).",[84,21595,21596,21599],{},[32,21597,21598],{},"ERD.md §10","에 재생성 절차 명문화 — 다음 누군가 갱신할 때 막힘 없음.",[237,21601,21603,21604,6685,21607,4343],{"id":21602},"_127-마이그레이션-절차-정본-malgn-noti-apidocmigrationmd-47afe1a","12.7 마이그레이션 절차 정본 — ",[32,21605,21606],{},"malgn-noti-api\u002Fdoc\u002FMIGRATION.md",[32,21608,21609],{},"47afe1a",[18,21611,21612,21613,21616,21617,21620],{},"위 12.1~12.6의 결정·절차를 운영 문서 1개로 정리해서 정본화. 9개 섹션 — 왜 이런 절차인가(Aurora SG 제약), 사전 준비, SQL 작성 규칙, 적용 절차 step-by-step, 실패 처리 3안, FAQ 8건, 적용 이력 책임(git + history\u002F), 파티션 운영 분리, 관련 문서. ",[32,21614,21615],{},"CLAUDE.md §8","에 링크 추가, ",[32,21618,21619],{},"pnpm db:migrate","는 직접 연결 불가라 비현실적임을 표기.",[237,21622,21624],{"id":21623},"_128-다음-단계-마이그레이션-운영","12.8 다음 단계 (마이그레이션 운영)",[81,21626,21627,21640,21647],{},[84,21628,21629,21630,21633,21634,21636,21637,21639],{},"파티션 자동 운영 Cron Worker(",[32,21631,21632],{},"src\u002Fworkers\u002Fpartition-maintenance.ts",") — 매월 25일 다음 달 파티션 ",[32,21635,20515],{},", 매월 1일 13개월 전 파티션 R2 덤프 + ",[32,21638,20402],{}," (SCALABILITY §1·§2).",[84,21641,21642,21643,21646],{},"시드 데이터 ",[32,21644,21645],{},"0001_seed.sql"," (system terms, 샘플 템플릿 카탈로그).",[84,21648,21649,15217,21652,21655],{},[32,21650,21651],{},"pnpm db:introspect",[32,21653,21654],{},"src\u002Fdb\u002Fschema.ts"," 자동 생성.",[76,21657,21659,21660,4473,21663,4473,21666,4343],{"id":21658},"_13-기본-crud-api-골격-hono-drizzle-zod-a146e81-8128468-2b6f720","13. 기본 CRUD API 골격 — Hono + Drizzle + Zod (",[32,21661,21662],{},"a146e81",[32,21664,21665],{},"8128468",[32,21667,21668],{},"2b6f720",[18,21670,21671,21672,21675],{},"49 테이블 전부 CRUD는 과하므로, ",[21,21673,21674],{},"재사용 가능한 인프라(errors\u002Fpagination\u002Fauth\u002Fschema) + 가장 활용도 높은 도메인(주소록·발신번호)을 패턴 사례로"," 완성. 나머지 도메인은 동일 패턴 복제.",[237,21677,21679,21680,4343],{"id":21678},"_131-사전-픽스-a146e81","13.1 사전 픽스 (",[32,21681,21662],{},[81,21683,21684,21704],{},[84,21685,21686,13739,21689,21691,21692,21695,21696,21699,21700,21703],{},[32,21687,21688],{},"getDb()",[32,21690,20607],{},"을 즉시 등록해서 핸들러 사용 전에 연결이 닫히던 버그. 모든 라우트가 500 (",[32,21693,21694],{},"Can't add new command when connection is in closed state","). → ",[32,21697,21698],{},"conn.end()"," 호출 제거. Hyperdrive 풀링에 의존, isolate 종료시 GC. TODO: ",[32,21701,21702],{},"withDb"," 미들웨어로 finally + waitUntil 구조 리팩터.",[84,21705,21706,5069,21709,21712],{},[32,21707,21708],{},"\u002Fadmin\u002Fmigrate",[32,21710,21711],{},"?allow_existing=1"," 쿼리 — 0001+ 마이그레이션 \u002F 시드 데이터 적용 시 TB_* 존재 가드 우회. 신규 DDL은 가드 유지.",[237,21714,21716,21717,4343],{"id":21715},"_132-의존성-8128468","13.2 의존성 (",[32,21718,21665],{},[18,21720,21721,4339,21724,21727,21728,21731,21732,21735],{},[32,21722,21723],{},"zod 4.4.3",[32,21725,21726],{},"@hono\u002Fzod-validator 0.8.0",". CLAUDE.md §9 \"모든 입력은 Zod로 파싱\" 규칙 준수. Zod v4는 ",[32,21729,21730],{},".partial()","이 ",[32,21733,21734],{},".refine()","된 스키마에서 동작하지 않으므로 베이스 스키마 공유 + refine 각각 적용 패턴 채택.",[237,21737,21739,21740,4343],{"id":21738},"_133-인프라-라우트-2b6f720","13.3 인프라 + 라우트 (",[32,21741,21668],{},[18,21743,21744],{},[21,21745,21746],{},"재사용 가능 인프라",[81,21748,21749,21761,21774,21799],{},[84,21750,21751,89,21754,4339,21757,21760],{},[32,21752,21753],{},"src\u002Flib\u002Ferrors.ts",[32,21755,21756],{},"AppError",[32,21758,21759],{},"errors"," 헬퍼(notFound\u002Fforbidden\u002Fconflict\u002Fvalidation 등).",[84,21762,21763,21766,21767,4473,21770,21773],{},[32,21764,21765],{},"src\u002Flib\u002Fpagination.ts"," — 커서 페이징 (SCALABILITY §5) base64url JSON ",[32,21768,21769],{},"{ c: ISO, i: id }",[32,21771,21772],{},"paginate(rows, limit, toCursor)"," 헬퍼.",[84,21775,21776,89,21779,4361,21782,4361,21785,21788,21789,4361,21792,4361,21795,21798],{},[32,21777,21778],{},"src\u002Fmiddleware\u002Fauth.ts",[32,21780,21781],{},"requireAuth()",[32,21783,21784],{},"requireRole()",[32,21786,21787],{},"authCtx()",". 로컬 dev 단축(",[32,21790,21791],{},"X-Dev-Company-Id",[32,21793,21794],{},"X-Dev-User-Id",[32,21796,21797],{},"X-Dev-Role"," 헤더). 프로덕션 JWT는 signup\u002Flogin 라우트 구현 시 활성.",[84,21800,21801,21803,21804,4473,21807,4703],{},[32,21802,21654],{}," — Drizzle 수기 스키마 (touch한 6개 — TB_COMPANY, TB_USER, TB_CONTACT, TB_CONTACT_GROUP, TB_CONTACT_GROUP_MEMBER, TB_SENDER_PHONE). TS camelCase ↔ 물리 snake_case, ",[32,21805,21806],{},"status INT default 1",[32,21808,20083],{},[18,21810,21811],{},[21,21812,21813],{},"도메인 라우트",[81,21815,21816,21822,21835,21845],{},[84,21817,21818,21821],{},[32,21819,21820],{},"GET \u002Fme"," — 현재 사용자 + 소속 고객사 (auth 검증용 최소).",[84,21823,21824,21827,21828,4536,21831,21834],{},[32,21825,21826],{},"\u002Fcontacts"," — CRUD 완전체. list (커서·",[32,21829,21830],{},"?q=",[32,21832,21833],{},"?status=","), POST\u002FGET\u002FPATCH\u002FDELETE(soft).",[84,21836,21837,21840,21841,21844],{},[32,21838,21839],{},"\u002Fcontact-groups"," — CRUD + ",[32,21842,21843],{},"\u002F:id\u002Fmembers"," POST·DELETE (memberCount 캐시 자동 갱신, IN 절 + 소유 검증).",[84,21846,21847,21850,21851,21854],{},[32,21848,21849],{},"\u002Fsender-phones"," — 신청·조회·삭제. ",[32,21852,21853],{},"approval_state=대기"," 신청, 승인된 번호는 자가 삭제 금지(403).",[18,21856,21857],{},[21,21858,21859,21860,4343],{},"전역 wiring (",[32,21861,11243],{},[81,21863,21864,21876],{},[84,21865,21866,51,21868,21871,21872,21875],{},[32,21867,21756],{},[32,21869,21870],{},"onError"," 핸들러 → ",[32,21873,21874],{},"{ code, message, details? }"," 표준 응답.",[84,21877,21878,21879,4536,21881,4536,21883,21885],{},"4개 라우트 등록 + 기존 ",[32,21880,11248],{},[32,21882,20116],{},[32,21884,21053],{}," 유지.",[237,21887,21889,21890,21892],{"id":21888},"_134-검증-pnpm-dev-remote-curl","13.4 검증 (",[32,21891,21386],{}," + curl)",[5251,21894,21896],{"className":10197,"code":21895,"language":10199,"meta":4208,"style":4208},"TOKEN=$(grep ^MIGRATE_TOKEN= .dev.vars | cut -d= -f2)\n# 시드\n{ echo 'INSERT IGNORE INTO TB_COMPANY (id, name, status) VALUES (1, \"테스트사\", 1);';\n  echo 'INSERT IGNORE INTO TB_USER (id, company_id, loginid, password_hash, name, role, status) VALUES (1, 1, \"admin@test.com\", \"stub\", \"테스트관리자\", \"admin\", 1);';\n} | curl -X POST \"http:\u002F\u002Flocalhost:8787\u002Fadmin\u002Fmigrate?allow_existing=1\" \\\n    -H \"X-Migrate-Token: $TOKEN\" -H \"Content-Type: application\u002Fsql\" --data-binary @-\n",[32,21897,21898,21924,21929,21941,21951,21971],{"__ignoreMap":4208},[7968,21899,21900,21902,21904,21906,21909,21912,21914,21916,21918,21920,21922],{"class":5110,"line":7970},[7968,21901,21163],{"class":7984},[7968,21903,8254],{"class":7973},[7968,21905,21168],{"class":7984},[7968,21907,21908],{"class":7980},"grep",[7968,21910,21911],{"class":7993}," ^MIGRATE_TOKEN=",[7968,21913,21174],{"class":7993},[7968,21915,21177],{"class":7973},[7968,21917,21180],{"class":7980},[7968,21919,21183],{"class":8892},[7968,21921,21186],{"class":8892},[7968,21923,9563],{"class":7984},[7968,21925,21926],{"class":5110,"line":4212},[7968,21927,21928],{"class":8398},"# 시드\n",[7968,21930,21931,21933,21936,21939],{"class":5110,"line":4209},[7968,21932,21251],{"class":7984},[7968,21934,21935],{"class":8892},"echo",[7968,21937,21938],{"class":7993}," 'INSERT IGNORE INTO TB_COMPANY (id, name, status) VALUES (1, \"테스트사\", 1);'",[7968,21940,8901],{"class":7984},[7968,21942,21943,21946,21949],{"class":5110,"line":4219},[7968,21944,21945],{"class":8892},"  echo",[7968,21947,21948],{"class":7993}," 'INSERT IGNORE INTO TB_USER (id, company_id, loginid, password_hash, name, role, status) VALUES (1, 1, \"admin@test.com\", \"stub\", \"테스트관리자\", \"admin\", 1);'",[7968,21950,8901],{"class":7984},[7968,21952,21953,21956,21959,21962,21964,21966,21969],{"class":5110,"line":8291},[7968,21954,21955],{"class":7984},"} ",[7968,21957,21958],{"class":7973},"|",[7968,21960,21961],{"class":7980}," curl",[7968,21963,21196],{"class":8892},[7968,21965,21199],{"class":7993},[7968,21967,21968],{"class":7993}," \"http:\u002F\u002Flocalhost:8787\u002Fadmin\u002Fmigrate?allow_existing=1\"",[7968,21970,10292],{"class":8892},[7968,21972,21973,21976,21978,21980,21982,21985,21987,21990],{"class":5110,"line":8301},[7968,21974,21975],{"class":8892},"    -H",[7968,21977,21212],{"class":7993},[7968,21979,21215],{"class":7984},[7968,21981,9848],{"class":7993},[7968,21983,21984],{"class":8892}," -H",[7968,21986,21226],{"class":7993},[7968,21988,21989],{"class":8892}," --data-binary",[7968,21991,21992],{"class":7993}," @-\n",[18,21994,21995],{},"확인된 동작:",[81,21997,21998,22001,22009,22018,22028,22037],{},[84,21999,22000],{},"401 미인증 \u002F 200 인증 \u002F 400 Zod 검증 실패 \u002F 404 미존재 모두 표준 응답",[84,22002,22003,22005,22006],{},[32,22004,21820],{}," → 200, ",[32,22007,22008],{},"{ user, company, ctxRole }",[84,22010,22011,22014,22015,4343],{},[32,22012,22013],{},"POST \u002Fcontacts"," → 201, 한글·JSON 필드 정상 (",[32,22016,22017],{},"extraVars: {\"city\":\"서울\"}",[84,22019,22020,22023,22024,22027],{},[32,22021,22022],{},"GET \u002Fcontacts?limit=5"," → 커서 페이지, ",[32,22025,22026],{},"nextCursor: null"," (1건)",[84,22029,22030,22033,22034],{},[32,22031,22032],{},"POST \u002Fsender-phones"," → 201, ",[32,22035,22036],{},"approvalState: \"대기\"",[84,22038,22039,22042],{},[32,22040,22041],{},"GET \u002Fsender-phones?approvalState=대기"," → URL 인코딩 한글 필터 정상",[237,22044,22046],{"id":22045},"_135-다음-단계","13.5 다음 단계",[18,22048,22049],{},"동일 패턴 복제:",[81,22051,22052],{},[84,22053,22054,4473,22057,22060,22061,22064,22065,22068,22069,4473,22072,4536,22074,4703],{},[32,22055,22056],{},"\u002Foptout-entries",[32,22058,22059],{},"\u002Ftemplates","(채널별 spec), ",[32,22062,22063],{},"\u002Fhistory\u002F*","(read-only 조인 뷰), ",[32,22066,22067],{},"\u002Fsender-*","(브랜드·도메인·인증서), ",[32,22070,22071],{},"\u002Fcampaigns",[32,22073,18609],{},[32,22075,22076],{},"\u002Fcredit",[18,22078,22079],{},"코어 미흡 영역:",[81,22081,22082,22090,22095],{},[84,22083,22084,4361,22086,22089],{},[32,22085,4552],{},[32,22087,22088],{},"login"," → JWT 검증 + auth 미들웨어 활성. 현재는 dev 단축만.",[84,22091,22092,22094],{},[32,22093,21702],{}," 미들웨어 (finally + waitUntil 패턴) — 현재는 conn auto-close 안 함.",[84,22096,22097,22098,22101,22102,22104],{},"Zod 검증 실패 응답 — 현재 ",[32,22099,22100],{},"@hono\u002Fzod-validator"," 기본 형식. ",[32,22103,21756],{}," 형식과 통일하려면 hook 등록 검토.",[76,22106,22108,22109,22111,22112,4343],{"id":22107},"_14-api-문서-페이지-doc-scalar-ui-beef401","14. API 문서 페이지 — ",[32,22110,16900],{}," Scalar UI (",[32,22113,22114],{},"beef401",[237,22116,22118],{"id":22117},"_141-엔드포인트","14.1 엔드포인트",[81,22120,22121,22127],{},[84,22122,22123,22126],{},[32,22124,22125],{},"GET \u002Fdoc\u002Fopenapi.json"," — OpenAPI 3.1 스펙 (raw JSON, ~17 KB)",[84,22128,22129,22132],{},[32,22130,22131],{},"GET \u002Fdoc"," — Scalar API Reference UI (현대적 OpenAPI 뷰어, Swagger UI 대안)",[237,22134,22136,22137,4343],{"id":22135},"_142-내용-srcopenapits","14.2 내용 (",[32,22138,22139],{},"src\u002Fopenapi.ts",[81,22141,22142,22162,22200,22219,22245],{},[84,22143,22144,89,22147,4536,22149,4536,22151,4536,22153,4536,22156,4536,22159],{},[21,22145,22146],{},"10 paths \u002F 16 operations",[32,22148,11248],{},[32,22150,20116],{},[32,22152,3987],{},[32,22154,22155],{},"\u002Fcontacts(\u002F{id})",[32,22157,22158],{},"\u002Fcontact-groups(\u002F{id}, \u002F{id}\u002Fmembers)",[32,22160,22161],{},"\u002Fsender-phones(\u002F{id})",[84,22163,22164,89,22167,4361,22170,4361,22173,4361,22176,4361,22179,4361,22182,4361,22185,4361,22188,4361,22191,4361,22194,4361,22197],{},[21,22165,22166],{},"11 schemas",[32,22168,22169],{},"Contact",[32,22171,22172],{},"ContactCreate",[32,22174,22175],{},"ContactPatch",[32,22177,22178],{},"ContactGroup",[32,22180,22181],{},"ContactGroupCreate",[32,22183,22184],{},"ContactGroupPatch",[32,22186,22187],{},"MembersBody",[32,22189,22190],{},"SenderPhone",[32,22192,22193],{},"SenderPhoneCreate",[32,22195,22196],{},"Me",[32,22198,22199],{},"Error",[84,22201,22202,89,22205,4361,22208,4361,22211,22214,22215,22218],{},[21,22203,22204],{},"security schemes",[32,22206,22207],{},"DevCompanyId",[32,22209,22210],{},"DevUserId",[32,22212,22213],{},"DevRole","(로컬 dev API 키 방식) + ",[32,22216,22217],{},"BearerAuth","(프로덕션 JWT, 구현 시 활성)",[84,22220,22221,89,22224,4361,22227,4361,22230,5439,22233,89,22236,4361,22239,4361,22242],{},[21,22222,22223],{},"공통 parameters",[32,22225,22226],{},"Cursor",[32,22228,22229],{},"Limit",[32,22231,22232],{},"IdPath",[21,22234,22235],{},"공통 responses",[32,22237,22238],{},"Unauthorized",[32,22240,22241],{},"NotFound",[32,22243,22244],{},"Validation",[84,22246,22247,22250,22251,22254],{},[21,22248,22249],{},"사용 가이드"," — 인증 방식·응답 형식·커서 페이징·멀티 테넌트 격리·관련 문서 링크 — ",[32,22252,22253],{},"info.description","에 명문화",[237,22256,22258],{"id":22257},"_143-설계-결정","14.3 설계 결정",[81,22260,22261,22267,22276],{},[84,22262,22263,22266],{},[21,22264,22265],{},"손으로 작성"," (zod-openapi 자동 생성 미사용). Zod v4 호환 우려 + 단순성 우선. 라우트가 안정화되면 자동 생성 마이그레이션 검토.",[84,22268,22269,22272,22273,22275],{},[21,22270,22271],{},"드리프트 위험",": 라우트 추가\u002F변경 시 ",[32,22274,22139],{},"도 함께 갱신 필요. PR 리뷰 체크리스트에 명시.",[84,22277,22278,22281],{},[21,22279,22280],{},"Scalar UI 선택"," — Swagger UI 대비 모던 디자인·다크모드·코드 샘플 자동 생성.",[237,22283,22285],{"id":22284},"_144-검증","14.4 검증",[5251,22287,22290],{"className":22288,"code":22289,"language":5256},[5254],"GET \u002Fdoc\u002Fopenapi.json → 200, 17 KB, openapi 3.1.0\n  paths: 10, schemas: 11\nGET \u002Fdoc              → 200, text\u002Fhtml, scalar 마커 포함\n",[32,22291,22289],{"__ignoreMap":4208},[18,22293,22294,22295,22299],{},"브라우저로 ",[26,22296,22297],{"href":22297,"rel":22298},"http:\u002F\u002Flocalhost:8787\u002Fdoc",[30]," 접속 → 좌측 사이드바 네비게이션 + 우측 인터랙티브 콜 패널.",[76,22301,22303],{"id":22302},"_15-malgn-noti-api-프로덕션-배포-2","15. malgn-noti-api 프로덕션 배포 #2",[18,22305,22306,22307,22310],{},"§7에서 적용한 첫 배포(",[32,22308,22309],{},"Version 8b0d8674",") 이후 누적된 변경(§10 운영 컨벤션 명문화 \u002F §12 admin 라우트 + Aurora DDL 적용 인프라 \u002F §13 기본 CRUD API + Drizzle 스키마 \u002F §14 \u002Fdoc Scalar UI)을 한 번에 라이브로.",[237,22312,22314,22315,22317],{"id":22313},"_151-배포-흐름-malgn-noti-apiclaudemd-81-준수","15.1 배포 흐름 (",[32,22316,20133],{}," 준수)",[5251,22319,22322],{"className":22320,"code":22321,"language":5256},[5254],"pnpm typecheck       → 통과\npnpm run deploy      → Cloudflare Workers\n검증                  → \u002Fhealth, \u002Fhealth\u002Fdb, \u002Fdoc, \u002Fdoc\u002Fopenapi.json, \u002Fme, \u002Fadmin\u002F* 7건\n커밋·푸시              → 이미 sync (이전 단계마다 push했음)\nhistory              → 본 절 추가\n",[32,22323,22321],{"__ignoreMap":4208},[237,22325,22327],{"id":22326},"_152-배포-결과","15.2 배포 결과",[81,22329,22330,22338,22341],{},[84,22331,22332,47,22335],{},[21,22333,22334],{},"Version ID",[32,22336,22337],{},"1fdc3b12-9e43-4c31-90c4-609845569e65",[84,22339,22340],{},"번들: 2309.97 KiB \u002F gzip 549.75 KiB (이전 1638.71 KiB → 2309.97 KiB, drizzle + mysql2 + zod + Scalar 포함으로 증가)",[84,22342,22343],{},"Worker Startup: 49 ms",[237,22345,22347,22348,4343],{"id":22346},"_153-검증-httpsmalgn-noti-apimalgnsoftworkersdev","15.3 검증 (",[26,22349,11267],{"href":11267,"rel":22350},[30],[101,22352,22353,22363],{},[104,22354,22355],{},[107,22356,22357,22360],{},[110,22358,22359],{},"엔드포인트",[110,22361,22362],{},"결과",[126,22364,22365,22378,22387,22398,22407,22416,22432],{},[107,22366,22367,22372],{},[131,22368,22369],{},[32,22370,22371],{},"GET \u002F",[131,22373,22374,22375],{},"200, ",[32,22376,22377],{},"env: \"production\"",[107,22379,22380,22385],{},[131,22381,22382],{},[32,22383,22384],{},"GET \u002Fhealth",[131,22386,21542],{},[107,22388,22389,22393],{},[131,22390,22391],{},[32,22392,20624],{},[131,22394,22374,22395],{},[32,22396,22397],{},"mysql_version: \"8.0.42\"",[107,22399,22400,22404],{},[131,22401,22402],{},[32,22403,22125],{},[131,22405,22406],{},"200, 17 KB, 10 paths",[107,22408,22409,22413],{},[131,22410,22411],{},[32,22412,22131],{},[131,22414,22415],{},"200, text\u002Fhtml (Scalar UI)",[107,22417,22418,22423],{},[131,22419,22420,22422],{},[32,22421,21820],{}," (인증 없음)",[131,22424,22425,51,22428,22431],{},[21,22426,22427],{},"401",[32,22429,22430],{},"unauthenticated"," ← 인증 가드 작동",[107,22433,22434,22439],{},[131,22435,22436,22438],{},[32,22437,21090],{}," (유효 토큰)",[131,22440,22441,51,22443,22446],{},[21,22442,21490],{},[32,22444,22445],{},"not_found"," ← env 가드(APP_ENV=production) 작동",[18,22448,22449],{},"두 가드 모두 프로덕션에서 정상 작동 — auth는 dev 헤더 거부 + JWT 미구현으로 401, admin은 토큰과 무관하게 404로 위장.",[237,22451,22453],{"id":22452},"_154-라이브-main-일치","15.4 라이브 ↔ main 일치",[18,22455,22456,22457,22459,22460,22462],{},"배포 시점 working tree와 ",[32,22458,12995],{},"이 이미 일치 (",[32,22461,22114],{},"). 추가 동기화 커밋 불필요.",[76,22464,22466,22467,4343],{"id":22465},"_16-14개-도메인-라우트-추가-발신정보-주소록-템플릿-문의-결제-3bd9864","16. 14개 도메인 라우트 추가 — 발신정보 \u002F 주소록 \u002F 템플릿 \u002F 문의 \u002F 결제 (",[32,22468,22469],{},"3bd9864",[18,22471,22472],{},"§13의 4개 라우트(기본 CRUD 골격) 패턴을 복제해 나머지 주요 도메인을 일괄 확장. 49 테이블 중 ~24개를 API로 노출.",[237,22474,22476,22477,4343],{"id":22475},"_161-스키마-확장-srcdbschemats","16.1 스키마 확장 (",[32,22478,21654],{},[18,22480,22481],{},"13개 Drizzle 테이블 추가:",[81,22483,22484,22505,22511,22519,22534],{},[84,22485,22486,22487,4536,22490,4536,22493,4536,22496,4536,22499,4536,22502],{},"발신정보: ",[32,22488,22489],{},"rcsBrand",[32,22491,22492],{},"emailDomain",[32,22494,22495],{},"pushCert",[32,22497,22498],{},"kakaoProfileGroup",[32,22500,22501],{},"kakaoSenderProfile",[32,22503,22504],{},"optout080Number",[84,22506,22507,22508],{},"주소록: ",[32,22509,22510],{},"optoutEntry",[84,22512,22513,22514,4536,22517],{},"템플릿: ",[32,22515,22516],{},"templateCategory",[32,22518,8296],{},[84,22520,22521,22522,4536,22525,4536,22528,4536,22531],{},"시스템: ",[32,22523,22524],{},"inquiry",[32,22526,22527],{},"inquiryReply",[32,22529,22530],{},"landingPage",[32,22532,22533],{},"companySettings",[84,22535,22536,22537,4536,22540,22543],{},"결제: ",[32,22538,22539],{},"paymentMethod",[32,22541,22542],{},"creditLedger","(파티션 PK)",[237,22545,22547],{"id":22546},"_162-신규-라우트-14종","16.2 신규 라우트 14종",[101,22549,22550,22563],{},[104,22551,22552],{},[107,22553,22554,22557,22560],{},[110,22555,22556],{},"라우트",[110,22558,22559],{},"메서드",[110,22561,22562],{},"특징",[126,22564,22565,22578,22590,22602,22614,22626,22638,22649,22661,22672,22687,22700,22712,22724],{},[107,22566,22567,22572,22575],{},[131,22568,22569],{},[32,22570,22571],{},"\u002Frcs-brands",[131,22573,22574],{},"CRUD",[131,22576,22577],{},"brandCode UNIQUE 409 처리",[107,22579,22580,22585,22587],{},[131,22581,22582],{},[32,22583,22584],{},"\u002Femail-domains",[131,22586,22574],{},[131,22588,22589],{},"verified_yn \u002F dkim_state 워크플로",[107,22591,22592,22597,22599],{},[131,22593,22594],{},[32,22595,22596],{},"\u002Fpush-certs",[131,22598,22574],{},[131,22600,22601],{},"credentialEnc 응답 제외, base64 입력",[107,22603,22604,22609,22611],{},[131,22605,22606],{},[32,22607,22608],{},"\u002Fkakao-profile-groups",[131,22610,22574],{},[131,22612,22613],{},"단순 그룹 메타",[107,22615,22616,22621,22623],{},[131,22617,22618],{},[32,22619,22620],{},"\u002Fkakao-sender-profiles",[131,22622,22574],{},[131,22624,22625],{},"sendKeyEnc 응답 제외, profileId UNIQUE",[107,22627,22628,22633,22635],{},[131,22629,22630],{},[32,22631,22632],{},"\u002Foptout-080-numbers",[131,22634,22574],{},[131,22636,22637],{},"line_state 워크플로",[107,22639,22640,22644,22646],{},[131,22641,22642],{},[32,22643,22056],{},[131,22645,22574],{},[131,22647,22648],{},"채널별, 발송 직전 핫 경로, status=-1로 거부 해제",[107,22650,22651,22656,22658],{},[131,22652,22653],{},[32,22654,22655],{},"\u002Ftemplate-categories",[131,22657,22574],{},[131,22659,22660],{},"트리(부모 검증), 자식 있으면 삭제 거부",[107,22662,22663,22667,22669],{},[131,22664,22665],{},[32,22666,22059],{},[131,22668,22574],{},[131,22670,22671],{},"채널 필터, 시스템 샘플(company_id NULL) 조회 포함, 수정 시 review_state→draft",[107,22673,22674,22678,22684],{},[131,22675,22676],{},[32,22677,4073],{},[131,22679,22680,22681],{},"CRUD + ",[32,22682,22683],{},"\u002F:id\u002Freplies",[131,22685,22686],{},"답변 추가 시 answer_state→progress",[107,22688,22689,22694,22697],{},[131,22690,22691],{},[32,22692,22693],{},"\u002Fcompany-settings",[131,22695,22696],{},"GET \u002F PUT",[131,22698,22699],{},"1:1 upsert (settings JSON)",[107,22701,22702,22707,22709],{},[131,22703,22704],{},[32,22705,22706],{},"\u002Fpayment-methods",[131,22708,22574],{},[131,22710,22711],{},"billingKeyEnc 마스킹, default_yn 단일성 보장",[107,22713,22714,22719,22721],{},[131,22715,22716],{},[32,22717,22718],{},"\u002Flanding-pages",[131,22720,22574],{},[131,22722,22723],{},"publishedYn=Y 시 publishedAt 자동",[107,22725,22726,22731,22734],{},[131,22727,22728],{},[32,22729,22730],{},"\u002Fcredit-ledger",[131,22732,22733],{},"GET (read-only)",[131,22735,22736],{},"append-only, entryType\u002F기간 필터",[237,22738,22740],{"id":22739},"_163-공통-패턴-기존-13과-동일","16.3 공통 패턴 (기존 §13과 동일)",[81,22742,22743,22752,22758,22764,22767],{},[84,22744,22745,22747,22748,22751],{},[32,22746,21781],{}," 미들웨어 + ",[32,22749,22750],{},"companyId"," 스코프",[84,22753,22754,22755],{},"커서 페이징 ",[32,22756,22757],{},"(created_at DESC, id DESC)",[84,22759,22760,22761],{},"soft delete ",[32,22762,22763],{},"status=-1",[84,22765,22766],{},"시크릿 필드(credentialEnc·sendKeyEnc·billingKeyEnc)는 응답에서 제외",[84,22768,22769],{},"UNIQUE 위반은 409 conflict 응답",[237,22771,22773],{"id":22772},"_164-검증-pnpm-dev-curl","16.4 검증 (pnpm dev + curl)",[18,22775,22776],{},"9개 라우트 POST\u002FGET 정상 동작 확인:",[81,22778,22779,22789,22794,22801],{},[84,22780,22781,4473,22783,4473,22785,4473,22787],{},[32,22782,22571],{},[32,22784,22584],{},[32,22786,22056],{},[32,22788,22655],{},[84,22790,22791,22793],{},[32,22792,22059],{}," (한글 + JSON spec)",[84,22795,22796,4473,22798,22800],{},[32,22797,4073],{},[32,22799,22693],{}," (GET\u002FPUT upsert)",[84,22802,22803,22805],{},[32,22804,22730],{}," (빈 목록 페이징)",[237,22807,22809],{"id":22808},"_165-미완료-알려진-한계","16.5 미완료 \u002F 알려진 한계",[81,22811,22812,22830,22837,22840],{},[84,22813,22814,22819,22820,22822,22823,22829],{},[21,22815,22816,22818],{},[32,22817,16900],{}," OpenAPI 스펙 동기화 미반영"," — 14개 신규 라우트가 ",[32,22821,22139],{},"에 없음. 손으로 작성 부담이 너무 큼. 다음 단계로 ",[21,22824,22825,22828],{},[32,22826,22827],{},"hono-openapi"," 자동 생성 마이그레이션"," 후 일괄 갱신 권장.",[84,22831,22832,22833,22836],{},"일부 PATCH 미제공 — sender-phones, kakao-profile-groups, optout-080-numbers, optout-entries 등 워크플로 상태 변경은 별도 RPC 라우트(",[32,22834,22835],{},"\u002Fsender-phones\u002F:id\u002Fapprove",")로 분리하는 게 깔끔.",[84,22838,22839],{},"인증·JWT는 여전히 dev 헤더만. signup\u002Flogin 구현이 다음 큰 마일스톤.",[84,22841,22842,22845],{},[32,22843,22844],{},"dispatch-*"," 라우트(발송 이력 read-only)는 파티션 테이블이라 별도 설계 필요. 다음 단계.",[76,22847,22849,22850,4473,22853,4473,22856,4343],{"id":22848},"_17-phase-123-doc-동기화-인증-발송-이력-32f7ce4-c19f116-f45ad01","17. Phase 1·2·3 — \u002Fdoc 동기화 + 인증 + 발송 이력 (",[32,22851,22852],{},"32f7ce4",[32,22854,22855],{},"c19f116",[32,22857,22858],{},"f45ad01",[18,22860,22861],{},"§16 직후 사용자의 \"차례대로 진행\" 지시에 따라 3 phase 연속 진행.",[237,22863,22865,22866,4343],{"id":22864},"_171-phase-1-openapits-확장-32f7ce4","17.1 Phase 1 — openapi.ts 확장 (",[32,22867,22852],{},[18,22869,22870,22871,22873],{},"§16에서 추가한 14개 라우트가 ",[32,22872,16900],{},"에 안 보이던 문제 해결.",[81,22875,22876,22882,22898],{},[84,22877,22878,22879,22881],{},"자동 생성 시도(",[32,22880,22827],{}," 1.x) → Standard Schema 기반 새 버전이 Zod v4 vendor 등록 필요로 복잡 → 롤백.",[84,22883,22884,22885,4339,22888,4536,22891,4536,22894,22897],{},"대신 ",[21,22886,22887],{},"수기 확장",[32,22889,22890],{},"cursorList()",[32,22892,22893],{},"single()",[32,22895,22896],{},"ok"," 헬퍼로 응답 재사용해 간결화.",[84,22899,22900,22901,22904,22905,22908,22909,22912,22913,22916,22917,4703],{},"규모: paths 10→",[21,22902,22903],{},"37",", operations 16→",[21,22906,22907],{},"71",", schemas 11→",[21,22910,22911],{},"45",", tags 5→",[21,22914,22915],{},"19",", 스펙 17 KB → ",[21,22918,22919],{},"54 KB",[237,22921,22923,22924,4343],{"id":22922},"_172-phase-2-인증-signup-login-jwt-c19f116","17.2 Phase 2 — 인증 (signup \u002F login + JWT) (",[32,22925,22855],{},[18,22927,22928],{},"dev 헤더만으론 외부에서 호출 불가 → JWT 흐름 정식 구현.",[18,22930,22931],{},"신규 파일:",[81,22933,22934,22947,22957],{},[84,22935,22936,89,22939,22942,22943,22946],{},[32,22937,22938],{},"src\u002Flib\u002Fpassword.ts",[21,22940,22941],{},"PBKDF2-SHA256"," 100k 라운드 + salt 16B (Workers Web Crypto). 저장 형식 ",[32,22944,22945],{},"pbkdf2$\u003Citer>$\u003CsaltB64>$\u003ChashB64>",". timing-safe compare.",[84,22948,22949,22952,22953,22956],{},[32,22950,22951],{},"src\u002Flib\u002Fjwt.ts"," — HS256 (hono\u002Futils\u002Fjwt), payload ",[32,22954,22955],{},"{ sub, cid, role, iat, exp }",", 기본 만료 7일.",[84,22958,22959,22962],{},[32,22960,22961],{},"src\u002Froutes\u002Fauth.ts",[81,22963,22964,22970],{},[84,22965,22966,22969],{},[32,22967,22968],{},"POST \u002Fauth\u002Fsignup"," — 신규 고객사 + owner 사용자 생성, JWT 발급. loginid 중복 → 409. 사용자 생성 실패 시 고객사 best-effort 무효화.",[84,22971,22972,22975],{},[32,22973,22974],{},"POST \u002Fauth\u002Flogin"," — companyId + loginid + password 검증, JWT 발급. account enumeration 방지(일관된 401). lastLoginAt 갱신.",[18,22977,22978],{},"미들웨어:",[81,22980,22981],{},[84,22982,22983,89,22985,22988],{},[32,22984,21781],{},[21,22986,22987],{},"Bearer JWT 우선"," 검증 → 실패하면 dev 헤더(APP_ENV=local) 백업 → 둘 다 없으면 401.",[18,22990,22991],{},"설정:",[81,22993,22994],{},[84,22995,22996,22999,23000,23002,23003,5606],{},[32,22997,22998],{},"JWT_SECRET"," 환경변수 추가 (",[32,23001,12527],{}," + 운영은 ",[32,23004,21365],{},[18,23006,23007],{},"검증 6건:",[81,23009,23010,23013,23016,23019,23022,23025],{},[84,23011,23012],{},"signup → 201 + JWT",[84,23014,23015],{},"GET \u002Fme with JWT → 200",[84,23017,23018],{},"login(정확) → 200 + JWT, lastLoginAt 갱신",[84,23020,23021],{},"login(틀린 pw) → 401",[84,23023,23024],{},"잘못된 JWT → 401",[84,23026,23027],{},"헤더 없음 → 401",[18,23029,23030],{},"알려진 한계:",[81,23032,23033,23036],{},[84,23034,23035],{},"OTP·약관·재설정·2FA·refresh token·세션 강제 로그아웃 미구현",[84,23037,23038],{},"고객사+사용자 생성 트랜잭션 미사용 (saga 권장)",[237,23040,23042,23043,4343],{"id":23041},"_173-phase-3-발송-이력-read-only-f45ad01","17.3 Phase 3 — 발송 이력 read-only (",[32,23044,22858],{},[18,23046,23047],{},"발송 화면(history\u002F*.vue) 백엔드 API. 파티션 테이블이므로 시간 윈도우 강제.",[18,23049,23050],{},"스키마 추가:",[81,23052,23053,23064],{},[84,23054,23055,4536,23058,23061,23062],{},[32,23056,23057],{},"dispatchRequest",[32,23059,23060],{},"dispatchItem"," 파티션 PK ",[32,23063,20394],{},[84,23065,23066,23069,23070],{},[32,23067,23068],{},"dispatchStatDaily"," 복합 PK ",[32,23071,23072],{},"(companyId, channel, statDate)",[18,23074,23075,23076,8864],{},"신규 라우트 (",[32,23077,23078],{},"src\u002Froutes\u002Fdispatch-history.ts",[81,23080,23081,23091,23101,23110],{},[84,23082,23083,23086,23087,23090],{},[32,23084,23085],{},"GET \u002Fdispatch\u002Frequests"," — 기본 30일, ",[21,23088,23089],{},"max 90일"," 윈도우 강제, channel\u002FdispatchState 필터, 커서 페이징. window > 90일 → 400 Validation + \"Use Export job\" 힌트",[84,23092,23093,23096,23097,23100],{},[32,23094,23095],{},"GET \u002Fdispatch\u002Frequests\u002F:id?createdAt="," — 단건. ",[21,23098,23099],{},"createdAt 필수"," (파티션 pruning, SCALABILITY §1)",[84,23102,23103,23106,23107,23109],{},[32,23104,23105],{},"GET \u002Fdispatch\u002Frequests\u002F:id\u002Fitems"," — 수신자별 항목. ",[32,23108,22750],{}," 비정규화로 스코프 격리",[84,23111,23112,23115],{},[32,23113,23114],{},"GET \u002Fdispatch-stats"," — 일별 집계 (max 365일)",[18,23117,23118],{},"설계 (SCALABILITY §5 준수):",[81,23120,23121,23124,23131,23134],{},[84,23122,23123],{},"OFFSET 금지, 커서 페이징",[84,23125,23126,23127,23130],{},"단건 조회는 ",[32,23128,23129],{},"createdAt"," 명시 — 파티션 pruning 안 되면 전 파티션 스캔",[84,23132,23133],{},"시간 윈도우 강제로 사용자 실수에 의한 대량 스캔 차단",[84,23135,23136],{},"더 넓은 범위는 Export 잡(\u002Fexport-jobs)으로 우회 — 추후 추가",[18,23138,23139],{},"openapi.ts에 4 paths + 3 스키마(DispatchRequest\u002FItem\u002FStat) 추가. 최종 paths 41, operations 76, schemas 49.",[18,23141,23142],{},"검증:",[81,23144,23145,23148,23151],{},[84,23146,23147],{},"\u002Fdispatch\u002Frequests 기본 30일 → 200 empty + window",[84,23149,23150],{},"from-to 145일 → 400 validation + \"range too wide ... use Export job\"",[84,23152,23153],{},"\u002Fdispatch-stats 채널·기간 필터 → 200 empty + window",[76,23155,23157],{"id":23156},"_18-malgn-noti-api-프로덕션-배포-3-인증14-라우트발송-이력-라이브","18. malgn-noti-api 프로덕션 배포 #3 — 인증·14 라우트·발송 이력 라이브",[18,23159,23160],{},"§16·§17 누적 변경(14 도메인 라우트 + \u002Fauth + \u002Fdispatch + openapi 확장)을 라이브 반영.",[237,23162,23164],{"id":23163},"_181-사전","18.1 사전",[81,23166,23167,23176],{},[84,23168,23169,23171,23172,23175],{},[32,23170,22998],{}," wrangler secret 등록 — ",[32,23173,23174],{},"openssl rand -hex 32 | pnpm wrangler secret put JWT_SECRET"," (값은 로그 안 남김, 향후에도 노출 불가).",[84,23177,23178,23179,23181],{},"typecheck 통과, working tree clean (",[32,23180,12995],{},"이 모든 변경 이미 포함).",[237,23183,23185],{"id":23184},"_182-배포","18.2 배포",[81,23187,23188,23193,23199,23202],{},[84,23189,23190,23192],{},[32,23191,18672],{}," (wrangler deploy)",[84,23194,23195,23196],{},"Version: ",[32,23197,23198],{},"926017d2-6ba8-440f-b405-5330ef3f2ffb",[84,23200,23201],{},"번들: 2439 KiB \u002F gzip 568 KiB (이전 #2 2310 → +130 KiB, auth + dispatch + openapi 확장)",[84,23203,23204],{},"Worker Startup 62 ms",[237,23206,23208],{"id":23207},"_183-검증-프로덕션-엔드투엔드-인증-흐름","18.3 검증 — 프로덕션 엔드투엔드 인증 흐름",[101,23210,23211,23219],{},[104,23212,23213],{},[107,23214,23215,23217],{},[110,23216,22359],{},[110,23218,22362],{},[126,23220,23221,23233,23244,23263,23274,23285,23298,23315],{},[107,23222,23223,23227],{},[131,23224,23225],{},[32,23226,22384],{},[131,23228,23229,23230],{},"200 ",[32,23231,23232],{},"env: production",[107,23234,23235,23239],{},[131,23236,23237],{},[32,23238,20624],{},[131,23240,23229,23241],{},[32,23242,23243],{},"mysql_version: 8.0.42",[107,23245,23246,23250],{},[131,23247,23248],{},[32,23249,22125],{},[131,23251,23252,23253,6685,23256,4536,23259,23262],{},"200, 61.9 KB, ",[21,23254,23255],{},"43 paths · 77 ops · 51 schemas",[32,23257,23258],{},"\u002Fauth\u002Fsignup",[32,23260,23261],{},"\u002Fauth\u002Flogin"," 포함)",[107,23264,23265,23269],{},[131,23266,23267,22438],{},[32,23268,21090],{},[131,23270,23271,23273],{},[21,23272,21490],{}," ← env 가드 유지",[107,23275,23276,23281],{},[131,23277,23278,23280],{},[32,23279,21820],{}," (no auth)",[131,23282,23283],{},[21,23284,22427],{},[107,23286,23287,23292],{},[131,23288,23289,23291],{},[32,23290,22968],{}," (실제 가입)",[131,23293,23294,23297],{},[21,23295,23296],{},"201"," + JWT — companyId=4, \"프로덕션테스트\" 생성",[107,23299,23300,23305],{},[131,23301,23302,23304],{},[32,23303,21820],{}," with Bearer JWT",[131,23306,23307,89,23309,4473,23312],{},[21,23308,21542],{},[32,23310,23311],{},"user.role=owner",[32,23313,23314],{},"company.name=프로덕션테스트",[107,23316,23317,23322],{},[131,23318,23319,23321],{},[32,23320,22974],{}," (잘못된 pw)",[131,23323,23324],{},[21,23325,22427],{},[18,23327,23328,23330],{},[21,23329,4629],{}," — 프로덕션 Worker가 실제 Aurora에 직접 INSERT\u002FSELECT 수행, JWT가 7일 만료로 발급, dev 헤더는 production에서 무시되어 보안 격리 유지.",[237,23332,23334],{"id":23333},"_184-라이브-main-일치","18.4 라이브 ↔ main 일치",[18,23336,22456,23337,6100,23339,23341],{},[32,23338,12995],{},[32,23340,22858],{},") 일치. 추가 동기화 커밋 불필요.",[237,23343,23345],{"id":23344},"_185-다음-단계","18.5 다음 단계",[18,23347,23348,23349,23352,23353,23356],{},"추천 2번 — ",[21,23350,23351],{},"POST \u002Fsend (발송 큐 producer)"," + NHN 어댑터. 발송 이력은 readonly 갖췄으니 쓰기 경로 차례.\n추천 3번 — Export 잡 (",[32,23354,23355],{},"\u002Fexport-jobs","): 90일 초과 이력 조회 우회.",[76,23358,23360,23361,4343],{"id":23359},"_19-post-sendsms-발송-producer-db-적재까지-fb99b66","19. POST \u002Fsend\u002Fsms — 발송 producer (DB 적재까지) (",[32,23362,23363],{},"fb99b66",[237,23365,23367],{"id":23366},"_191-흐름","19.1 흐름",[6674,23369,23370,23376,23382,23385,23392,23395],{},[84,23371,23372,23375],{},[32,23373,23374],{},"Idempotency-Key"," 헤더 필수 (멱등성 — 현재 버그 있음, 19.4 참조)",[84,23377,23378,23379],{},"발신번호 검증 — 본인 고객사 + ",[32,23380,23381],{},"approval_state=승인",[84,23383,23384],{},"smsType 자동 추론(90B 초과→LMS, 첨부→MMS) 또는 명시",[84,23386,23387,23388,23391],{},"옵트아웃 필터 — ",[32,23389,23390],{},"TB_OPTOUT_ENTRY"," IN 절 lookup",[84,23393,23394],{},"단가 계산 (임시 단가표: SMS 9.9, LMS 30, MMS 100)",[84,23396,23397,23398,23400,23401],{},"트랜잭션 — 크레딧 조건부 차감 + 원장 hold + ",[32,23399,20187],{}," + bulk ",[32,23402,23403],{},"TB_DISPATCH_ITEMs",[237,23405,23407],{"id":23406},"_192-신규-파일","19.2 신규 파일",[81,23409,23410,23421],{},[84,23411,23412,89,23415,4339,23418],{},[32,23413,23414],{},"src\u002Flib\u002Fpricing.ts",[32,23416,23417],{},"SMS_PRICING",[32,23419,23420],{},"detectSmsType(body, hasAttachment)",[84,23422,23423,89,23426,23429],{},[32,23424,23425],{},"src\u002Froutes\u002Fsend.ts",[32,23427,23428],{},"POST \u002Fsend\u002Fsms",", 최대 1000 수신자\u002F요청",[237,23431,23433],{"id":23432},"_193-검증","19.3 검증",[81,23435,23436,23446,23449,23452,23455],{},[84,23437,23438,23439,4420,23442,23445],{},"정상 발송 → 201, ",[32,23440,23441],{},"recipientCount",[32,23443,23444],{},"totalCredit"," · 잔액 갱신 OK",[84,23447,23448],{},"옵트아웃 필터: 2명 중 1명만 발송 동작 확인",[84,23450,23451],{},"미승인 발신번호 → 403",[84,23453,23454],{},"잘못된 senderPhoneId → 404",[84,23456,23457],{},"크레딧 부족 (조건부 UPDATE affectedRows=0) → 409 conflict",[18,23459,23460,23461,4536,23464,4339,23467,23469],{},"openapi.ts: ",[32,23462,23463],{},"SendSmsRequest",[32,23465,23466],{},"SendResponse",[32,23468,23428],{},". 최종 paths 44, ops 78, schemas 53.",[237,23471,23473],{"id":23472},"_194-알려진-한계-idempotency-bug","19.4 알려진 한계 — IDEMPOTENCY BUG",[18,23475,23476,23477,23479,23480,23483],{},"같은 ",[32,23478,23374],{}," 연달아 호출 시 멱등 SELECT가 직전 INSERT를 못 보고 중복 적재됨. 두 번 호출에 ",[32,23481,23482],{},"dispatchRequestId"," 2개 생성, 크레딧 2번 차감 발생.",[81,23485,23486,23492,23495],{},[84,23487,23488,23489,23491],{},"Drizzle 일반 select \u002F ",[32,23490,20614],{},"...`)` raw \u002F cache-bust 주석 모두 같은 동작",[84,23493,23494],{},"Hyperdrive read-cache 후보가 가장 유력했으나 raw + 주석에도 실패 → 다른 원인 가능",[84,23496,23497,23500,23501,23504,23505,23508],{},[21,23498,23499],{},"TODO(idempotency v2)"," — 별 ",[32,23502,23503],{},"TB_IDEMPOTENCY"," (비파티션, ",[32,23506,23507],{},"UNIQUE(company_id, key)",") 테이블로 INSERT-then-conflict 패턴 정식 구현. race-free + 캐시 무관. 코드에 상세 주석 남김.",[237,23510,23512],{"id":23511},"_195-다음-단계","19.5 다음 단계",[18,23514,23515],{},"본 라우트는 DB 적재까지만. 실제 NHN 발송까지:",[81,23517,23518,23527,23536,23545,23556],{},[84,23519,23520,89,23523,23526],{},[21,23521,23522],{},"Cloudflare Queues 설정",[32,23524,23525],{},"wrangler.toml [[queues.producers]]"," + 큐 생성",[84,23528,23529,89,23532,23535],{},[21,23530,23531],{},"Queue consumer worker",[32,23533,23534],{},"src\u002Fworkers\u002Fdispatch.ts"," (NHN 어댑터 호출)",[84,23537,23538,89,23541,23544],{},[21,23539,23540],{},"NHN SMS 어댑터",[32,23542,23543],{},"src\u002Fadapters\u002Fnhn\u002Fsms.ts"," (AppKey\u002FSecretKey 사용)",[84,23546,23547,89,23550,6219,23553,23555],{},[21,23548,23549],{},"Webhook handler",[32,23551,23552],{},"POST \u002Fwebhooks\u002Fnhn",[32,23554,21328],{}," 적재",[84,23557,23558,89,23561,4473,23563,4473,23565,4473,23568,4473,23570],{},[21,23559,23560],{},"다른 채널",[32,23562,17575],{},[32,23564,12298],{},[32,23566,23567],{},"\u002Fsend\u002Femail",[32,23569,17578],{},[32,23571,15268],{},[76,23573,23575],{"id":23574},"_20-malgn-noti-api-프로덕션-배포-4-sendsms-발송-producer-라이브","20. malgn-noti-api 프로덕션 배포 #4 — \u002Fsend\u002Fsms 발송 producer 라이브",[18,23577,23578,23579,23581],{},"§19 발송 producer(",[32,23580,23363],{},")를 라이브 반영.",[237,23583,23585],{"id":23584},"_201-배포","20.1 배포",[81,23587,23588,23593,23596],{},[84,23589,23195,23590],{},[32,23591,23592],{},"4d9e1fbe-c8c5-4b70-933d-a48196fc2599",[84,23594,23595],{},"번들: 2450 KiB \u002F gzip 571 KiB (#3 2439 → 2450, send 라우트 + openapi 추가분 +11 KB)",[84,23597,23598],{},"Worker Startup 70 ms",[237,23600,23602,23603,4343],{"id":23601},"_202-검증-httpsmalgn-noti-apimalgnsoftworkersdev","20.2 검증 (",[26,23604,11267],{"href":11267,"rel":23605},[30],[101,23607,23608,23616],{},[104,23609,23610],{},[107,23611,23612,23614],{},[110,23613,22359],{},[110,23615,22362],{},[126,23617,23618,23628,23638,23652,23661,23669,23681,23692,23707],{},[107,23619,23620,23624],{},[131,23621,23622],{},[32,23623,22384],{},[131,23625,23229,23626],{},[32,23627,23232],{},[107,23629,23630,23634],{},[131,23631,23632],{},[32,23633,20624],{},[131,23635,23229,23636],{},[32,23637,23243],{},[107,23639,23640,23644],{},[131,23641,23642],{},[32,23643,22125],{},[131,23645,23646,23647,6685,23650,23262],{},"200, 65 KB, ",[21,23648,23649],{},"paths 44 · ops 78 · schemas 53",[32,23651,12271],{},[107,23653,23654,23658],{},[131,23655,23656,22438],{},[32,23657,21090],{},[131,23659,23660],{},"404 ← env 가드",[107,23662,23663,23667],{},[131,23664,23665,23280],{},[32,23666,21820],{},[131,23668,22427],{},[107,23670,23671,23678],{},[131,23672,23673,6685,23675,4343],{},[32,23674,22974],{},[32,23676,23677],{},"prod-1@test.com",[131,23679,23680],{},"200 + JWT",[107,23682,23683,23689],{},[131,23684,23685,23688],{},[32,23686,23687],{},"GET \u002Fsender-phones"," with JWT",[131,23690,23691],{},"200 빈 결과 (tenant 격리 정상)",[107,23693,23694,23699],{},[131,23695,23696,23698],{},[32,23697,23428],{}," (잘못된 senderPhoneId)",[131,23700,23701,51,23703,23706],{},[21,23702,21490],{},[32,23704,23705],{},"sender_phone not found"," ← 검증 흐름 동작",[107,23708,23709,23714],{},[131,23710,23711,23713],{},[32,23712,23428],{}," (Idempotency-Key 누락)",[131,23715,23716,51,23718,23721],{},[21,23717,5351],{},[32,23719,23720],{},"Idempotency-Key 헤더 필수"," ← 헤더 가드 동작",[237,23723,23725],{"id":23724},"_203-의미","20.3 의미",[81,23727,23728,23731,23734],{},[84,23729,23730],{},"\u002Fsend\u002Fsms 라우트가 프로덕션 Aurora에 도달하여 검증·격리·에러 응답 모두 정상.",[84,23732,23733],{},"발신번호·옵트아웃·크레딧 hold·트랜잭션 등 코드 경로는 로컬에서 검증 완료, 프로덕션은 발신번호 시드가 없어 실 발송까지는 미테스트 (시드 적용 후 가능).",[84,23735,23736],{},"프로덕션 admin\u002Fmigrate가 404 차단이라 시드는 로컬 dev 경유로만 — 향후 신청·승인 흐름 도입 시 자연스레 해결.",[237,23738,23740],{"id":23739},"_204-라이브-main-일치","20.4 라이브 ↔ main 일치",[18,23742,22456,23743,6100,23745,23341],{},[32,23744,12995],{},[32,23746,23363],{},[237,23748,23750],{"id":23749},"_205-다음-단계-변경-없음","20.5 다음 단계 (변경 없음)",[6674,23752,23753,23759,23762,23765],{},[84,23754,23755,23756,23758],{},"🐛 멱등 버그 수정 — ",[32,23757,23503],{}," + INSERT-then-conflict",[84,23760,23761],{},"NHN SMS 어댑터 + Queues + consumer worker (실 발송)",[84,23763,23764],{},"다른 채널 send (RCS\u002FKakao\u002FEmail\u002FPush\u002FFlow)",[84,23766,23767],{},"Export 잡 — 90일 초과 이력 우회",[76,23769,23771],{"id":23770},"_21-재배포-5-코드-변경-없는-새-version-발급","21. 재배포 #5 — 코드 변경 없는 새 Version 발급",[18,23773,23774,23775,23778,23779,23781,23782,5069,23784,23787],{},"§20 배포 직후 ",[32,23776,23777],{},"git push"," 단계에서 도구 권한 오류로 history 커밋이 중단됨. 다음 세션에서 사용자가 \"배포\"를 재실행하여 finalize. API 코드는 ",[32,23780,23363],{}," 그대로 (linter가 ",[32,23783,22951],{},[32,23785,23786],{},"as unknown as JwtPayload"," 캐스트 명시화 — HEAD에 이미 반영).",[81,23789,23790,23795,23798],{},[84,23791,23195,23792],{},[32,23793,23794],{},"afaa4c89-999e-4c0d-832e-3aef96acc326",[84,23796,23797],{},"같은 번들(2450 KiB \u002F gzip 571), Worker Startup 60 ms",[84,23799,13284,23800,4420,23802,23804,23805,23807,23808,23810],{},[32,23801,11248],{},[32,23803,20116],{}," (mysql 8.0.42) · ",[32,23806,16900],{}," (paths 44 · ops 78 · schemas 53) · ",[32,23809,12271],{}," 미인증 401 — 4건 모두 정상.",[18,23812,23813,23814,23816],{},"라이브 ↔ main: ",[32,23815,23363],{},"로 일치.",[76,23818,23820,23821,4343],{"id":23819},"_22-멱등-버그-해결-tb_idempotency-insert-then-conflict-020307f","22. 🐛 멱등 버그 해결 — TB_IDEMPOTENCY + INSERT-then-conflict (",[32,23822,23823],{},"020307f",[18,23825,23826],{},"§19·§21에서 추적했던 발송 멱등 버그(같은 Idempotency-Key 재호출 시 중복 적재) 정식 수정.",[237,23828,23830],{"id":23829},"_221-해결-패턴","22.1 해결 패턴",[81,23832,23833,23853,23859],{},[84,23834,23835,23838,23839,23841,23842,4339,23844,4339,23847,4473,23850,4343],{},[21,23836,23837],{},"0001_idempotency.sql"," — 비파티션 추적 테이블 ",[32,23840,23503],{}," (PK ",[32,23843,20071],{},[32,23845,23846],{},"scope",[32,23848,23849],{},"idempotency_key",[32,23851,23852],{},"result_id NULL→채움",[84,23854,23855,23858],{},[21,23856,23857],{},"race-free",": MySQL이 PK 인덱스로 atomic dedup. 진행 중 트랜잭션과는 row-lock 대기 후 duplicate key error.",[84,23860,23861,9194,23864],{},[21,23862,23863],{},"\u002Fsend\u002Fsms 신규 흐름",[6674,23865,23866,23872,23882,23889,23896],{},[84,23867,23868,23871],{},[32,23869,23870],{},"INSERT TB_IDEMPOTENCY (resultType='pending')"," — 점유 시도",[84,23873,23874,23875,23878,23879,23881],{},"중복키 에러 → 다른 요청이 owner. ",[32,23876,23877],{},"result_id","로 기존 ",[32,23880,20187],{}," 반환 (idempotent:true)",[84,23883,23884,23885,23888],{},"점유 성공 → 검증·트랜잭션 진행, 마지막에 ",[32,23886,23887],{},"UPDATE result_id"," 로 매핑",[84,23890,23891,23892,23895],{},"트랜잭션 실패 시 ",[32,23893,23894],{},"rollbackIdempotency()"," — 키 해제(재시도 가능)",[84,23897,23898,23899,23902],{},"진행 중인 요청이 commit 전인 경우 ",[32,23900,23901],{},"202 idempotent_in_flight"," 응답",[237,23904,23906],{"id":23905},"_222-적용-검증","22.2 적용 + 검증",[81,23908,23909,23919],{},[84,23910,23911,23912,23914,23915,23918],{},"Aurora에 ",[32,23913,23837],{}," 적용: ",[32,23916,23917],{},"count=50"," (TB_IDEMPOTENCY 신규)",[84,23920,23921,23922],{},"pnpm dev 검증:\n",[81,23923,23924,23927,23937],{},[84,23925,23926],{},"call 1 (key=K) → dispatchRequestId=8, idempotent:false",[84,23928,23929,23930,23933,23934,23936],{},"call 2 (key=K) → dispatchRequestId=",[21,23931,23932],{},"8"," (같음), idempotent:",[21,23935,21259],{}," ✅",[84,23938,23939],{},"call 3 (key=K2) → dispatchRequestId=9 (새), idempotent:false ✅",[237,23941,23943],{"id":23942},"_223-부속-변경","22.3 부속 변경",[81,23945,23946,23954,23965],{},[84,23947,23948,89,23950,23953],{},[32,23949,21654],{},[32,23951,23952],{},"idempotency"," 테이블 정의 추가",[84,23955,23956,89,23958,23960,23961,23964],{},[32,23957,22139],{},[32,23959,12271],{}," description의 TODO 문구 제거 + 202 ",[32,23962,23963],{},"idempotent_in_flight"," 응답 추가",[84,23966,23967,23969,23970,23973,23974,23976],{},[32,23968,22951],{}," — linter가 ",[32,23971,23972],{},"verifyJwt"," 캐스트를 ",[32,23975,23786],{},"로 명시화",[237,23978,23980],{"id":23979},"_224-다음-단계-변동-없음","22.4 다음 단계 (변동 없음)",[6674,23982,23983,23986,23989,23991],{},[84,23984,23985],{},"✅ 멱등 버그 — 완료",[84,23987,23988],{},"NHN SMS 어댑터 + Cloudflare Queues + consumer worker (실 발송)",[84,23990,23764],{},[84,23992,23767],{},[76,23994,23996],{"id":23995},"_23-malgn-noti-api-프로덕션-배포-6-queues-producer-consumer-라이브","23. malgn-noti-api 프로덕션 배포 #6 — Queues Producer + Consumer 라이브",[18,23998,23999,24000,4473,24002,24005],{},"§22 멱등 수정 + NHN 어댑터 + Queues 일체(",[32,24001,23823],{},[32,24003,24004],{},"5e1ac72",") 라이브 반영.",[237,24007,24009],{"id":24008},"_231-배포","23.1 배포",[81,24011,24012,24017,24020,24023],{},[84,24013,23195,24014],{},[32,24015,24016],{},"b30dc2a3-dc5a-4050-a435-c3d03a5e69a7",[84,24018,24019],{},"번들: 2460 KiB \u002F gzip 572 KiB",[84,24021,24022],{},"Worker Startup 74 ms",[84,24024,24025,9194,24028],{},[21,24026,24027],{},"신규 바인딩 라이브",[81,24029,24030,24039],{},[84,24031,24032,6685,24035,24038],{},[32,24033,24034],{},"env.DISPATCH_QUEUE",[32,24036,24037],{},"malgn-noti-dispatch",") — Producer + Consumer 동시",[84,24040,24041,24044,24045,24048],{},[32,24042,24043],{},"env.NHN_MOCK"," secret = ",[32,24046,24047],{},"\"1\""," (모의 모드)",[237,24050,24052],{"id":24051},"_232-검증","23.2 검증",[101,24054,24055,24063],{},[104,24056,24057],{},[107,24058,24059,24061],{},[110,24060,22359],{},[110,24062,22362],{},[126,24064,24065,24075,24085,24094,24105],{},[107,24066,24067,24071],{},[131,24068,24069],{},[32,24070,22384],{},[131,24072,23229,24073],{},[32,24074,23232],{},[107,24076,24077,24081],{},[131,24078,24079],{},[32,24080,20624],{},[131,24082,23229,24083],{},[32,24084,23243],{},[107,24086,24087,24091],{},[131,24088,24089],{},[32,24090,22125],{},[131,24092,24093],{},"200, 65.3 KB, paths 44 · ops 78 · schemas 53",[107,24095,24096,24100],{},[131,24097,24098,23280],{},[32,24099,23428],{},[131,24101,24102,24103],{},"401 ",[32,24104,22430],{},[107,24106,24107,24113],{},[131,24108,24109,6685,24111,4343],{},[32,24110,22974],{},[32,24112,23677],{},[131,24114,23680],{},[18,24116,24117,24118,4339,24121,24124],{},"배포 명세 — ",[32,24119,24120],{},"Producer for malgn-noti-dispatch",[32,24122,24123],{},"Consumer for malgn-noti-dispatch"," 동시 등록 확인.",[237,24126,24128],{"id":24127},"_233-큐-end-to-end-검증-보류","23.3 큐 end-to-end 검증 — 보류",[18,24130,24131,24133,24134,4536,24137,24140],{},[21,24132,13542],{},": Cloudflare 원격 미리보기 인프라 장애 (1105 Temporarily unavailable, Ray ID ",[32,24135,24136],{},"a02212096ea185af",[32,24138,24139],{},"a02213318ecb85af"," 등). 30분간 지속.",[81,24142,24143,24148],{},[84,24144,20657,24145,24147],{},[32,24146,21386],{},"가 Cloudflare edge-preview 토큰을 받아오는 단계에서 503 → 시드 SQL POST가 모두 1105 응답",[84,24149,24150,24151,24154,24155,24158,24159,24161,24162,24164],{},"결과: 프로덕션 company 4에 ",[32,24152,24153],{},"sender_phone"," (승인) + ",[32,24156,24157],{},"credit_balance"," 시드 불가 → ",[32,24160,12271],{}," 호출이 404 ",[32,24163,23705],{},"로 종료",[18,24166,24167],{},"코드·바인딩 자체는 정상 등록됐고, 큐 처리 흐름은 Cloudflare 회복 후 재검증 예정. 검증 절차:",[6674,24169,24170,24175,24181,24187,24192,24211],{},[84,24171,24172,24174],{},[32,24173,21386],{}," (인프라 회복 후)",[84,24176,24177,24180],{},[32,24178,24179],{},"\u002Fadmin\u002Fmigrate?allow_existing=1"," 로 company 4 시드",[84,24182,24183,24184,24186],{},"PROD URL ",[32,24185,23261],{}," → JWT",[84,24188,24183,24189,24191],{},[32,24190,12271],{}," (senderPhoneId=100)",[84,24193,24194,24195,24198,24199,24201,24202,6219,24205,6219,24208],{},"5~10초 대기 후 PROD ",[32,24196,24197],{},"\u002Fdispatch\u002Frequests\u002F:id"," 로 ",[32,24200,20261],{}," 천이 추적: ",[32,24203,24204],{},"queued",[32,24206,24207],{},"sending",[32,24209,24210],{},"delivered",[84,24212,24213,24216,24217,4473,24220,24223],{},[32,24214,24215],{},"\u002Fdispatch\u002Frequests\u002F:id\u002Fitems"," 에서 ",[32,24218,24219],{},"send_state=sent",[32,24221,24222],{},"nhn_request_id=mock-..."," 확인",[237,24225,24227],{"id":24226},"_234-라이브-main-일치","23.4 라이브 ↔ main 일치",[18,24229,22456,24230,6100,24232,24234],{},[32,24231,12995],{},[32,24233,24004],{},") 일치.",[76,24236,24238],{"id":24237},"_24-다음-단계-알려진-한계","24. 다음 단계 \u002F 알려진 한계",[81,24240,24241,24251,24259,24266,24277,24287,24307],{},[84,24242,24243,24246,24247,24250],{},[21,24244,24245],{},"DDL 적용"," — Hyperdrive 콘솔은 자격증명만 보유. Aurora 측에 ",[32,24248,24249],{},"0000_initial.sql","을 적용해야 실제 테이블 생성. MySQL CLI 또는 Bastion 경유.",[84,24252,24253,89,24256,24258],{},[21,24254,24255],{},"파티션 자동 운영 Cron Worker",[32,24257,21632],{}," (월 1일 DROP + 25일 REORGANIZE).",[84,24260,24261,89,24264,21646],{},[21,24262,24263],{},"시드 데이터",[32,24265,21645],{},[84,24267,24268,24271,24272,15217,24274,24276],{},[21,24269,24270],{},"Drizzle 스키마 자동 생성"," — DDL 적용 후 ",[32,24273,21651],{},[32,24275,21654],{}," 생성, 카멜케이스 객체명 정리.",[84,24278,24279,24282,24283,24286],{},[21,24280,24281],{},"Reader 분리"," — 트래픽 증가 시 별도 Reader Hyperdrive 추가, ",[32,24284,24285],{},"HYPERDRIVE_R"," 바인딩 (SCALABILITY §6).",[84,24288,24289,89,24297,24299,24300,4361,24303,24306],{},[21,24290,24291,4361,24294],{},[32,24292,24293],{},"before",[32,24295,24296],{},"after",[32,24298,20390],{},"는 DDL에서 MySQL 예약어 충돌 회피 차 ",[32,24301,24302],{},"before_json",[32,24304,24305],{},"after_json","으로 명명. DATA-MODEL.md 본문 표기와 다음 동기화 시 일치 필요.",[84,24308,24309,24312,24313,24316,24317,13735,24319,24322],{},[21,24310,24311],{},"로컬 MySQL 옵션"," — 오프라인 개발이 필요해지면 ",[32,24314,24315],{},"localConnectionString = \"mysql:\u002F\u002F...\""," 추가하고 ",[32,24318,20679],{},[32,24320,24321],{},"--remote"," 제거.",[8560,24324,24325],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":4208,"searchDepth":4209,"depth":4209,"links":24327},[24328,24329,24331,24333,24335,24337,24339,24340,24341,24347,24349,24350,24366,24378,24386,24394,24403,24412,24419,24427,24435,24436,24443,24449],{"id":10635,"depth":4212,"text":10636},{"id":20144,"depth":4212,"text":24330},"1. 데이터 모델 (malgn-noti-api\u002Fdoc\u002FDATA-MODEL.md)",{"id":20317,"depth":4212,"text":24332},"2. ERD (malgn-noti-api\u002Fdoc\u002FERD.md)",{"id":20358,"depth":4212,"text":24334},"3. 확장성·파티셔닝 전략 (malgn-noti-api\u002Fdoc\u002FSCALABILITY.md)",{"id":20481,"depth":4212,"text":24336},"4. 초기 DDL (malgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0000_initial.sql)",{"id":20550,"depth":4212,"text":24338},"5. Hyperdrive 연결 (malgn-noti-api\u002Fwrangler.toml + 신규 코드)",{"id":20651,"depth":4212,"text":20652},{"id":20697,"depth":4212,"text":20698},{"id":20741,"depth":4212,"text":20742,"children":24342},[24343,24345,24346],{"id":20745,"depth":4209,"text":24344},"신규 파일 (malgn-noti-api\u002F)",{"id":20792,"depth":4209,"text":20793},{"id":20824,"depth":4209,"text":20825},{"id":20854,"depth":4212,"text":24348},"10. 운영 컨벤션 명문화 (malgn-noti-api\u002FCLAUDE.md §8.1)",{"id":20946,"depth":4212,"text":20947},{"id":21038,"depth":4212,"text":21039,"children":24351},[24352,24354,24355,24356,24357,24359,24361,24363,24365],{"id":21049,"depth":4209,"text":24353},"12.1 \u002Fadmin\u002F* 라우트 (malgn-noti-api\u002Fsrc\u002Froutes\u002Fadmin.ts)",{"id":21145,"depth":4209,"text":21146},{"id":21299,"depth":4209,"text":21300},{"id":21344,"depth":4209,"text":21345},{"id":21375,"depth":4209,"text":24358},"12.5 결정 — admin 라우트는 로컬 전용 (선택 A 유지)",{"id":21390,"depth":4209,"text":24360},"12.6 환경 가드 추가 — 실수로도 프로덕션에 안 뚫리도록 (63ba424)",{"id":21562,"depth":4209,"text":24362},"12.7-pdf ERD를 인쇄용 PDF로 (malgn-noti-api\u002Fdoc\u002FERD.pdf, 470b55a)",{"id":21602,"depth":4209,"text":24364},"12.7 마이그레이션 절차 정본 — malgn-noti-api\u002Fdoc\u002FMIGRATION.md (47afe1a)",{"id":21623,"depth":4209,"text":21624},{"id":21658,"depth":4212,"text":24367,"children":24368},"13. 기본 CRUD API 골격 — Hono + Drizzle + Zod (a146e81, 8128468, 2b6f720)",[24369,24371,24373,24375,24377],{"id":21678,"depth":4209,"text":24370},"13.1 사전 픽스 (a146e81)",{"id":21715,"depth":4209,"text":24372},"13.2 의존성 (8128468)",{"id":21738,"depth":4209,"text":24374},"13.3 인프라 + 라우트 (2b6f720)",{"id":21888,"depth":4209,"text":24376},"13.4 검증 (pnpm dev --remote + curl)",{"id":22045,"depth":4209,"text":22046},{"id":22107,"depth":4212,"text":24379,"children":24380},"14. API 문서 페이지 — \u002Fdoc Scalar UI (beef401)",[24381,24382,24384,24385],{"id":22117,"depth":4209,"text":22118},{"id":22135,"depth":4209,"text":24383},"14.2 내용 (src\u002Fopenapi.ts)",{"id":22257,"depth":4209,"text":22258},{"id":22284,"depth":4209,"text":22285},{"id":22302,"depth":4212,"text":22303,"children":24387},[24388,24390,24391,24393],{"id":22313,"depth":4209,"text":24389},"15.1 배포 흐름 (malgn-noti-api\u002FCLAUDE.md §8.1 준수)",{"id":22326,"depth":4209,"text":22327},{"id":22346,"depth":4209,"text":24392},"15.3 검증 (https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev)",{"id":22452,"depth":4209,"text":22453},{"id":22465,"depth":4212,"text":24395,"children":24396},"16. 14개 도메인 라우트 추가 — 발신정보 \u002F 주소록 \u002F 템플릿 \u002F 문의 \u002F 결제 (3bd9864)",[24397,24399,24400,24401,24402],{"id":22475,"depth":4209,"text":24398},"16.1 스키마 확장 (src\u002Fdb\u002Fschema.ts)",{"id":22546,"depth":4209,"text":22547},{"id":22739,"depth":4209,"text":22740},{"id":22772,"depth":4209,"text":22773},{"id":22808,"depth":4209,"text":22809},{"id":22848,"depth":4212,"text":24404,"children":24405},"17. Phase 1·2·3 — \u002Fdoc 동기화 + 인증 + 발송 이력 (32f7ce4, c19f116, f45ad01)",[24406,24408,24410],{"id":22864,"depth":4209,"text":24407},"17.1 Phase 1 — openapi.ts 확장 (32f7ce4)",{"id":22922,"depth":4209,"text":24409},"17.2 Phase 2 — 인증 (signup \u002F login + JWT) (c19f116)",{"id":23041,"depth":4209,"text":24411},"17.3 Phase 3 — 발송 이력 read-only (f45ad01)",{"id":23156,"depth":4212,"text":23157,"children":24413},[24414,24415,24416,24417,24418],{"id":23163,"depth":4209,"text":23164},{"id":23184,"depth":4209,"text":23185},{"id":23207,"depth":4209,"text":23208},{"id":23333,"depth":4209,"text":23334},{"id":23344,"depth":4209,"text":23345},{"id":23359,"depth":4212,"text":24420,"children":24421},"19. POST \u002Fsend\u002Fsms — 발송 producer (DB 적재까지) (fb99b66)",[24422,24423,24424,24425,24426],{"id":23366,"depth":4209,"text":23367},{"id":23406,"depth":4209,"text":23407},{"id":23432,"depth":4209,"text":23433},{"id":23472,"depth":4209,"text":23473},{"id":23511,"depth":4209,"text":23512},{"id":23574,"depth":4212,"text":23575,"children":24428},[24429,24430,24432,24433,24434],{"id":23584,"depth":4209,"text":23585},{"id":23601,"depth":4209,"text":24431},"20.2 검증 (https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev)",{"id":23724,"depth":4209,"text":23725},{"id":23739,"depth":4209,"text":23740},{"id":23749,"depth":4209,"text":23750},{"id":23770,"depth":4212,"text":23771},{"id":23819,"depth":4212,"text":24437,"children":24438},"22. 🐛 멱등 버그 해결 — TB_IDEMPOTENCY + INSERT-then-conflict (020307f)",[24439,24440,24441,24442],{"id":23829,"depth":4209,"text":23830},{"id":23905,"depth":4209,"text":23906},{"id":23942,"depth":4209,"text":23943},{"id":23979,"depth":4209,"text":23980},{"id":23995,"depth":4212,"text":23996,"children":24444},[24445,24446,24447,24448],{"id":24008,"depth":4209,"text":24009},{"id":24051,"depth":4209,"text":24052},{"id":24127,"depth":4209,"text":24128},{"id":24226,"depth":4209,"text":24227},{"id":24237,"depth":4212,"text":24238},{},"\u002Fhistory\u002Fhistory.20260526",{"title":20049,"description":4208},"history\u002Fhistory.20260526","RIk-WTDanK0NymPPuHC1C6TWMJIOkuhOG52nCRUXtIY",{"id":24456,"title":24457,"body":24458,"description":4208,"extension":4257,"meta":26844,"navigation":4259,"path":26845,"seo":26846,"stem":26847,"__hash__":26848},"docs\u002Fhistory\u002Fhistory.20260527.md","2026-05-27 — malgn-noti-admin Nuxt 3 부트스트랩 + 셸 레이아웃 + 첫 프로덕션 배포 + malgn-noti-api 멱등 수정·NHN 어댑터·Queues·webhook·Email\u002FKakao\u002FPush 채널·배포 #6·#7 + 사용자단 추가(랜딩페이지·문의 이동·나의 페이지·충전 — 배포 #44~#46) + malgn-noti-api 루트→\u002Fdoc 리다이렉트 + RCS 채널·Export 잡·Flow 정의 정식 커밋·배포 #8",{"type":8,"value":24459,"toc":26792},[24460,24463,24465,24566,24570,24629,24633,24740,24744,24834,24838,24882,24886,24928,24930,24972,24974,24978,24985,24995,25004,25050,25060,25063,25216,25225,25235,25239,25321,25325,25362,25385,25389,25395,25398,25425,25432,25435,25487,25494,25501,25505,25512,25583,25587,25704,25708,25768,25782,25786,25795,25801,25808,25812,25817,25881,25895,25899,25923,25927,25930,25978,25980,25984,25993,25997,26084,26088,26134,26138,26144,26148,26227,26231,26275,26279,26320,26324,26355,26359,26369,26373,26405,26409,26421,26424,26428,26469,26473,26537,26541,26580,26584,26656,26658,26661,26723,26726,26789],[11,24461,24457],{"id":24462},"_2026-05-27-malgn-noti-admin-nuxt-3-부트스트랩-셸-레이아웃-첫-프로덕션-배포-malgn-noti-api-멱등-수정nhn-어댑터queueswebhookemailkakaopush-채널배포-67-사용자단-추가랜딩페이지문의-이동나의-페이지충전-배포-4446-malgn-noti-api-루트doc-리다이렉트-rcs-채널export-잡flow-정의-정식-커밋배포-8",[76,24464,10636],{"id":10635},[18,24466,24467,24468,24471,24472,4339,24475,24478,24479,24482,24483,24485,24486,24489,24490,24492,24493,24495,24496,24498,24499,24502,24503,6251,24506,24509,24510,7086,24513,24516,24517,24519,24520,24523,24524,24526,24527,24529,24530,7086,24533,24536,24537,24540,24541,4339,24544,24546,24547,4339,24550,24553,24554,24557,24558,24561,24562,24565],{},"세 트랙 병행. ",[21,24469,24470],{},"(A) malgn-noti-admin"," — 비어 있던 BackOffice 레포를 ",[21,24473,24474],{},"Nuxt 3 + Nuxt UI v3로 부트스트랩",[32,24476,24477],{},"design_handoff_customer_detail"," 정본 참조 ",[21,24480,24481],{},"LNB(256px sticky · 8 그룹 메뉴) + TopBar(64px sticky)"," 셸 레이아웃 + ",[32,24484,11230],{}," 첫 Nuxt 앱 프로덕션 배포(정적 placeholder 대체). ",[21,24487,24488],{},"(B) malgn-noti-api"," — §19에서 추적한 멱등 버그를 ",[32,24491,23503],{}," + INSERT-then-conflict race-free 패턴으로 정식 해결 + NHN SMS 어댑터(mock\u002Freal) + Cloudflare Queues(",[32,24494,24037],{},") + consumer worker(",[32,24497,20261],{}," 천이) + ",[32,24500,24501],{},"POST \u002Fwebhooks\u002Fnhn\u002Fsms"," HMAC 콜백 수신 + Producer·Consumer 동시 바인딩 프로덕션 배포 #6(Version ",[32,24504,24505],{},"b30dc2a3...",[21,24507,24508],{},"Email · Kakao(알림톡\u002F친구톡) · Push 3채널 동시 추가"," + 배포 #7(Version ",[32,24511,24512],{},"12dae362...",[21,24514,24515],{},"(C) malgn-noti 사용자단"," — 메시지 관리 랜딩페이지 만들기 신규(목록·기본형\u002F확장형 등록 폼·미리보기 모달) + 문의하기 경로를 ",[32,24518,16882],{},"로 이동(GNB·푸터·사이트맵 링크 정리) + 나의 페이지 섹션(공통 셸 + 9개 라우트·회원 정보 변경·결제 카드 관리·이메일\u002F휴대폰 인증 모달) + 크레딧 충전 플로우(충전 페이지 재구성·결제 컨펌·결과 화면)로 Pages 배포 #44·#45·#46. ",[21,24521,24522],{},"(D) malgn-noti-api 추가"," — 루트(",[32,24525,4361],{},") placeholder JSON을 ",[32,24528,18666],{},"로 교체해 워커 도메인 접속이 곧장 Scalar API 문서로 이동하도록 변경, Workers 재배포(Version ",[32,24531,24532],{},"f3fd3eb4...",[21,24534,24535],{},"(E) malgn-noti-api §13 WIP 정식 커밋"," — §13 시점에 라이브로 올라가 있던 RCS 채널(5채널째 — adapter·producer·consumer·webhook·",[32,24538,24539],{},"RCS_PRICING",") + Export 잡(",[32,24542,24543],{},"TB_EXPORT_JOB",[32,24545,23355],{}," CRUD) + Flow 정의(",[32,24548,24549],{},"TB_FLOW_DEFINITION\u002FRUN\u002FSTEP_RUN",[32,24551,24552],{},"\u002Fflow-definitions"," CRUD) + OpenAPI 4지점(+230) + ",[32,24555,24556],{},"0002_export_flow.sql"," 마이그레이션을 단일 배치 커밋(",[32,24559,24560],{},"1e7bd61",", 12 files +1228 −16) + 배포 #8(Version ",[32,24563,24564],{},"95f9f894...",")로 working tree ↔ main 동기화. DDL 적용은 Cloudflare 1105 회복 후로 보류.",[76,24567,24569],{"id":24568},"_1-사전-조사","1. 사전 조사",[81,24571,24572,24602,24620],{},[84,24573,24574,47,24577,24580,24581,24584,24585],{},[21,24575,24576],{},"참조 정본",[32,24578,24579],{},"\u002FUsers\u002Fdotype\u002FProjects\u002Fdesign_handoff_customer_detail\u002F"," — README + ",[32,24582,24583],{},"prototype\u002F"," HTML\u002FJSX 프로토타입. 고객사 상세 1화면 hi-fi 시안.\n",[81,24586,24587,24593,24596],{},[84,24588,24589,24590,24592],{},"권장 스택: Nuxt 3 + ",[21,24591,10521],{}," + Tailwind v4 + DM Sans(라틴) + Pretendard(한글) + Lucide.",[84,24594,24595],{},"레이아웃: LNB 256px · TopBar 64px · Content + MemoPanel 360px.",[84,24597,24598,24601],{},[32,24599,24600],{},"prototype\u002Flnb.jsx","에서 8개 그룹 메뉴 트리 + 액티브\u002F배지\u002FNEW 스타일 추출.",[84,24603,24604,47,24607,12916,24610,24612,24613,24616,24617,24619],{},[21,24605,24606],{},"대상 상태",[32,24608,24609],{},"malgn-noti-admin\u002F",[32,24611,7682],{},"(상세 운영 도메인 문서)·",[32,24614,24615],{},"doc\u002FDESIGN-ADMIN.md","(Part A 상속 + Part B 관리자 밀도)·정적 ",[32,24618,11224],{},"(placeholder)만 존재 — Nuxt 미부트스트랩.",[84,24621,24622,47,24625,24628],{},[21,24623,24624],{},"사용자 확인",[32,24626,24627],{},"AskUserQuestion"," — 산출물 형태 → \"Nuxt 3 부트스트랩 + 셸 레이아웃\", 범위 → \"셸만(LNB + TopBar + 빈 콘텐츠)\".",[76,24630,24632],{"id":24631},"_2-프로젝트-부트스트랩","2. 프로젝트 부트스트랩",[81,24634,24635,24662,24679,24688,24704,24712],{},[84,24636,24637,24639,24640,24642,24643,4420,24646,4420,24649,4420,24652,4420,24655,4420,24658,24661],{},[21,24638,10928],{},"(신규): name=",[32,24641,7672],{},", 사용자단과 동일 버전 라인 — ",[32,24644,24645],{},"nuxt ^3.16",[32,24647,24648],{},"@nuxt\u002Fui ^3.0",[32,24650,24651],{},"@iconify-json\u002Flucide",[32,24653,24654],{},"vue ^3.5",[32,24656,24657],{},"vue-router ^4.4",[32,24659,24660],{},"typescript ^5.6"," · pnpm 10.25. Pinia\u002FChart\u002FZod 제외 — 셸 범위 밖.",[84,24663,24664,16097,24666,24668,24669,4420,24672,24674,24675,24678],{},[21,24665,4405],{},[32,24667,10689],{},"(app\u002F 디렉터리 구조) · ",[32,24670,24671],{},"modules: ['@nuxt\u002Fui']",[32,24673,11189],{}," · DM Sans + Pretendard 폰트 head 주입 · ",[32,24676,24677],{},"noindex,nofollow"," 메타.",[84,24680,24681,24683,24684,24687],{},[21,24682,7890],{},"(신규): Nuxt 자동생성 ",[32,24685,24686],{},".nuxt\u002Ftsconfig.json"," 상속.",[84,24689,24690,24692,24693,24699,24700,24703],{},[21,24691,4392],{},"(신규): Nuxt UI 컬러 매핑 — ",[21,24694,24695,24696],{},"primary ",[32,24697,24698],{},"blue"," + neutral ",[32,24701,24702],{},"slate"," (핸드오프 정본).",[84,24705,24706,16097,24708,24711],{},[21,24707,10955],{},[32,24709,24710],{},"\u003CUApp :locale=\"ko\">\u003CNuxtLayout>\u003CNuxtPage\u002F>\u003C\u002FNuxtLayout>\u003C\u002FUApp>"," 루트.",[84,24713,24714,16097,24716,4339,24719,5439,24722,24724,24725,5069,24727,4420,24730,4420,24733,4420,24736,24739],{},[21,24715,4352],{},[32,24717,24718],{},"@import \"tailwindcss\"",[32,24720,24721],{},"@import \"@nuxt\u002Fui\"",[32,24723,4342],{},"에 폰트 정의(DM Sans + Pretendard), ",[32,24726,4338],{},[32,24728,24729],{},"--lnb-width: 256px",[32,24731,24732],{},"--topbar-height: 64px",[32,24734,24735],{},"--ring-default",[32,24737,24738],{},"--shadow-ring",". 본문 13px · slate-50 paper · slate-900 텍스트.",[76,24741,24743],{"id":24742},"_3-셸-레이아웃","3. 셸 레이아웃",[81,24745,24746,24761,24810,24828],{},[84,24747,24748,16097,24751,24754,24755,4339,24758,4703],{},[21,24749,24750],{},"app\u002Flayouts\u002Fdefault.vue",[32,24752,24753],{},"flex min-h-screen bg-slate-50"," 컨테이너 — ",[32,24756,24757],{},"\u003CAppLnb\u002F>",[32,24759,24760],{},"\u003Cdiv class=\"flex-1 min-w-0 flex flex-col\">\u003CAppTopbar\u002F>\u003Cmain class=\"flex-1 px-6 py-5\">\u003Cslot\u002F>\u003C\u002Fmain>\u003C\u002Fdiv>",[84,24762,24763,24766,24767],{},[21,24764,24765],{},"app\u002Fcomponents\u002FAppLnb.vue","(신규): 256px sticky 사이드바.\n",[81,24768,24769,24776,24783,24796,24807],{},[84,24770,24771,24772,24775],{},"브랜드 헤더 — 그라데이션 로고 + \"맑은 message ",[21,24773,24774],{},"Admin","\".",[84,24777,24778,24779,24782],{},"⌘K 검색 버튼(",[32,24780,24781],{},"ring-1 ring-inset"," 입력형).",[84,24784,24785,24788,24789,24792,24793,24795],{},[21,24786,24787],{},"8개 그룹 메뉴","(핸드오프 ",[32,24790,24791],{},"lnb.jsx"," 1:1 매핑) — 대시보드 · 회원\u002F고객사(고객사·계정·감사로그+",[32,24794,4662],{}," 배지·차단\u002F제재) · 발송 관리(PUSH·RCS·SMS\u002FLMS·알림톡·이메일·예약 발송+NEW) · 템플릿(채널 4종+치환 변수) · 리포트(발송량·실패율·채널·크레딧·정산) · 채널\u002F연동(FCM·APNs·RCS Biz·카카오 비즈·SMPP) · 결제\u002F크레딧(발급·사용 내역·세금계산서) · 운영(공지·FAQ·1:1 문의).",[84,24797,24798,24799,24802,24803,24806],{},"1depth 액티브 — ",[32,24800,24801],{},"bg-blue-50 text-blue-700"," + 아이콘 박스 ",[32,24804,24805],{},"bg-blue-100",". 2depth 액티브 — 좌측 점 표식. 그룹 펼침\u002F접힘 토글.",[84,24808,24809],{},"하단 사용자 chip(아바타 + 이름\u002F이메일 + chevron).",[84,24811,24812,24815,24816],{},[21,24813,24814],{},"app\u002Fcomponents\u002FAppTopbar.vue","(신규): 64px sticky 상단 바.\n",[81,24817,24818,24821],{},[84,24819,24820],{},"좌측 — 사이드바 토글 + 홈 아이콘 · 브레드크럼 \"대시보드\".",[84,24822,24823,24824,24827],{},"우측 — ",[32,24825,24826],{},"bg-emerald-50 ring-emerald-200"," 시스템 상태 pill(\"시스템 정상 · 14ms\"), 검색·벨(+빨간 점), 회사 스위처(\"맑은소프트\"), 사용자 chip.",[84,24829,24830,24833],{},[21,24831,24832],{},"app\u002Fpages\u002Findex.vue","(신규): 빈 콘텐츠 데모 — 페이지 제목 \"맑은 메시징 BackOffice\" + 가운데 placeholder 카드(\"페이지 콘텐츠가 들어갈 영역입니다\").",[76,24835,24837],{"id":24836},"_4-부팅-트러블슈팅","4. 부팅 트러블슈팅",[81,24839,24840,24845,24857,24871],{},[84,24841,24842,24844],{},[32,24843,11062],{}," 완료(Nuxt 3.21.6, Nuxt UI 3.3.7).",[84,24846,24847,24850,24851,24853,24854,24856],{},[32,24848,24849],{},"pnpm dev --port 3001"," 백그라운드 실행 → ",[32,24852,4361],{}," 응답이 부트스트랩 전 정적 ",[32,24855,11224],{},"(어두운 placeholder)을 반환.",[84,24858,24859,24861,24862,24865,24866,21731,24868,24870],{},[21,24860,13542],{},": Nitro가 ",[32,24863,24864],{},"public\u002F"," 정적 파일을 Nuxt 라우트보다 우선 처리 → ",[32,24867,11224],{},[32,24869,4361],{},"를 가로챔.",[84,24872,24873,47,24876,24878,24879,24881],{},[21,24874,24875],{},"해결",[32,24877,11224],{}," 삭제 → Nuxt SSR이 ",[32,24880,24832],{},"를 정상 렌더, LNB 메뉴 마커(대시보드·발송 관리·템플릿·리포트) 확인.",[76,24883,24885],{"id":24884},"_5-첫-프로덕션-배포","5. 첫 프로덕션 배포",[81,24887,24888,24895,24900,24910,24918],{},[84,24889,24890,6219,24892,24894],{},[32,24891,11195],{},[32,24893,10325],{}," 272 kB gzip (셸 단계라 작음).",[84,24896,24897,4703],{},[32,24898,24899],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti-admin --branch=main --commit-dirty=true --commit-message \"Initial admin shell: Nuxt 3 + Nuxt UI v3 + LNB + TopBar\"",[84,24901,24902,24903,24906,24907,4703],{},"프로덕션 검증 — ",[32,24904,24905],{},"https:\u002F\u002Fmalgn-noti-admin.pages.dev\u002F"," HTTP 200, \"대시보드·발송 관리·템플릿·맑은 메시징\" 마커 확인. alias ",[32,24908,24909],{},"https:\u002F\u002F471451c7.malgn-noti-admin.pages.dev",[84,24911,15276,24912,24915,24916,5606],{},[32,24913,24914],{},"6cbf238 Nuxt 3 + Nuxt UI v3 부트스트랩 + LNB·TopBar 셸 레이아웃"," (12 files, +9064 −24 — 대부분 ",[32,24917,20821],{},[84,24919,20848,24920,6685,24922,4473,24925,5606],{},[32,24921,12610],{},[32,24923,24924],{},"d2a6bb6..6cbf238",[32,24926,24927],{},"malgnsoft\u002Fmalgn-noti-admin",[76,24929,10916],{"id":10916},[81,24931,24932,24960,24966],{},[84,24933,24934,24935,24937,24938,4420,24940,4420,24942,4420,24944,4420,24946,4420,24948,4420,24950,4420,24952,4420,24954,4420,24957,24959],{},"신규 파일(",[32,24936,24609],{},"): ",[32,24939,10928],{},[32,24941,4405],{},[32,24943,7890],{},[32,24945,20821],{},[32,24947,4392],{},[32,24949,10955],{},[32,24951,4352],{},[32,24953,24750],{},[32,24955,24956],{},"app\u002Fcomponents\u002F{AppLnb,AppTopbar}.vue",[32,24958,24832],{}," (10종 + lockfile).",[84,24961,24962,24963,24965],{},"삭제: ",[32,24964,11224],{}," (부트스트랩 전 placeholder, Nuxt 라우트 가로채는 충돌 해소).",[84,24967,24968,24969,24971],{},"프로덕션 URL: ",[32,24970,11230],{}," (첫 Nuxt 앱 배포 — 이전 #2 placeholder 정적 페이지 대체).",[73,24973],{},[11,24975,24977],{"id":24976},"트랙-b-malgn-noti-api-백엔드-작업","트랙 B — malgn-noti-api 백엔드 작업",[18,24979,24980,24981,24984],{},"§19에서 추적한 멱등 버그 정식 해결 + 실제 NHN 발송 경로(어댑터 + 큐 + worker) 구축 + 프로덕션 배포 #6. 본 트랙의 상세는 ",[32,24982,24983],{},"history.20260526.md §22·§23"," 에 기록 — 26일 파일에 §N으로 누적 중이던 흐름의 연속이라 자연 위치를 따랐고, 본 27일 파일에는 요약 + 참조만 둠.",[76,24986,24988,24989,4473,24992,4343],{"id":24987},"_6-멱등-버그-해결-malgn-noti-api-020307f-history20260526md-22","6. 멱등 버그 해결 (",[32,24990,24991],{},"malgn-noti-api 020307f",[32,24993,24994],{},"history.20260526.md §22",[18,24996,24997,24998,25000,25001,25003],{},"§19에서 보고했던 ",[32,24999,12271],{}," 멱등 버그(같은 ",[32,25002,23374],{}," 재호출 시 중복 적재·크레딧 중복 차감)를 정식 수정.",[81,25005,25006,25018,25024,25040],{},[84,25007,25008,23838,25010,23841,25012,4473,25015,25017],{},[21,25009,23837],{},[32,25011,23503],{},[32,25013,25014],{},"(company_id, scope, idempotency_key)",[32,25016,23852],{},"). Aurora 적용 후 테이블 50개 라이브.",[84,25019,25020,25023],{},[21,25021,25022],{},"race-free 패턴"," — MySQL이 PK 인덱스로 atomic dedup. 진행 중 트랜잭션과는 row-lock 대기 후 duplicate key error.",[84,25025,25026,25028,25029,25031,25032,25034,25035,25037,25038,4703],{},[32,25027,12271],{}," 신규 흐름 — ",[32,25030,23870],{}," 점유 → 검증·트랜잭션 → 마지막에 ",[32,25033,23887],{},". 점유 후 실패 시 ",[32,25036,23894],{}," 로 키 해제(재시도 가능). 처리 중이지만 commit 전이면 ",[32,25039,23901],{},[84,25041,25042,25045,25046,25049],{},[21,25043,25044],{},"검증 3건",": call 1 (key=K) → ID=8, call 2 (key=K, 같음) → ",[21,25047,25048],{},"ID=8 + idempotent:true",", call 3 (key=K2) → ID=9. 통과 확인.",[76,25051,25053,25054,4473,25057,4343],{"id":25052},"_7-nhn-sms-어댑터-cloudflare-queues-consumer-worker-malgn-noti-api-5e1ac72-history20260526md-231232","7. NHN SMS 어댑터 + Cloudflare Queues + consumer worker (",[32,25055,25056],{},"malgn-noti-api 5e1ac72",[32,25058,25059],{},"history.20260526.md §23.1~23.2",[18,25061,25062],{},"발송 흐름의 비동기 처리 인프라 구축.",[81,25064,25065,25079,25121,25162,25176,25185,25197,25206],{},[84,25066,25067,47,25070,25072,25073,7086,25076,25078],{},[21,25068,25069],{},"Cloudflare Queue 생성",[32,25071,24037],{}," (id ",[32,25074,25075],{},"6c67d698...",[32,25077,11253],{},"에 producer + consumer 동시 바인딩, batch 10·timeout 5s·max_retries 3.",[84,25080,25081,6685,25083,9563,25086],{},[21,25082,23540],{},[32,25084,25085],{},"src\u002Fadapters\u002Fnhn\u002F",[81,25087,25088,25105],{},[84,25089,25090,89,25093,4420,25096,4420,25099,4420,25102,4703],{},[32,25091,25092],{},"types.ts",[32,25094,25095],{},"NhnCredentials",[32,25097,25098],{},"NhnSmsSendRequest",[32,25100,25101],{},"NhnSmsSendResponse",[32,25103,25104],{},"NormalizedSendResult",[84,25106,25107,89,25110,25113,25114,25117,25118,4703],{},[32,25108,25109],{},"sms.ts",[32,25111,25112],{},"sendSms(creds, input, mockMode)",". mock 모드는 외부 호출 없이 시뮬레이션, real 모드는 ",[32,25115,25116],{},"\u002Fsms\u002Fv3.0\u002FappKeys\u002F{appKey}\u002Fsender\u002F(sms|mms)"," POST + ",[32,25119,25120],{},"X-Secret-Key",[84,25122,25123,6685,25126,6121,25128,6219,25131,25134,25135,6219,25138,25141,25142,4536,25145,4536,25148,25151,25152,4339,25155,25158,25159,4703],{},[21,25124,25125],{},"Consumer worker",[32,25127,23534],{},[32,25129,25130],{},"processDispatchBatch(batch, env)",[32,25132,25133],{},"processOne()"," 으로 메시지마다: dispatch_request 조회 → items + sender + nhn_credential 조회 → ",[32,25136,25137],{},"dispatch_state→'sending'",[32,25139,25140],{},"sendSms()"," → item별 ",[32,25143,25144],{},"send_state",[32,25146,25147],{},"fail_code",[32,25149,25150],{},"nhn_request_id"," 갱신 → ",[32,25153,25154],{},"dispatch_state→'delivered'\u002F'failed'",[32,25156,25157],{},"completed_at",". 일시 오류는 ",[32,25160,25161],{},"msg.retry({ delaySeconds: 30 })",[84,25163,25164,89,25167,25169,25170,4339,25173,4703],{},[21,25165,25166],{},"Producer 연동",[32,25168,12271],{}," 트랜잭션 commit 후 ",[32,25171,25172],{},"c.executionCtx.waitUntil(env.DISPATCH_QUEUE.send({ dispatchRequestId }))",[32,25174,25175],{},"dispatch_state→'queued'",[84,25177,25178,89,25181,25184],{},[21,25179,25180],{},"스키마 확장",[32,25182,25183],{},"nhnCredential"," 테이블 Drizzle 추가 (DATA-MODEL §10.5 반영).",[84,25186,25187,89,25190,25192,25193,25196],{},[21,25188,25189],{},"Workers entry 변경",[32,25191,11243],{}," 의 default export를 ",[32,25194,25195],{},"{ fetch: app.fetch, queue: processDispatchBatch }"," 객체로 (fetch + queue handler 분리).",[84,25198,25199,89,25202,25205],{},[21,25200,25201],{},"secret",[32,25203,25204],{},"NHN_MOCK=1"," wrangler secret 등록(프로덕션도 모의 모드로 시작).",[84,25207,25208,25211,25212,25215],{},[21,25209,25210],{},"로컬 검증 한계"," — wrangler 경고 ",[32,25213,25214],{},"\"Queues are not yet supported in wrangler dev remote mode\""," → 로컬 dev에서 큐 처리 검증 불가.",[76,25217,25219,25220,6685,25222,4343],{"id":25218},"_8-nhn-webhook-핸들러-post-webhooksnhnsms-malgn-noti-api-0d28173","8. NHN webhook 핸들러 — ",[32,25221,24501],{},[32,25223,25224],{},"malgn-noti-api 0d28173",[18,25226,25227,25228,25230,25231,25234],{},"NHN의 발송 결과 콜백을 받아 ",[32,25229,21328],{}," 적재 + ",[32,25232,25233],{},"TB_DISPATCH_ITEM.recv_state"," 갱신. 발송 흐름의 피드백 루프를 완성.",[237,25236,25238],{"id":25237},"_81-흐름","8.1 흐름",[6674,25240,25241,25265,25281,25292,25303,25312],{},[84,25242,25243,89,25246,25249,25250,25253,25254],{},[21,25244,25245],{},"HMAC-SHA256 서명 검증",[32,25247,25248],{},"X-NHN-Signature: sha256=\u003Chex>"," 헤더 + ",[32,25251,25252],{},"NHN_WEBHOOK_SIGNING_SECRET"," env.\n",[81,25255,25256,25259],{},[84,25257,25258],{},"프로덕션: secret 미설정 → 403, 서명 불일치 → 401.",[84,25260,25261,25262,25264],{},"로컬 dev (",[32,25263,21515],{},"): 서명 헤더 없으면 우회(개발 편의).",[84,25266,25267,89,25270,6219,25273,25276,25277,25280],{},[21,25268,25269],{},"payload 정규화",[32,25271,25272],{},"parseSmsCallback()",[32,25274,25275],{},"msgStatus"," (0\u002F1\u002F2\u002F3\u002F4) → ",[32,25278,25279],{},"eventType"," (accepted\u002Fsent\u002Fdelivered\u002Ffailed\u002Fbounced).",[84,25282,25283,89,25286,4339,25288,25291],{},[21,25284,25285],{},"dispatch_item lookup",[32,25287,25150],{},[32,25289,25290],{},"recipient_address"," 로 매핑.",[84,25293,25294,89,25299,25302],{},[21,25295,25296,25298],{},[32,25297,21328],{}," INSERT",[32,25300,25301],{},"dedup_key = \"sms:\u003CrequestId>:\u003CrecipientNo>\"",". UNIQUE 위반 → 200 dedup (멱등).",[84,25304,25305,25311],{},[21,25306,25307,25310],{},[32,25308,25309],{},"recv_state"," 천이"," — delivered→success \u002F failed,bounced→failed \u002F 그 외 pending 유지.",[84,25313,25314,25317,25318,25320],{},[21,25315,25316],{},"dispatch_request 마무리"," — 모든 item이 final 이면 ",[32,25319,20261],{}," 갱신(best-effort).",[237,25322,25324],{"id":25323},"_82-신규-파일","8.2 신규 파일",[81,25326,25327,25338,25347,25355],{},[84,25328,25329,89,25331,25334,25335,5606],{},[32,25330,21654],{},[32,25332,25333],{},"dispatchEvent"," (파티션 PK ",[32,25336,25337],{},"(id, received_at)",[84,25339,25340,89,25343,25346],{},[32,25341,25342],{},"src\u002Flib\u002Fwebhook-signature.ts",[32,25344,25345],{},"verifyHmacSignature()",", Web Crypto + timing-safe.",[84,25348,25349,89,25352,25354],{},[32,25350,25351],{},"src\u002Fadapters\u002Fnhn\u002Fwebhook.ts",[32,25353,25272],{}," (NHN payload → NormalizedEvent).",[84,25356,25357,89,25360,4703],{},[32,25358,25359],{},"src\u002Froutes\u002Fwebhooks.ts",[32,25361,24501],{},[18,25363,25364,89,25367,25370,25371,25373,25374,89,25377,25380,25381,25384],{},[32,25365,25366],{},"index.ts",[32,25368,25369],{},"app.route('\u002Fwebhooks', webhooks)",", Bindings에 ",[32,25372,25252],{}," 추가.\n",[32,25375,25376],{},"openapi.ts",[32,25378,25379],{},"webhooks"," 태그 + ",[32,25382,25383],{},"\u002Fwebhooks\u002Fnhn\u002Fsms"," 경로(요청·응답·서명 헤더 명세).",[237,25386,25388],{"id":25387},"_83-검증-보류","8.3 검증 보류",[18,25390,25391,25392,25394],{},"Cloudflare 원격 미리보기 인프라 장애(1105) 지속 — 로컬 ",[32,25393,11083],{}," 자체가 503 → 웹훅 라우트 검증 차단. 타입 검사·코드 패턴은 동일.",[18,25396,25397],{},"Cloudflare 회복 후 검증 절차:",[6674,25399,25400,25405,25408,25417],{},[84,25401,25402,25403,4343],{},"NHN_WEBHOOK_SIGNING_SECRET 등록(",[32,25404,21365],{},[84,25406,25407],{},"외부에서 NHN 형식 + 서명 헤더로 POST",[84,25409,25410,24216,25412,4339,25415,24223],{},[32,25411,24215],{},[32,25413,25414],{},"recv_state=success",[32,25416,20398],{},[84,25418,25419,24198,25422,25424],{},[32,25420,25421],{},"\u002Fadmin\u002F...",[32,25423,21328],{}," 적재 행 확인",[76,25426,25428,25429,4343],{"id":25427},"_9-malgn-noti-api-프로덕션-배포-6-history20260526md-231233","9. malgn-noti-api 프로덕션 배포 #6 (",[32,25430,25431],{},"history.20260526.md §23.1~23.3",[18,25433,25434],{},"§6·§7 변경을 라이브 반영.",[81,25436,25437,25445,25456,25473],{},[84,25438,25439,47,25442,25444],{},[21,25440,25441],{},"Version",[32,25443,24016],{},". 번들 2460 KiB \u002F gzip 572. Worker Startup 74 ms.",[84,25446,25447,25448,4339,25452,24124],{},"배포 명세에 ",[21,25449,25450],{},[32,25451,24120],{},[21,25453,25454],{},[32,25455,24123],{},[84,25457,25458,25459,4473,25461,25463,25464,25466,25467,25469,25470,25472],{},"검증 5건 — ",[32,25460,11248],{},[32,25462,20116],{},"(mysql 8.0.42), ",[32,25465,16900],{},"(paths 44·ops 78·schemas 53), ",[32,25468,12271],{}," no-auth 401, ",[32,25471,23261],{}," JWT 발급 정상.",[84,25474,25475,25478,25479,4536,25481,25483,25484,25486],{},[21,25476,25477],{},"큐 e2e 검증 보류"," — Cloudflare 원격 미리보기 인프라 장애(1105 Temporarily unavailable, Ray ID ",[32,25480,24136],{},[32,25482,24139],{}," 등 30분 지속) → 로컬 ",[32,25485,21386],{}," 가 edge-preview 토큰을 받지 못해 prod Aurora 시드 SQL 송신 자체 실패. 코드·바인딩은 정상이며 Cloudflare 회복 후 외부에서 e2e 절차로 검증 가능.",[76,25488,25490,25491,4343],{"id":25489},"_10-email-kakao-push-채널-추가-malgn-noti-api-06cf052","10. Email · Kakao · Push 채널 추가 (",[32,25492,25493],{},"malgn-noti-api 06cf052",[18,25495,25496,25497,25500],{},"§7 SMS 토대 위에 3개 추가 채널을 동일 패턴으로 적층 — ",[21,25498,25499],{},"producer · adapter · worker · OpenAPI"," 4지점 갱신.",[237,25502,25504],{"id":25503},"_101-공통-패턴","10.1 공통 패턴",[18,25506,25507,25508,25511],{},"모든 ",[32,25509,25510],{},"POST \u002Fsend\u002F{channel}"," 라우트는 §6 멱등 + §7 큐 패턴을 재사용:",[6674,25513,25514,25522,25528,25540,25553,25562,25571],{},[84,25515,25516,25521],{},[21,25517,25518,25520],{},[32,25519,23374],{}," 헤더 점유"," — TB_IDEMPOTENCY INSERT-then-conflict (race-free).",[84,25523,25524,25527],{},[21,25525,25526],{},"발신자 자격 검증"," — own + 채널별 승인 상태(아래 표).",[84,25529,25530,25533,25534,4361,25537,25539],{},[21,25531,25532],{},"수신자 옵트아웃 필터"," — channelKind 키(",[32,25535,25536],{},"phone",[32,25538,12969],{},")로 TB_OPTOUT_ENTRY 매칭. Push만 예외(앱 내 설정 책임).",[84,25541,25542,25545,25546,25549,25550,25552],{},[21,25543,25544],{},"단가·총액 계산"," + 크레딧 hold (",[32,25547,25548],{},"TB_COMPANY.credit_balance"," UPDATE…WHERE balance ≥ amount + ",[32,25551,20387],{}," append).",[84,25554,25555,25561],{},[21,25556,25557,4339,25559],{},[32,25558,20187],{},[32,25560,21325],{}," bulk insert (단일 트랜잭션).",[84,25563,25564,25570],{},[21,25565,25566,25569],{},[32,25567,25568],{},"TB_IDEMPOTENCY.result_id"," = 발송 ID"," (트랜잭션 안에서).",[84,25572,25573,4339,25576,25579,25580,5606],{},[21,25574,25575],{},"큐 enqueue",[32,25577,25578],{},"dispatch_state=queued"," 천이 (",[32,25581,25582],{},"executionCtx.waitUntil",[237,25584,25586],{"id":25585},"_102-채널별-발신자옵트아웃단가","10.2 채널별 발신자·옵트아웃·단가",[101,25588,25589,25610],{},[104,25590,25591],{},[107,25592,25593,25596,25599,25601,25604,25607],{},[110,25594,25595],{},"채널",[110,25597,25598],{},"senderRef",[110,25600,7844],{},[110,25602,25603],{},"옵트아웃 키",[110,25605,25606],{},"단가(1건)",[110,25608,25609],{},"추가 검증",[126,25611,25612,25639,25672],{},[107,25613,25614,25618,25623,25628,25632,25634],{},[131,25615,25616],{},[32,25617,12969],{},[131,25619,25620],{},[32,25621,25622],{},"emailDomainId",[131,25624,25625],{},[32,25626,25627],{},"verified_yn=Y",[131,25629,25630],{},[32,25631,12969],{},[131,25633,7278],{},[131,25635,25636,25638],{},[32,25637,9490],{},"의 도메인이 emailDomain.domain과 일치",[107,25640,25641,25645,25650,25658,25662,25665],{},[131,25642,25643],{},[32,25644,12961],{},[131,25646,25647],{},[32,25648,25649],{},"kakaoSenderProfileId",[131,25651,25652,4339,25655],{},[32,25653,25654],{},"profile_state=승인",[32,25656,25657],{},"token_state=완료",[131,25659,25660],{},[32,25661,25536],{},[131,25663,25664],{},"alimtalk 8 \u002F friendtalk 12",[131,25666,25667,25668,25671],{},"알림톡은 ",[32,25669,25670],{},"templateCode"," 필수(Zod refine)",[107,25673,25674,25678,25683,25692,25695,25698],{},[131,25675,25676],{},[32,25677,12973],{},[131,25679,25680],{},[32,25681,25682],{},"pushCertId",[131,25684,25685,4339,25688,25691],{},[32,25686,25687],{},"status=1",[32,25689,25690],{},"expires_at"," 미경과",[131,25693,25694],{},"(없음)",[131,25696,25697],{},"0.5",[131,25699,25700,25703],{},[32,25701,25702],{},"recipients[].token"," 8~255자 device token",[237,25705,25707],{"id":25706},"_103-어댑터-신규-파일","10.3 어댑터 (신규 파일)",[81,25709,25710,25730,25754],{},[84,25711,25712,25717,25718,25721,25722,25725,25726,25729],{},[21,25713,25714],{},[32,25715,25716],{},"src\u002Fadapters\u002Fnhn\u002Femail.ts"," — NHN ",[32,25719,25720],{},"\u002Femail\u002Fv2.1\u002FappKeys\u002F{appKey}\u002Fsender\u002Fmail"," POST. ",[32,25723,25724],{},"receiverList[].receiveType='MRT0'","(TO). mock 시 ",[32,25727,25728],{},"mock-email-\u003Cuuid>"," requestId.",[84,25731,25732,25737,25738,25741,25742,25745,25746,25749,25750,25753],{},[21,25733,25734],{},[32,25735,25736],{},"src\u002Fadapters\u002Fnhn\u002Fkakao.ts"," — 알림톡 ",[32,25739,25740],{},"\u002Falimtalk\u002Fv2.3\u002F..."," + 친구톡 ",[32,25743,25744],{},"\u002Ffriendtalk\u002Fv2.0\u002F..."," 분기. ",[32,25747,25748],{},"senderKey"," 는 ",[32,25751,25752],{},"TB_KAKAO_SENDER_PROFILE.send_key_enc"," (평문 가정 — envelope 복호화는 TODO).",[84,25755,25756,25717,25761,25721,25764,25767],{},[21,25757,25758],{},[32,25759,25760],{},"src\u002Fadapters\u002Fnhn\u002Fpush.ts",[32,25762,25763],{},"\u002Fpush\u002Fv2.5\u002Fappkeys\u002F{appKey}\u002Fmessages",[32,25765,25766],{},"target.type='TOKEN'",". NHN Push는 messageId 1개만 반환 → 개별 토큰 결과는 webhook(후속)으로 갱신.",[18,25769,3830,25770,25773,25774,25777,25778,25781],{},[32,25771,25772],{},"src\u002Fadapters\u002Fnhn\u002Ftypes.ts"," 의 ",[32,25775,25776],{},"NormalizedSendResult.perRecipient"," 필드 ",[32,25779,25780],{},"phone → address"," 로 리네임 → 채널 무관(phone\u002Femail\u002Ftoken). SMS 어댑터도 동반 수정.",[237,25783,25785],{"id":25784},"_104-worker-분기","10.4 worker 분기",[18,25787,25788,25790,25791,25794],{},[32,25789,23534],{}," — 조건 ",[32,25792,25793],{},"channel !== 'sms' && !== 'email' && !== 'kakao' && !== 'push'"," 외 skip. channel별 spec 파싱 + 어댑터 호출:",[5251,25796,25799],{"className":25797,"code":25798,"language":5256},[5254],"sms   → senderRefId → TB_SENDER_PHONE.number\nemail → senderRefId → TB_EMAIL_DOMAIN (존재만 확인, verified는 producer 단계에서 검증 완료)\nkakao → senderRefId → TB_KAKAO_SENDER_PROFILE.send_key_enc → senderKey\npush  → senderRefId → TB_PUSH_CERT (존재만 확인)\n",[32,25800,25798],{"__ignoreMap":4208},[18,25802,25803,25804,25807],{},"credential 조회는 ",[32,25805,25806],{},"nhn_credential.channel = dr.channel"," 로 generic 변경(SMS 하드코딩 제거).",[237,25809,25811],{"id":25810},"_105-단가-정의","10.5 단가 정의",[18,25813,25814,25816],{},[32,25815,23414],{}," 확장:",[5251,25818,25820],{"className":7962,"code":25819,"language":7964,"meta":4208,"style":4208},"export const EMAIL_PRICING = 0.65\nexport const KAKAO_PRICING = { alimtalk: 8, friendtalk: 12 } as const\nexport const PUSH_PRICING = 0.5\n",[32,25821,25822,25836,25867],{"__ignoreMap":4208},[7968,25823,25824,25826,25828,25831,25833],{"class":5110,"line":7970},[7968,25825,7974],{"class":7973},[7968,25827,9400],{"class":7973},[7968,25829,25830],{"class":8892}," EMAIL_PRICING",[7968,25832,9210],{"class":7973},[7968,25834,25835],{"class":8892}," 0.65\n",[7968,25837,25838,25840,25842,25845,25847,25850,25852,25855,25858,25861,25864],{"class":5110,"line":4212},[7968,25839,7974],{"class":7973},[7968,25841,9400],{"class":7973},[7968,25843,25844],{"class":8892}," KAKAO_PRICING",[7968,25846,9210],{"class":7973},[7968,25848,25849],{"class":7984}," { alimtalk: ",[7968,25851,23932],{"class":8892},[7968,25853,25854],{"class":7984},", friendtalk: ",[7968,25856,25857],{"class":8892},"12",[7968,25859,25860],{"class":7984}," } ",[7968,25862,25863],{"class":7973},"as",[7968,25865,25866],{"class":7973}," const\n",[7968,25868,25869,25871,25873,25876,25878],{"class":5110,"line":4209},[7968,25870,7974],{"class":7973},[7968,25872,9400],{"class":7973},[7968,25874,25875],{"class":8892}," PUSH_PRICING",[7968,25877,9210],{"class":7973},[7968,25879,25880],{"class":8892}," 0.5\n",[18,25882,25883,25884,11686,25887,25890,25891,25894],{},"프론트엔드 ",[32,25885,25886],{},"app\u002Ftypes\u002Fchannel.ts",[32,25888,25889],{},"*_META.pricePerUnit"," 와 동기. 실제 운영 시 ",[32,25892,25893],{},"TB_PRICING"," 테이블로 이전 예정.",[237,25896,25898],{"id":25897},"_106-openapi","10.6 OpenAPI",[18,25900,25901,89,25903,4420,25906,4420,25909,25912,25913,4420,25915,4420,25917,25919,25920,25922],{},[32,25902,22139],{},[32,25904,25905],{},"SendEmailRequest",[32,25907,25908],{},"SendKakaoRequest",[32,25910,25911],{},"SendPushRequest"," 스키마 + ",[32,25914,23567],{},[32,25916,12298],{},[32,25918,17578],{}," 경로 추가. 응답은 SMS와 동일한 ",[32,25921,23466],{}," 재사용.",[76,25924,25926],{"id":25925},"_11-malgn-noti-api-프로덕션-배포-7","11. malgn-noti-api 프로덕션 배포 #7",[18,25928,25929],{},"§10 변경을 라이브 반영.",[81,25931,25932,25940,25948,25951,25975],{},[84,25933,25934,47,25936,25939],{},[21,25935,25441],{},[32,25937,25938],{},"12dae362-2900-42ca-9a2e-e6dfb9c60091",". 번들 2508 KiB \u002F gzip 579. Worker Startup 80 ms.",[84,25941,25942,25943,25945,25946,5606],{},"명령: ",[32,25944,18672],{}," (자동 ",[32,25947,11261],{},[84,25949,25950],{},"바인딩: DISPATCH_QUEUE · HYPERDRIVE(a2ba4efe...) · APP_ENV=production · NHN_MOCK=1(default).",[84,25952,25953,25954],{},"검증 3건:\n",[81,25955,25956,25963,25970],{},[84,25957,25958,22005,25960],{},[32,25959,11248],{},[32,25961,25962],{},"{\"ok\":true,\"env\":\"production\",\"time\":\"2026-05-27T04:42:50.327Z\"}",[84,25964,25965,22005,25967],{},[32,25966,20116],{},[32,25968,25969],{},"{\"ok\":true,\"mysql_version\":\"8.0.42\",...}",[84,25971,25972,25974],{},[32,25973,16900],{}," → 200 (Scalar UI 페이지 응답)",[84,25976,25977],{},"큐 e2e + 외부 NHN 자격증명 연결은 별도 작업(미수행).",[73,25979],{},[11,25981,25983],{"id":25982},"트랙-c-malgn-noti-사용자단-추가-작업","트랙 C — malgn-noti 사용자단 추가 작업",[15,25985,25986],{},[18,25987,25988,25989,25992],{},"비고: 본 트랙의 §12·§13은 같은 날(2026-05-27) 작업이지만 세션 도중 시스템 날짜 알림 혼선으로 일부 세부 섹션이 ",[32,25990,25991],{},"history.20260521.md §22~§25","로 먼저 기록됨. 거기에는 화면별 상세 + 동시 작업 격리 절차가 남아 있고, 본 파일에는 요약·배포·커밋만 둠.",[76,25994,25996],{"id":25995},"_12-사용자단-추가-랜딩페이지-만들기문의-경로-이동나의-페이지충전-배포-444546","12. 사용자단 추가 — 랜딩페이지 만들기·문의 경로 이동·나의 페이지·충전 (배포 #44·#45·#46)",[81,25998,25999,26023,26051],{},[84,26000,26001,6685,26004,26006,26007,26009,26010,4536,26012,26014,26015,26017,26018,26020,26021,4703],{},[21,26002,26003],{},"#44 — 메시지 관리 \u002F 랜딩페이지 만들기",[26,26005,18349],{"href":18357},"): GNB 메시지 관리 메뉴 ",[32,26008,18225],{}," 위에 신규. C 테이블 스타일 목록(공개여부 필터·이름 검색·선택 복사\u002F삭제·",[32,26011,7244],{},[32,26013,18364],{},") + 기본형\u002F확장형 등록·수정 폼 뷰 전환(공개 여부 토글·랜딩페이지명·URL·메인 타이틀·확장형 비주얼 이미지·콘텐츠 영역·확장형 CTA 버튼) + ",[32,26016,18384],{},"(LIVELY SHOP 빅세일 샘플 렌더, width 960·min-height 74vh) + ",[32,26019,18390],{},"(URL 복사 완료). alias ",[32,26022,18404],{},[84,26024,26025,26030,26031,26033,26034,26036,26037,4536,26039,4536,26041,11686,26043,26045,26046,26048,26049,4703],{},[21,26026,26027,26028,16883],{},"#45 — 문의하기 페이지 ",[32,26029,16882],{},": 문의 폼·완료 페이지를 ",[32,26032,18452],{}," 하위로 이동, 중복되던 별도 문의 목록 페이지 삭제(나의 문의 목록은 ",[32,26035,18439],{},"에 별도 작업). ",[26,26038,11316],{"href":17197},[26,26040,13094],{"href":18466},[26,26042,17867],{"href":17978},[32,26044,18473],{}," 링크를 새 경로로 갱신. 동시 진행 중인 '나의 페이지'·충전 작업분이 working tree에 섞여 있어 임시 git worktree(",[32,26047,18482],{}," 체크아웃)에서 문의 변경만 격리 빌드 후 배포. alias ",[32,26050,18502],{},[84,26052,26053,26056,26057,18526,26059,26061,26062,6251,26065,4536,26067,4536,26069,4536,26071,4536,26073,26075,26076,26078,26079,26081,26082,4703],{},[21,26054,26055],{},"#46 — 나의 페이지 섹션 + 크레딧 충전 플로우",": 계정 관리를 ",[32,26058,18522],{},[32,26060,5836],{},"(공통 셸 + 좌측 메뉴 9종) + 라우트 9개(",[32,26063,26064],{},"\u002Faccount\u002F{settings,cards,password,security,multi,contract,credit,billing,inquiries}",[32,26066,18572],{},[32,26068,18578],{},[32,26070,18591],{},[32,26072,18595],{},[32,26074,18599],{},". 크레딧 충전은 ",[26,26077,18609],{"href":18608}," 시안 기반 재구성(충전 금액 선택·결제 카드 등록·결제 및 환불안내·동의) + 진행 컨펌 모달 + ",[26,26080,18621],{"href":18620}," 완료 화면. alias ",[32,26083,18642],{},[76,26085,26087],{"id":26086},"_13-malgn-noti-api-루트-doc-리다이렉트","13. malgn-noti-api 루트(\u002F) → \u002Fdoc 리다이렉트",[81,26089,26090,26100,26117],{},[84,26091,26092,18663,26094,26096,26097,26099],{},[26,26093,18662],{"href":18661},[32,26095,18666],{}," 한 줄로 교체 — ",[32,26098,18675],{}," 접속이 곧장 Scalar API 문서로 302 이동.",[84,26101,26102,26103,4361,26106,26109,26110,18695,26112,18699,26114,26116],{},"동시 진행 중인 API 작업분(NHN webhook·send·schema·dispatch worker·",[32,26104,26105],{},"flow-definitions",[32,26107,26108],{},"export-jobs"," 신규 라우트)이 working tree에 섞여 있어 — 배포는 working tree 기준이라 함께 라이브에 올라갔으나(typecheck 통과·",[32,26111,11248],{},[32,26113,18698],{},[21,26115,18702],{}," 격리 기록.",[84,26118,26119,26120,5439,26122,26124,26125,4473,26128,26130,26131,26133],{},"Version ",[32,26121,18686],{},[32,26123,22371],{}," → 302 ",[32,26126,26127],{},"Location: \u002Fdoc",[32,26129,22131],{}," → 200 (Scalar UI), ",[32,26132,22384],{}," → 200 production 확인.",[76,26135,26137],{"id":26136},"_14-malgn-noti-api-13-wip-정식-커밋-rcs-채널-export-잡-flow-정의-배포-8","14. malgn-noti-api §13 WIP 정식 커밋 — RCS 채널 + Export 잡 + Flow 정의 + 배포 #8",[18,26139,26140,26141,26143],{},"§13 시점에 working tree에 라이브로 올라가 있었으나 커밋 격리로 인해 ",[32,26142,12995],{},"과 어긋난 채 남아 있던 3 도메인 슬라이스를 단일 배치로 정리 — 코드 + 신규 마이그레이션 SQL + Workers 재배포까지 일관화.",[237,26145,26147],{"id":26146},"_141-rcs-채널-5채널째-smsemailkakaopush-잇는","14.1 RCS 채널 (5채널째 — sms·email·kakao·push 잇는)",[81,26149,26150,26173,26190,26199,26211,26216],{},[84,26151,26152,26155,26156,26159,26160,26162,26163,26166,26167,25249,26169,26172],{},[32,26153,26154],{},"src\u002Fadapters\u002Fnhn\u002Frcs.ts"," 신규 — NHN ",[32,26157,26158],{},"\u002Frcs-biz\u002Fv2.0"," 4타입(sms\u002Flms\u002Fmms\u002Ftemplate) 어댑터. ",[32,26161,25204],{}," 또는 ",[32,26164,26165],{},"brandId"," 미설정 시 mock 응답. 실 모드는 ",[32,26168,25120],{},[32,26170,26171],{},"chatbotId"," 발신.",[84,26174,26175,26177,26178,4536,26181,4536,26184,4536,26187,26189],{},[32,26176,25351],{}," — RCS 콜백 파서 추가(",[32,26179,26180],{},"nhnRequestId",[32,26182,26183],{},"recipientNo",[32,26185,26186],{},"resultCode",[32,26188,25309],{}," 천이).",[84,26191,26192,26194,26195,26198],{},[32,26193,23425],{}," (+227) — RCS producer. 발신 RCS 브랜드(",[32,26196,26197],{},"TB_RCS_BRAND",") 검증 + 옵트아웃 필터 + 크레딧 hold + 트랜잭션 적재(기존 SMS\u002FEmail\u002FKakao\u002FPush 패턴 generic 재사용).",[84,26200,26201,89,26203,26206,26207,26210],{},[32,26202,25359],{},[32,26204,26205],{},"POST \u002Fwebhooks\u002Fnhn\u002Frcs"," (기존 SMS 핸들러와 동일 HMAC-SHA256 검증 + ",[32,26208,26209],{},"dedup_key"," 멱등).",[84,26212,26213,26215],{},[32,26214,23534],{}," — Consumer에 RCS 채널 분기 추가.",[84,26217,26218,89,26220,26223,26224,26226],{},[32,26219,23414],{},[32,26221,26222],{},"RCS_PRICING = { sms: 12, lms: 40, mms: 120, template: 50 }"," (frontend ",[32,26225,25886],{}," RCS_META 참조).",[237,26228,26230],{"id":26229},"_142-export-잡-비동기-다운로드","14.2 Export 잡 (비동기 다운로드)",[81,26232,26233,26240,26262,26272],{},[84,26234,26235,26236,26239],{},"시안의 \"",[21,26237,26238],{},"다운로드 요청 PU + 목록 PU","\" 패턴 + 90일 윈도우 우회 경로.",[84,26241,26242,89,26244,26246,26247,26250,26251,26254,26255,26258,26259,26261],{},[32,26243,21654],{},[32,26245,24543],{}," 비파티션. ",[32,26248,26249],{},"resource_type"," 7종(history_sms\u002Femail\u002Fkakao\u002Fpush\u002Frcs + contacts + credit_ledger) · ",[32,26252,26253],{},"job_state"," 5단계(pending → running → ready \u002F failed \u002F expired) · ",[32,26256,26257],{},"r2_key","(R2 결과 위치) · ",[32,26260,25690],{},"(등록 +30일).",[84,26263,26264,26267,26268,26271],{},[32,26265,26266],{},"src\u002Froutes\u002Fexport-jobs.ts"," 신규 — POST 등록 \u002F GET 목록(커서·필터) \u002F GET 단건(ready면 ",[32,26269,26270],{},"downloadUrl"," placeholder 노출) \u002F DELETE soft.",[84,26273,26274],{},"처리 worker + R2 presigned URL 발급은 후속.",[237,26276,26278],{"id":26277},"_143-flow-정의-복합-발송-그래프","14.3 Flow 정의 (복합 발송 그래프)",[81,26280,26281,26297,26310],{},[84,26282,26283,89,26285,26288,26289,26292,26293,26296],{},[32,26284,21654],{},[32,26286,26287],{},"TB_FLOW_DEFINITION","(nodes JSON) + ",[32,26290,26291],{},"TB_FLOW_RUN","(1회 실행 인스턴스) + ",[32,26294,26295],{},"TB_FLOW_STEP_RUN","(노드 단위). 3 테이블 모두 비파티션 + 인덱스.",[84,26298,26299,26302,26303,26306,26307,5606],{},[32,26300,26301],{},"src\u002Froutes\u002Fflow-definitions.ts"," 신규 — Zod superRefine로 노드 검증(order 0부터 연속 + 첫 노드 ",[32,26304,26305],{},"condition='always'"," 강제 + 채널 5종 enum + ",[32,26308,26309],{},"delayMinutes ≤ 7일",[84,26311,26312,26313,26316,26317,26319],{},"실행 엔진(",[32,26314,26315],{},"POST \u002Fsend\u002Fflow",")·",[32,26318,26295],{}," 천이는 후속.",[237,26321,26323],{"id":26322},"_144-신규-마이그레이션","14.4 신규 마이그레이션",[81,26325,26326,26332],{},[84,26327,26328,26331],{},[32,26329,26330],{},"src\u002Fdb\u002Fmigrations\u002F0002_export_flow.sql"," 신규 — TB_EXPORT_JOB + TB_FLOW_DEFINITION + TB_FLOW_RUN + TB_FLOW_STEP_RUN (인덱스 4종 포함).",[84,26333,26334,89,26337,26339,26340,26342,26343,26345,26346,4536,26348,26350,26351,26354],{},[21,26335,26336],{},"DDL 적용 보류",[32,26338,20120],{},"가 Cloudflare API edge-preview 호출에서 실패(1105 잔류). 1105 회복 후 ",[32,26341,21386],{}," 띄워 ",[32,26344,21708],{},"에 POST 예정. 그동안 ",[32,26347,23355],{},[32,26349,24552],{},"는 라이브 5xx(table not found) 가능 — ",[21,26352,26353],{},"프런트 호출처 0개","로 무영향.",[237,26356,26358],{"id":26357},"_145-openapi-4지점-갱신","14.5 OpenAPI 4지점 갱신",[81,26360,26361],{},[84,26362,26363,26365,26366,26368],{},[32,26364,22139],{}," (+230) — RCS 발송·Export 잡 CRUD·Flow 정의 CRUD 문서화. Scalar UI(",[32,26367,16900],{},")에서 즉시 확인 가능.",[237,26370,26372],{"id":26371},"_146-배포-8-검증","14.6 배포 #8 + 검증",[81,26374,26375,26385,26392],{},[84,26376,26377,26379,26380,26382,26383,4703],{},[32,26378,11166],{}," OAuth 토큰 재인증(",[26,26381,11171],{"href":11170},") 선행 후 ",[32,26384,18672],{},[84,26386,26387,26388,26391],{},"Workers Version ",[32,26389,26390],{},"95f9f894-4d6c-419d-9cec-8bc7f6c37999",". Total Upload 2549.53 KiB \u002F gzip 583.70 KiB, Startup 78 ms.",[84,26393,13284,26394,26396,26397,26399,26400,4536,26402,26404],{},[32,26395,11248],{}," 200(env=production), ",[32,26398,20116],{}," 200(mysql 8.0.42), ",[32,26401,23355],{},[32,26403,24552],{}," 모두 401(auth 가드 작동 — DDL 부재가 트리거되지 않음).",[237,26406,26408],{"id":26407},"_147-정리-working-tree-main-동기","14.7 정리 — working tree ↔ main 동기",[81,26410,26411],{},[84,26412,26413,26414,26417,26418,5606],{},"단일 배치 커밋 ",[32,26415,26416],{},"malgn-noti-api: 1e7bd61 feat: RCS 채널 + Export 잡 + Flow 정의 — 3 도메인 슬라이스"," (12 files, +1228 −16). origin\u002Fmain 푸시 완료(",[32,26419,26420],{},"677dffa..1e7bd61",[76,26422,10916],{"id":26423},"산출물-1",[237,26425,26427],{"id":26426},"트랙-a-admin","트랙 A (admin)",[81,26429,26430,26454,26458,26462],{},[84,26431,24934,26432,24937,26434,4420,26436,4420,26438,4420,26440,4420,26442,4420,26444,4420,26446,4420,26448,4420,26450,4420,26452,24959],{},[32,26433,24609],{},[32,26435,10928],{},[32,26437,4405],{},[32,26439,7890],{},[32,26441,20821],{},[32,26443,4392],{},[32,26445,10955],{},[32,26447,4352],{},[32,26449,24750],{},[32,26451,24956],{},[32,26453,24832],{},[84,26455,24962,26456,24965],{},[32,26457,11224],{},[84,26459,24968,26460,24971],{},[32,26461,11230],{},[84,26463,15276,26464,24915,26467,5606],{},[32,26465,26466],{},"malgn-noti-admin: 6cbf238 Nuxt 3 + Nuxt UI v3 부트스트랩 + LNB·TopBar 셸 레이아웃",[32,26468,20821],{},[237,26470,26472],{"id":26471},"트랙-b-api","트랙 B (api)",[81,26474,26475,26483,26494,26509,26518,26532],{},[84,26476,26477,26480,26481,14421],{},[32,26478,26479],{},"malgn-noti-api: 020307f fix(idempotency): TB_IDEMPOTENCY + INSERT-then-conflict 패턴 — 멱등 버그 해결"," (4 files, +151 −74). ",[32,26482,23837],{},[84,26484,26485,26488,26489,4536,26492,14421],{},[32,26486,26487],{},"malgn-noti-api: 5e1ac72 feat(send): NHN SMS 어댑터 + Cloudflare Queues + consumer worker (mock 모드)"," (8 files, +439 −5). ",[32,26490,26491],{},"src\u002Fadapters\u002Fnhn\u002F{types,sms}.ts",[32,26493,23534],{},[84,26495,26496,26499,26500,26502,26503,4420,26505,4420,26507,14421],{},[32,26497,26498],{},"malgn-noti-api: 0d28173 feat(webhooks): POST \u002Fwebhooks\u002Fnhn\u002Fsms — NHN 발송 결과 콜백 수신"," (6 files). ",[32,26501,21654],{}," (dispatchEvent) · ",[32,26504,25342],{},[32,26506,25351],{},[32,26508,25359],{},[84,26510,26511,26514,26515,14421],{},[32,26512,26513],{},"malgn-noti-api: 06cf052 feat(send): Email · Kakao · Push 채널 추가 — 3채널 producer + adapter + worker"," (9 files, +1211 −50). ",[32,26516,26517],{},"src\u002Fadapters\u002Fnhn\u002F{email,kakao,push}.ts",[84,26519,26520,26521,25072,26523,26525,26526,26528,26529,26531],{},"Cloudflare Queue ",[32,26522,24037],{},[32,26524,25075],{},") + Workers Version ",[32,26527,24016],{}," (배포 #6) → ",[32,26530,25938],{}," (배포 #7).",[84,26533,26534,26536],{},[32,26535,24983],{}," 에 트랙 B 상세 기록.",[237,26538,26540],{"id":26539},"트랙-c-사용자단-추가","트랙 C (사용자단 추가)",[81,26542,26543,26551,26559,26567,26574],{},[84,26544,26545,26548,26549,4343],{},[32,26546,26547],{},"malgn-noti: 265395a"," 랜딩페이지 만들기 — 목록·등록\u002F수정 폼·미리보기 신규 구성 (배포 #44, alias ",[32,26550,18404],{},[84,26552,26553,26556,26557,4343],{},[32,26554,26555],{},"malgn-noti: a021e2b"," 문의하기 페이지를 \u002Faccount\u002Finquiry 경로로 이동 (배포 #45, alias ",[32,26558,18502],{},[84,26560,26561,26564,26565,4343],{},[32,26562,26563],{},"malgn-noti: 83c4c37"," 나의 페이지 섹션 + 크레딧 충전 플로우 신규 구성 (배포 #46, alias ",[32,26566,18642],{},[84,26568,26569,26571,26572,4343],{},[32,26570,19097],{}," 루트(\u002F) 요청을 API 문서(\u002Fdoc)로 302 리다이렉트 (Workers Version ",[32,26573,18686],{},[84,26575,26576,26577,26579],{},"화면·격리 절차 상세는 ",[32,26578,25991],{}," 에 기록(세션 도중 시스템 날짜 알림 혼선으로 1차 작성된 위치).",[237,26581,26583],{"id":26582},"트랙-e-api-13-wip-정식-커밋-rcsexportflow","트랙 E (api §13 WIP 정식 커밋 — RCS·Export·Flow)",[81,26585,26586,26621,26642],{},[84,26587,26588,26590,26591,4420,26593,4420,26596,26598,26599,26601,26602,26604,26605,26607,26608,6100,26610,7314,26612,4420,26614,4420,26616,4420,26618,26620],{},[32,26589,26416],{}," (12 files, +1228 −16). 신규: ",[32,26592,26154],{},[32,26594,26595],{},"src\u002Froutes\u002F{export-jobs,flow-definitions}.ts",[32,26597,26330],{},". 수정: ",[32,26600,21654],{},"(4 테이블 추가) · ",[32,26603,22139],{},"(+230) · ",[32,26606,23425],{},"(+227 RCS producer) · ",[32,26609,23414],{},[32,26611,24539],{},[32,26613,25351],{},[32,26615,25359],{},[32,26617,23534],{},[32,26619,11243],{},"(라우트 마운트).",[84,26622,26387,26623,26625,26626,26628,26629,5439,26631,4536,26633,4536,26635,4536,26638,26641],{},[32,26624,26390],{}," (배포 #8). ",[32,26627,11166],{}," 재인증 후 ",[32,26630,18672],{},[32,26632,11248],{},[32,26634,20116],{},[32,26636,26637],{},"\u002Fexport-jobs(401)",[32,26639,26640],{},"\u002Fflow-definitions(401)"," 검증.",[84,26643,26644,47,26647,26649,26650,26652,26653,26655],{},[21,26645,26646],{},"DDL 보류",[32,26648,24556],{}," 적용은 ",[32,26651,20120],{},"가 Cloudflare 1105로 막혀 있어 1105 회복 후 ",[32,26654,21708],{},"로 적용 예정.",[76,26657,12598],{"id":13153},[237,26659,26427],{"id":26660},"트랙-a-admin-1",[81,26662,26663,26685,26691,26707,26716],{},[84,26664,26665,26666,4473,26669,4473,26672,4473,26675,4473,26678,4473,26681,26684],{},"셸만 구성 — 각 라우트(",[32,26667,26668],{},"\u002Fcustomer",[32,26670,26671],{},"\u002Fsend",[32,26673,26674],{},"\u002Ftemplate",[32,26676,26677],{},"\u002Freport",[32,26679,26680],{},"\u002Fbilling",[32,26682,26683],{},"\u002Fops"," …)의 페이지\u002F컴포넌트는 미구현. LNB 메뉴 항목은 시각 동작만(클릭 시 라우트 이동 없음, 액티브 키만 변경).",[84,26686,26687,26690],{},[32,26688,26689],{},"MemoPanel","(우측 360px sticky)·고객사 상세의 InfoCard·계정 테이블·BarChart·ActivityList·권한 변경 모달 — 핸드오프 정본에 있으나 셸 범위 밖.",[84,26692,26693,26695,26696,26699,26700,51,26703,26706],{},[32,26694,24615],{},"의 primary ",[32,26697,26698],{},"#6366f1","(indigo)과 부트스트랩 정본 ",[32,26701,26702],{},"blue(#3b82f6)",[21,26704,26705],{},"불일치"," — 핸드오프를 우선해 구현, DESIGN-ADMIN.md 정합화는 후속 작업.",[84,26708,26709,26711,26712,26715],{},[32,26710,10815],{},"에 사용자단 §7.1 유사한 ",[21,26713,26714],{},"배포·Git·작업 이력 운영 컨벤션"," 미수록 — 후속 작성 필요(현재는 사용자단 컨벤션을 준용해 진행).",[84,26717,26718,26719,26722],{},"인증 미들웨어·RBAC·",[32,26720,26721],{},"Cmd+K"," 명령 팔레트·다크 모드 — 모두 셸 이후 별도 작업.",[237,26724,26472],{"id":26725},"트랙-b-api-1",[81,26727,26728,26737,26750,26763,26773,26776,26783,26786],{},[84,26729,26730,26731,26733,26734,26736],{},"큐 e2e 검증 — Cloudflare 1105 회복 후 ",[32,26732,11083],{}," + 시드 + PROD URL 발송 → ",[32,26735,20261],{}," 천이 추적.",[84,26738,26739,26741,26742,26744,26745,4536,26747,26749],{},[32,26740,24556],{}," DDL 적용 — 1105 회복 후 ",[32,26743,21708],{},"로 4 테이블 생성. 그 전까지 ",[32,26746,23355],{},[32,26748,24552],{}," 호출 시 5xx(table not found).",[84,26751,26752,26753,4420,26756,4420,26759,26762],{},"채널별 webhook — ",[32,26754,26755],{},"\u002Fwebhooks\u002Fnhn\u002Femail",[32,26757,26758],{},"\u002Fwebhooks\u002Fnhn\u002Fkakao",[32,26760,26761],{},"\u002Fwebhooks\u002Fnhn\u002Fpush"," (현재는 SMS·RCS만). Push는 별도 결과 조회 API 필요.",[84,26764,26765,26766,4339,26768,4361,26770,26772],{},"Flow 실행 엔진 — ",[32,26767,26315],{},[32,26769,26291],{},[32,26771,26295],{}," 천이 + 폴백 정책(예: 알림톡→친구톡→LMS) 실행기.",[84,26774,26775],{},"Export 잡 처리 worker — Cloudflare Queues 컨슈머 + 채널별 이력 조회 → R2 업로드 → presigned URL 발급.",[84,26777,26778,26779,26782],{},"실 NHN 자격증명 등록 + ",[32,26780,26781],{},"NHN_MOCK"," secret 삭제 → real 모드 전환 (채널별 appKey).",[84,26784,26785],{},"Kakao senderKey · pushCert credential의 envelope 복호화 (현재 평문 가정).",[84,26787,26788],{},"트랜잭션 rollback 시 idempotency cleanup race 추가 보강.",[8560,26790,26791],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":4208,"searchDepth":4209,"depth":4209,"links":26793},[26794,26795,26796,26797,26798,26799,26800,26801,26803,26805,26811,26813,26822,26823,26824,26825,26834,26840],{"id":10635,"depth":4212,"text":10636},{"id":24568,"depth":4212,"text":24569},{"id":24631,"depth":4212,"text":24632},{"id":24742,"depth":4212,"text":24743},{"id":24836,"depth":4212,"text":24837},{"id":24884,"depth":4212,"text":24885},{"id":10916,"depth":4212,"text":10916},{"id":24987,"depth":4212,"text":26802},"6. 멱등 버그 해결 (malgn-noti-api 020307f, history.20260526.md §22)",{"id":25052,"depth":4212,"text":26804},"7. NHN SMS 어댑터 + Cloudflare Queues + consumer worker (malgn-noti-api 5e1ac72, history.20260526.md §23.1~23.2)",{"id":25218,"depth":4212,"text":26806,"children":26807},"8. NHN webhook 핸들러 — POST \u002Fwebhooks\u002Fnhn\u002Fsms (malgn-noti-api 0d28173)",[26808,26809,26810],{"id":25237,"depth":4209,"text":25238},{"id":25323,"depth":4209,"text":25324},{"id":25387,"depth":4209,"text":25388},{"id":25427,"depth":4212,"text":26812},"9. malgn-noti-api 프로덕션 배포 #6 (history.20260526.md §23.1~23.3)",{"id":25489,"depth":4212,"text":26814,"children":26815},"10. Email · Kakao · Push 채널 추가 (malgn-noti-api 06cf052)",[26816,26817,26818,26819,26820,26821],{"id":25503,"depth":4209,"text":25504},{"id":25585,"depth":4209,"text":25586},{"id":25706,"depth":4209,"text":25707},{"id":25784,"depth":4209,"text":25785},{"id":25810,"depth":4209,"text":25811},{"id":25897,"depth":4209,"text":25898},{"id":25925,"depth":4212,"text":25926},{"id":25995,"depth":4212,"text":25996},{"id":26086,"depth":4212,"text":26087},{"id":26136,"depth":4212,"text":26137,"children":26826},[26827,26828,26829,26830,26831,26832,26833],{"id":26146,"depth":4209,"text":26147},{"id":26229,"depth":4209,"text":26230},{"id":26277,"depth":4209,"text":26278},{"id":26322,"depth":4209,"text":26323},{"id":26357,"depth":4209,"text":26358},{"id":26371,"depth":4209,"text":26372},{"id":26407,"depth":4209,"text":26408},{"id":26423,"depth":4212,"text":10916,"children":26835},[26836,26837,26838,26839],{"id":26426,"depth":4209,"text":26427},{"id":26471,"depth":4209,"text":26472},{"id":26539,"depth":4209,"text":26540},{"id":26582,"depth":4209,"text":26583},{"id":13153,"depth":4212,"text":12598,"children":26841},[26842,26843],{"id":26660,"depth":4209,"text":26427},{"id":26725,"depth":4209,"text":26472},{},"\u002Fhistory\u002Fhistory.20260527",{"title":24457,"description":4208},"history\u002Fhistory.20260527","B3vQNsMbcFOaNb4elNnAXxxARVFcH644NHQVIf0krpc",{"id":26850,"title":26851,"body":26852,"description":4208,"extension":4257,"meta":30080,"navigation":4259,"path":30081,"seo":30082,"stem":30083,"__hash__":30084},"docs\u002Fhistory\u002Fhistory.20260601.md","2026-06-01 — WBS 정본화 — doc\u002FWBS.md 신규 + 사용자단 \u002Fwbs 라이브 카탈로그 (배포 #47)",{"type":8,"value":26853,"toc":30026},[26854,26865,26867,26899,26901,26964,26968,27049,27053,27060,27066,27128,27135,27141,27420,27424,27476,27480,27532,27534,27564,27566,27625,27627,27634,27638,27669,27673,27740,27763,27767,27827,27831,27872,27876,27952,27956,28036,28043,28047,28110,28117,28161,28165,28188,28192,28250,28252,28260,28263,28290,28294,28300,28411,28421,28425,28444,28566,28574,28578,28616,28620,28662,28666,28677,28681,28697,28699,28710,28713,28763,28767,28776,28839,28852,28856,28906,28910,29118,29122,29131,29137,29149,29158,29162,29165,29247,29256,29260,29276,29280,29303,29307,29345,29349,29429,29431,29442,29445,29482,29486,29582,29588,29702,29708,29771,29775,29780,29884,29900,29903,29907,29936,29940,29960,29964,30021,30023],[11,26855,26857,26858,26860,26861,26864],{"id":26856},"_2026-06-01-wbs-정본화-docwbsmd-신규-사용자단-wbs-라이브-카탈로그-배포-47","2026-06-01 — WBS 정본화 — ",[32,26859,42],{}," 신규 + 사용자단 ",[32,26862,26863],{},"\u002Fwbs"," 라이브 카탈로그 (배포 #47)",[76,26866,10636],{"id":10635},[18,26868,26869,26872,26873,26876,26877,26880,26881,26883,26884,26895,26896,5606],{},[32,26870,26871],{},"malgn-helper\u002Fdoc\u002FWBS.md"," 양식 + ",[32,26874,26875],{},"malgn-helper-pms\u002Fpages\u002Fwbs.vue"," Notion soft SaaS 디자인을 차용해 ",[21,26878,26879],{},"맑은 메시징 프로젝트 WBS 정본","을 두 산출물로 정착. ",[32,26882,42],{},"(텍스트 정본 — 진행률 스냅샷·5단계 가중치·Step 1",[20367,26885,26886,26887,26890,26891,26894],{},"5 작업 내역·알려진 한계)와 ",[32,26888,26889],{},"app\u002Fpages\u002Fwbs.vue","(공개 라이브 카탈로그 — Hero stats·단계별 진행률 오버뷰·Stage 상세·그룹별 작업 카드·상태 칩·외부 링크). Step 5(서비스 개발)는 원본 WBS의 채널·도메인 단위 항목(대부분 0%)을 ",[21,26892,26893],{},"2026-06-01까지의 실제 진행","(사용자단 6채널 + 전 도메인 화면 완료 \u002F API 5채널·인증·OpenAPI·Queues·webhook 일부 완료 \u002F 관리자단 셸+기획 \u002F 배포 #1","#8)에 맞춰 5-1 설계·5-2 API·5-3 사용자단·5-4 관리자단·5-5 통합·배포의 5 그룹 58 작업으로 재정렬. Cloudflare Pages 프로덕션 배포 #47 (alias ",[32,26897,26898],{},"0ecc825e.malgn-noti.pages.dev",[76,26900,24569],{"id":24568},[81,26902,26903,26916,26938,26952],{},[84,26904,26905,47,26908,26915],{},[21,26906,26907],{},"MD 양식 정본",[26,26909,26912],{"href":26910,"rel":26911},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-helper\u002Fblob\u002Fmain\u002Fdoc\u002FWBS.md",[30],[32,26913,26914],{},"malgnsoft\u002Fmalgn-helper\u002Fblob\u002Fmain\u002Fdoc\u002FWBS.md"," — Phase 별 진행률 스냅샷 + 단계별 가중치 표 + 단계 상세 표(ID\u002F작업\u002F상태\u002F산출물\u002F비고) + 상태 범례(✅\u002F🟢\u002F⚪\u002F⛔).",[84,26917,26918,47,26920,51,26927,26930,26931,4339,26934,26937],{},[21,26919,10451],{},[26,26921,26924],{"href":26922,"rel":26923},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-helper-pms",[30],[32,26925,26926],{},"malgnsoft\u002Fmalgn-helper-pms",[32,26928,26929],{},"pages\u002Fwbs.vue"," — Notion·Linear·Height 풍 \"Soft SaaS\". ",[32,26932,26933],{},"UContainer max-w-5xl",[32,26935,26936],{},"rounded-xl border border-neutral-200"," 카드 + 단계 이모지(🎯📐🛠️📚🧪🚀) + 가중평균 hero + 단계별 오버뷰 행 + Stage 상세 + 상태 칩(emerald\u002Famber\u002Fneutral\u002Frose) + 진척률 막대 컬러 룰(≥70 emerald \u002F ≥30 amber \u002F >0 neutral-400 \u002F 0 neutral-200).",[84,26939,26940,26943,26944,26947,26948,26951],{},[21,26941,26942],{},"데이터 소스",": 사용자가 제공한 7장 스크린샷(Google Sheets 형식의 원본 WBS — ",[32,26945,26946],{},"맑은메시지(가칭) 프로젝트 작업 내역","). Step 1",[20367,26949,26950],{},"5 전체와 담당자\u002F목표일\u002F완료일\u002F진척율(0","100%) 포함.",[84,26953,26954,47,26957,26960,26961,26963],{},[21,26955,26956],{},"Step 5 재구성 근거",[32,26958,26959],{},"doc\u002Fhistory\u002Fhistory.20260511~20260527.md"," 12 일치 누적 이력 — 사용자단 화면 15 종 ✅ \u002F API 9 종 ✅ + 3 종 🟢 + 4 종 ⚪ \u002F 관리자단 ✅ 셸·기획·디자인 가이드 + ⚪ 페이지 11 종 \u002F 배포 사용자단 #1~#46·관리자단 #1·API #1~#8 + DDL ",[32,26962,24556],{}," ⛔(Cloudflare 1105).",[76,26965,26967],{"id":26966},"_2-결정-사항","2. 결정 사항",[81,26969,26970,26979,26990,26996,27002,27028],{},[84,26971,26972,26975,26976,26978],{},[21,26973,26974],{},"레포 위치",": 사용자단 ",[32,26977,7664],{},". 관리자단·API도 후속 검토 가능하나 1차는 사용자가 가장 자주 보는 사용자단에 두기로 결정(공개·blank 레이아웃).",[84,26980,26981,47,26984,26986,26987,26989],{},[21,26982,26983],{},"접근",[32,26985,26863],{}," 공개. ",[32,26988,8629],{},". GNB·푸터 없는 단독 페이지 — 외부 공유에 그대로 쓸 수 있도록.",[84,26991,26992,26995],{},[21,26993,26994],{},"두 산출물 운영",": MD가 정본, Vue 페이지는 동일 데이터를 살아있는 카탈로그로 노출. 어긋나면 MD 우선. Vue 페이지 내부 데이터는 정적 embed (별도 API 없음 — helper-pms처럼 R2 영속·자동 저장은 과한 인프라).",[84,26997,26998,27001],{},[21,26999,27000],{},"5 단계 가중치",": 1 준비 10% · 2 정책 15% · 3 기획 20% · 4 디자인 10% · 5 개발 45% — 개발 비중이 큰 프로젝트라 Step 5를 45%로 가중. Step 1·2는 합의·문서 위주라 가볍게.",[84,27003,27004,27007,27008,27011,27012,27015,27016,27019,27020,27023,27024,27027],{},[21,27005,27006],{},"Step 5 재구성 방침",": 원본 항목(채널·도메인 단위, 대부분 0%)을 실제 산출물 단위 5 그룹으로 재구성 — ",[32,27009,27010],{},"5-1 설계 및 준비","(7) \u002F ",[32,27013,27014],{},"5-2 API 서버","(16) \u002F ",[32,27017,27018],{},"5-3 사용자단 화면","(15) \u002F ",[32,27021,27022],{},"5-4 관리자단 화면","(13) \u002F ",[32,27025,27026],{},"5-5 통합·배포","(7).",[84,27029,27030,47,27033,27036,27037,27040,27041,27044,27045,27048],{},[21,27031,27032],{},"상태 매핑",[32,27034,27035],{},"done"," ✅ \u002F ",[32,27038,27039],{},"in_progress"," 🟢 \u002F ",[32,27042,27043],{},"pending"," ⚪ \u002F ",[32,27046,27047],{},"blocked"," ⛔. Vue에서는 색칩 + 점 + 진척률 막대 색상으로 동시 표현.",[76,27050,27052],{"id":27051},"_3-코드-변경","3. 코드 변경",[237,27054,27056,27057,27059],{"id":27055},"_31-docwbsmd-텍스트-정본-신규","3.1 ",[32,27058,42],{}," — 텍스트 정본 (신규)",[18,27061,27062,27065],{},[26,27063,42],{"href":27064},"..\u002FWBS"," — 약 220 라인. 구조:",[6674,27067,27068,27077,27081,27087,27093,27099,27105,27116,27122],{},[84,27069,27070,27073,27074,5606],{},[21,27071,27072],{},"진행률 스냅샷 (2026-06-01)"," — 5 단계 × 가중치·진행률·핵심 진행 사항. 가중평균 계산식 명시(",[32,27075,27076],{},"0.10×55 + 0.15×55 + 0.20×35 + 0.10×20 + 0.45×55 ≈ 44.5",[84,27078,27079,4703],{},[21,27080,70],{},[84,27082,27083,27086],{},[21,27084,27085],{},"단계별 가중치"," 표.",[84,27088,27089,27092],{},[21,27090,27091],{},"Step 1 — 프로젝트 준비 (10%)"," — 5 하위 섹션(R&R·사업기획 \u002F 사업준비 \u002F 커뮤니케이션 \u002F 서비스 메타 \u002F 환경 셋팅), 18 작업.",[84,27094,27095,27098],{},[21,27096,27097],{},"Step 2 — 주요 서비스 정책 이슈 정리 (15%)"," — 6 하위 섹션(프로토타입 \u002F 주요 서비스 참조 \u002F 캠페인 \u002F 회원·결제·계약 \u002F 메시지 채널 정책 \u002F 캠페인·주소록·브랜드), 22 작업.",[84,27100,27101,27104],{},[21,27102,27103],{},"Step 3 — 서비스 기획 (20%)"," — 3 하위 섹션(Front \u002F BackOffice 1차 \u002F BackOffice 2차), 22 작업.",[84,27106,27107,27110,27111,4339,27113,27115],{},[21,27108,27109],{},"Step 4 — 디자인 \u002F 퍼블리싱 (10%)"," — 2 작업 + \"현재는 개발 측 ",[32,27112,11891],{},[32,27114,4457],{}," 카탈로그로 대체 운영\" 주석.",[84,27117,27118,27121],{},[21,27119,27120],{},"Step 5 — 서비스 개발 (45%)"," — ⚠️ 재구성 마커 + 5 하위 섹션(설계 및 준비 7 \u002F API 서버 16 \u002F 사용자단 화면 15 \u002F 관리자단 화면 13 \u002F 통합·배포 7), 총 58 작업.",[84,27123,27124,27127],{},[21,27125,27126],{},"알려진 한계 \u002F 다음 단계"," — DDL 보류·백엔드 연동 미·관리자단 페이지 미·NHN real\u002FPG\u002FAI 게이트웨이 미정·시스템 페이지 재작업·Step 4 정식 산출물 미.",[237,27129,27131,27132,27134],{"id":27130},"_32-apppageswbsvue-공개-라이브-카탈로그-신규","3.2 ",[32,27133,26889],{}," — 공개 라이브 카탈로그 (신규)",[18,27136,27137,27140],{},[26,27138,26889],{"href":27139},"..\u002F..\u002Fapp\u002Fpages\u002Fwbs.vue"," — 약 650 라인 (스크립트 280 + 템플릿 130 + 스타일 240).",[81,27142,27143,27153,27328,27359,27368,27385,27395,27409,27415],{},[84,27144,27145,51,27147,5439,27150,27152],{},[21,27146,8587],{},[32,27148,27149],{},"{ layout: 'blank', auth: false }",[32,27151,5744],{},"와 동일한 단독 셸 패턴.",[84,27154,27155,9194,27158],{},[21,27156,27157],{},"데이터 형태",[5251,27159,27161],{"className":7962,"code":27160,"language":7964,"meta":4208,"style":4208},"type Status = 'done' | 'in_progress' | 'pending' | 'blocked'\ninterface Task { id, group?, title, status, owner, note?, targetDate?, completionDate?, href? }\ninterface Stage { id, no, emoji, name, summary, weight, progress, tasks }\nconst STAGES: Stage[] = [\u002F* Step 1~5, 113 tasks *\u002F]\n",[32,27162,27163,27190,27256,27303],{"__ignoreMap":4208},[7968,27164,27165,27167,27170,27172,27175,27177,27180,27182,27185,27187],{"class":5110,"line":7970},[7968,27166,9572],{"class":7973},[7968,27168,27169],{"class":7980}," Status",[7968,27171,9210],{"class":7973},[7968,27173,27174],{"class":7993}," 'done'",[7968,27176,21177],{"class":7973},[7968,27178,27179],{"class":7993}," 'in_progress'",[7968,27181,21177],{"class":7973},[7968,27183,27184],{"class":7993}," 'pending'",[7968,27186,21177],{"class":7973},[7968,27188,27189],{"class":7993}," 'blocked'\n",[7968,27191,27192,27195,27198,27201,27204,27206,27209,27212,27214,27216,27218,27221,27223,27226,27228,27231,27233,27235,27238,27240,27242,27245,27247,27249,27252,27254],{"class":5110,"line":4212},[7968,27193,27194],{"class":7973},"interface",[7968,27196,27197],{"class":7980}," Task",[7968,27199,27200],{"class":7984}," { ",[7968,27202,27203],{"class":8939},"id",[7968,27205,4473],{"class":7984},[7968,27207,27208],{"class":8939},"group",[7968,27210,27211],{"class":7973},"?",[7968,27213,4473],{"class":7984},[7968,27215,7082],{"class":8939},[7968,27217,4473],{"class":7984},[7968,27219,27220],{"class":8939},"status",[7968,27222,4473],{"class":7984},[7968,27224,27225],{"class":8939},"owner",[7968,27227,4473],{"class":7984},[7968,27229,27230],{"class":8939},"note",[7968,27232,27211],{"class":7973},[7968,27234,4473],{"class":7984},[7968,27236,27237],{"class":8939},"targetDate",[7968,27239,27211],{"class":7973},[7968,27241,4473],{"class":7984},[7968,27243,27244],{"class":8939},"completionDate",[7968,27246,27211],{"class":7973},[7968,27248,4473],{"class":7984},[7968,27250,27251],{"class":8939},"href",[7968,27253,27211],{"class":7973},[7968,27255,9317],{"class":7984},[7968,27257,27258,27260,27263,27265,27267,27269,27272,27274,27277,27279,27281,27283,27286,27288,27291,27293,27296,27298,27301],{"class":5110,"line":4209},[7968,27259,27194],{"class":7973},[7968,27261,27262],{"class":7980}," Stage",[7968,27264,27200],{"class":7984},[7968,27266,27203],{"class":8939},[7968,27268,4473],{"class":7984},[7968,27270,27271],{"class":8939},"no",[7968,27273,4473],{"class":7984},[7968,27275,27276],{"class":8939},"emoji",[7968,27278,4473],{"class":7984},[7968,27280,14992],{"class":8939},[7968,27282,4473],{"class":7984},[7968,27284,27285],{"class":8939},"summary",[7968,27287,4473],{"class":7984},[7968,27289,27290],{"class":8939},"weight",[7968,27292,4473],{"class":7984},[7968,27294,27295],{"class":8939},"progress",[7968,27297,4473],{"class":7984},[7968,27299,27300],{"class":8939},"tasks",[7968,27302,9317],{"class":7984},[7968,27304,27305,27307,27310,27312,27314,27317,27319,27322,27325],{"class":5110,"line":4219},[7968,27306,9204],{"class":7973},[7968,27308,27309],{"class":8892}," STAGES",[7968,27311,9148],{"class":7973},[7968,27313,27262],{"class":7980},[7968,27315,27316],{"class":7984},"[] ",[7968,27318,8254],{"class":7973},[7968,27320,27321],{"class":7984}," [",[7968,27323,27324],{"class":8398},"\u002F* Step 1~5, 113 tasks *\u002F",[7968,27326,27327],{"class":7984},"]\n",[84,27329,27330,47,27333,27335,27336,27339,27340,27343,27344,4339,27347,27350,27351,27354,27355,27358],{},[21,27331,27332],{},"헤더",[32,27334,5744],{}," 패턴 차용 — ",[32,27337,27338],{},"position: sticky"," 56px 높이 + ",[32,27341,27342],{},"\u003CAppLogoMark\u002F>"," 로고 + ",[32,27345,27346],{},"wbs-header-divider",[32,27348,27349],{},"WBS"," crumb + ",[32,27352,27353],{},"맑은 메시징 프로젝트 작업 내역"," 타이틀 + 우측 ",[32,27356,27357],{},"doc\u002FWBS.md ↗"," 외부 링크.",[84,27360,27361,89,27364,27367],{},[21,27362,27363],{},"Title row",[32,27365,27366],{},"맑은 메시징"," h1(30px·600·-0.01em) + 부제(서비스 한 줄 설명 + 마지막 현행화 날짜).",[84,27369,27370,27373,27374,27377,27378,27380,27381,27384],{},[21,27371,27372],{},"Hero stats"," (4-col grid) — ",[32,27375,27376],{},"전체 진행률","(가중평균 % + 36px tabular-nums + 너비 = 진행률인 검정 막대) span-2 + ",[32,27379,279],{},"(N\u002F총 작업 수) + ",[32,27382,27383],{},"진행 중","(N).",[84,27386,27387,27390,27391,27394],{},[21,27388,27389],{},"단계별 진행률 오버뷰"," — 카드형 ul, 행 클릭 시 ",[32,27392,27393],{},"scrollToStage"," smooth scroll. 6-col grid(이모지·번호·이름\u002F요약·작업 수·진척률 막대+%·화살표).",[84,27396,27397,27400,27401,27404,27405,27408],{},[21,27398,27399],{},"Stage 상세"," — 단계마다 head(이모지·이름·ID + 비중·진척률) + 작은 진척률 막대 + ",[32,27402,27403],{},"groupedTasks(stage)","로 그룹 카드 분할 렌더링. 각 작업 행은 ",[32,27406,27407],{},"task-id","(JetBrains Mono) + 상태 점 + 제목 + 외부 링크 아이콘 + 메모 + 우측(상태칩·담당자·목표→완료 날짜).",[84,27410,27411,27414],{},[21,27412,27413],{},"반응형"," — 720px 미만에서 오버뷰 행과 작업 행 그리드를 단순화.",[84,27416,27417,27419],{},[21,27418,8153],{}," — 상단 1px border + 브랜드 + 카피.",[237,27421,27423],{"id":27422},"_33-데이터-채우기","3.3 데이터 채우기",[81,27425,27426,27432,27441,27453],{},[84,27427,27428,27431],{},[21,27429,27430],{},"Step 1·2"," — 스크린샷 그대로 옮김.",[84,27433,27434,27437,27438,27440],{},[21,27435,27436],{},"Step 3"," — 스크린샷 그대로 옮김. 운영가이드 메모에 \"(사용자단 ",[32,27439,5744],{}," 라이브 — 컨텐츠 보강 필요)\" 부기.",[84,27442,27443,27446,27447,27449,27450,27452],{},[21,27444,27445],{},"Step 4"," — 스크린샷 그대로 옮기되 메모로 \"(개발 측에서 ",[32,27448,11891],{}," Relay-inspired v1.0 + ",[32,27451,4457],{}," 카탈로그로 대체 운영)\" 부기.",[84,27454,27455,27458,27459],{},[21,27456,27457],{},"Step 5"," — 사용자 요청대로 재구성:\n",[81,27460,27461,27464,27467,27470,27473],{},[84,27462,27463],{},"5-1 설계 및 준비 — 아키텍처·데이터 모델링·DS·사용자단\u002F관리자단 가이드·관리자단 셸·페이지 기획 MD 33종 ✅ 7건.",[84,27465,27466],{},"5-2 API 서버 — Workers 부트스트랩·DB 49 테이블·기초 CRUD 14·OpenAPI 37·인증·발송 5채널·멱등성·NHN 어댑터 5·Queues + Consumer ✅ 9건 \u002F Webhook 핸들러·Export·Flow 🟢 3건 \u002F 캠페인·PG·AI·NHN real ⚪ 4건 = 16건.",[84,27468,27469],{},"5-3 사용자단 화면 — 인증\u002F계정·발송 6채널·이력 5+stats·주소록·발신정보 6·템플릿 5+settings·캠페인·크레딧\u002F결제·문의·나의 페이지·랜딩페이지·공개 랜딩·디자인 가이드 ✅ 13건 \u002F 시스템 페이지 🟢 1 \u002F 백엔드 연동 ⚪ 1 = 15건.",[84,27471,27472],{},"5-4 관리자단 화면 — 셸·기획 MD ✅ 2건 \u002F P0·P1·P2 ⚪ 11건 = 13건.",[84,27474,27475],{},"5-5 통합·배포 — 사용자단 Pages 🟢 \u002F 관리자단 Pages ✅ \u002F API Workers ✅ \u002F DDL ⛔ \u002F NHN real·PG·AI ⚪ = 7건.",[76,27477,27479],{"id":27478},"_4-배포-47-사용자단","4. 배포 #47 (사용자단)",[81,27481,27482,27501,27506,27512],{},[84,27483,27484,27486,27487,20997,27489,27492,27493,27496,27497,27500],{},[32,27485,11195],{}," → Nitro ",[32,27488,13302],{},[32,27490,27491],{},"dist\u002F_worker.js"," 빌드 OK. ",[32,27494,27495],{},"wbs-BWsapYCM.mjs"," 30.3 kB \u002F ",[32,27498,27499],{},"wbs-styles.BOjKIqTn.mjs"," 29.9 kB 청크 생성. Total 3.02 MB(903 kB gzip).",[84,27502,27503,4703],{},[32,27504,27505],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"deploy wbs page\"",[84,27507,27508,27509,4703],{},"alias ",[32,27510,27511],{},"https:\u002F\u002F0ecc825e.malgn-noti.pages.dev",[84,27513,13284,27514,27517,27518,27520,27521,27524,27525,27527,27528,27531],{},[32,27515,27516],{},"GET https:\u002F\u002Fmalgn-noti.pages.dev\u002Fwbs"," → HTTP 200. 그렙으로 ",[32,27519,27353],{}," 헤더·",[32,27522,27523],{},"wbs-header-title"," 2·",[32,27526,27376],{}," 1·",[32,27529,27530],{},"stage-emoji"," 6 출력 확인(5 stages × 본문 + 1 hero\u002Foverview).",[76,27533,10916],{"id":10916},[81,27535,27536,27541,27554],{},[84,27537,27538],{},[32,27539,27540],{},"malgn-noti: WBS — doc\u002FWBS.md 정본 + \u002Fwbs 공개 라이브 카탈로그 (배포 #47)",[84,27542,27543,27544],{},"신규 파일:\n",[81,27545,27546,27550],{},[84,27547,27548],{},[26,27549,42],{"href":27064},[84,27551,27552],{},[26,27553,26889],{"href":27139},[84,27555,13333,27556,27560,27561,4703],{},[26,27557,27558],{"href":27558,"rel":27559},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fwbs",[30]," · alias ",[32,27562,27563],{},"https:\u002F\u002F0ecc825e.malgn-noti.pages.dev\u002Fwbs",[76,27565,12598],{"id":13153},[81,27567,27568,27574,27580,27594,27600,27611],{},[84,27569,27570,27573],{},[21,27571,27572],{},"MD ↔ Vue 동기화는 수동."," 가중치·진척률·작업 상태가 어긋나면 한 곳만 갱신해 두기 쉬움 — 큰 변화가 생기면 두 파일을 같이 수정하는 디스플린 유지.",[84,27575,27576,27579],{},[21,27577,27578],{},"진척률은 추정."," 실제 작업 수 대비 ✅\u002F🟢\u002F⚪\u002F⛔ 비율을 기준으로 추정 — 객관 지표(완료 PR 수·테스트 통과율 등) 도입 시 더 정확히.",[84,27581,27582,51,27587,27590,27591,27593],{},[21,27583,27584,27586],{},[32,27585,26863],{}," 인덱싱 차단 미비.",[32,27588,27589],{},"nuxt.config.ts head","에 전체 ",[32,27592,24677],{}," 설정이 있는지 재확인 필요(검색 노출 방지).",[84,27595,27596,27599],{},[21,27597,27598],{},"외부 자료 링크 일부 placeholder."," 컨설팅팀\u002F디자인팀 산출물(단가표·계약서·디자인 스타일 가이드 등)의 실제 URL이 정해지면 MD·Vue 양쪽에 채워 넣기.",[84,27601,27602,27605,27606,4339,27608,27610],{},[21,27603,27604],{},"Step 4 정식 산출물."," 디자인팀의 정식 디자인 스타일 가이드 + 퍼블리싱 MD는 여전히 미. 개발 측 ",[32,27607,11891],{},[32,27609,4457],{}," 카탈로그로 대체 운영 중.",[84,27612,27613,27616,27617,5069,27619,4339,27621,27624],{},[21,27614,27615],{},"자동화 가능성",": helper-pms처럼 R2 영속 + 자동 저장 형태로 운영하고 싶다면, ",[32,27618,50],{},[32,27620,54],{},[32,27622,27623],{},"PUT \u002Fwbs"," 추가 → 본 페이지를 편집 가능 페이지로 전환. 1차에서는 스코프에서 제외.",[73,27626],{},[11,27628,27630,27631,27633],{"id":27629},"_2-0002_export_flowsql-ddl-라이브-이미-적용-확인-라이브-검증-sql-파일-동기화","§2. ",[32,27632,24556],{}," DDL — 라이브 이미 적용 확인 + 라이브 검증 + SQL 파일 동기화",[76,27635,27637],{"id":27636},"한-줄","한 줄",[18,27639,27640,27642,27643,27645,27646,27649,27650,27653,27654,27656,27657,27660,27661,4536,27663,27665,27666,27668],{},[32,27641,20120],{},"의 1105 잔류로 ",[32,27644,21708],{}," 경로가 막혀 있던 상태에서, Aurora 직결(",[32,27647,27648],{},"noti"," 계정 + SSL REQUIRED)로 들어가 ",[21,27651,27652],{},"4 신규 테이블(TB_EXPORT_JOB \u002F TB_FLOW_DEFINITION \u002F TB_FLOW_RUN \u002F TB_FLOW_STEP_RUN)이 이미 적용돼 있음","을 확인. 컬럼은 우리 ",[32,27655,24556],{},"과 100% 일치, ",[21,27658,27659],{},"인덱스·FK는 라이브 쪽이 더 정교","(FK 6개 + 의미 있는 인덱스명) — 출처는 사전 작업으로 추정. 라이브 워커의 ",[32,27662,23355],{},[32,27664,24552],{}," GET\u002FPOST 4건 모두 200\u002F201 정상 응답으로 e2e 확인. 검증 과정에서 생긴 테스트 데이터(임시 user\u002Fcompany 2건 + export_job\u002Fflow_def 각 1건)를 즉시 cleanup해 빈 상태 복구. ",[32,27667,24556],{},"을 라이브 정본(인덱스·FK 포함)에 맞춰 갱신해 신규 환경에서도 동일하게 적용되도록 동기화.",[76,27670,27672],{"id":27671},"_21-cloudflare-1105-재시도-3회-모두-실패","2.1 Cloudflare 1105 재시도 (3회) → 모두 실패",[101,27674,27675,27690],{},[104,27676,27677],{},[107,27678,27679,27682,27685,27688],{},[110,27680,27681],{},"시도",[110,27683,27684],{},"시각 (UTC)",[110,27686,27687],{},"Ray ID",[110,27689,22362],{},[126,27691,27692,27711,27727],{},[107,27693,27694,27697,27700,27705],{},[131,27695,27696],{},"1차",[131,27698,27699],{},"01:47",[131,27701,27702],{},[32,27703,27704],{},"a04a8ca2dd1125d4",[131,27706,27707,27708],{},"HTTP 503 — ",[32,27709,27710],{},"Error 1105 Temporarily unavailable",[107,27712,27713,27716,27719,27724],{},[131,27714,27715],{},"2차",[131,27717,27718],{},"01:54",[131,27720,27721],{},[32,27722,27723],{},"a04a9753cbaedd38",[131,27725,27726],{},"HTTP 503 — 동일",[107,27728,27729,27732,27735,27737],{},[131,27730,27731],{},"3차",[131,27733,27734],{},"02:24~02:28",[131,27736,322],{},[131,27738,27739],{},"HTTP 503 12회 폴링 모두 (10초 간격)",[81,27741,27742,27760],{},[84,27743,27744,27745,27747,27748,27750,27751,4536,27753,27755,27756,27759],{},"모두 ",[32,27746,20120],{},"가 띄운 임시 edge-preview 워커에서 발생. 라이브 워커(",[32,27749,11267],{},")는 ",[32,27752,11248],{},[32,27754,20116],{}," 200으로 영향 없음 → ",[21,27757,27758],{},"1105는 Cloudflare 측 dev\u002Fpreview 인프라 한정 장애","로 확정.",[84,27761,27762],{},"결정: 한 번만 쓸 카드인 일회용 Aurora SG whitelist 경로로 우회. 운영 정책 갱신(Cloudflare Tunnel·RDS Proxy·bastion 등)은 별도 후속 작업으로 분리.",[76,27764,27766],{"id":27765},"_22-hyperdrive-aurora-라이브-연결-정상성-사전-확인","2.2 Hyperdrive ↔ Aurora 라이브 연결 정상성 사전 확인",[81,27768,27769,27786,27797,27811,27824],{},[84,27770,27771,27774,27775,27778,27779,27781,27782,27785],{},[32,27772,27773],{},"wrangler hyperdrive get a2ba4efe7421464da1d5ff5e620b33a3"," — 설정 정상 (origin: ",[32,27776,27777],{},"malgn-dev-db.cluster-c53h9wjjbjbr.ap-northeast-2.rds.amazonaws.com:3306"," \u002F db ",[32,27780,27648],{}," \u002F user ",[32,27783,27784],{},"admin"," \u002F SSL REQUIRED \u002F connection_limit 60 \u002F 캐싱 활성).",[84,27787,27788,27790,27791,4473,27793,27796],{},[32,27789,20116],{}," 3회 — 모두 200, ",[32,27792,23243],{},[21,27794,27795],{},"cold 512ms → warm 355ms → warm 360ms"," (Hyperdrive 캐시 효과 뚜렷).",[84,27798,27799,27800,4536,27802,4536,27804,27807,27808,27810],{},"보호 라우트 가드 — ",[32,27801,3987],{},[32,27803,21826],{},[32,27805,27806],{},"\u002Fdispatch\u002Frequests"," 모두 401 (DB 단계 진입 전 차단). ",[32,27809,21053],{}," 404 (프로덕션 가드 정상).",[84,27812,27813,27814,27816,27817,27819,27820,27823],{},"실 DB write+read — ",[32,27815,23258],{},"(임시) → JWT 169자 → ",[32,27818,3987],{}," SELECT 2회 → ",[21,27821,27822],{},"249ms 응답"," + 컬럼 정상 매핑.",[84,27825,27826],{},"결론: 1105와 무관하게 라이브 인프라는 한 통으로 살아 있음.",[76,27828,27830],{"id":27829},"_23-aurora-직결-tcp-도달성-mysql-인증","2.3 Aurora 직결 — TCP 도달성 + mysql 인증",[81,27832,27833,27843,27849,27859],{},[84,27834,27835,27836,27838,27839,27842],{},"사용자 제공 — ",[32,27837,27648],{}," 계정 + 패스워드(채팅 외부에 기록 안 함, ",[32,27840,27841],{},"MYSQL_PWD"," 환경변수로만 1회 쉘에서 사용).",[84,27844,27845,27846,4703],{},"내 outbound IP — ",[32,27847,27848],{},"211.119.233.35",[84,27850,27851,27852,6219,27855,27858],{},"TCP probe: ",[32,27853,27854],{},"nc -zv -G 5 malgn-dev-db.cluster-...:3306",[21,27856,27857],{},"즉시 connection succeeded"," — SG 인바운드가 이미 열려 있는 상태(추정: 사전에 noti IP 또는 관련 대역이 화이트리스트됨).",[84,27860,27861,27862,24937,27865,27868,27869,27871],{},"mysql 접속(",[32,27863,27864],{},"--ssl-mode=REQUIRED",[32,27866,27867],{},"noti@%",", DB ",[32,27870,27648],{},", 서버 8.0.42 → ✅.",[76,27873,27875],{"id":27874},"_24-사전-ddl-적용-확인-컬럼-100-일치-인덱스fk-더-풍부","2.4 사전 DDL 적용 확인 — 컬럼 100% 일치, 인덱스·FK 더 풍부",[81,27877,27878,27885,27895,27943],{},[84,27879,27880,27881,27884],{},"전체 TB_ 카운트: ",[21,27882,27883],{},"50"," (= 49 initial + 1 idempotency + 4 신규 − 4 중복? 아님. 0000_initial.sql의 정확 적용본은 45 + 0001 1 + 0002 4 = 50.) — 4 신규가 50 안에 이미 포함돼 있음.",[84,27886,27887,27888,27890,27891,27894],{},"4 신규 테이블 컬럼 — ",[32,27889,24556],{},"(2026-05-31자 초안)과 ",[21,27892,27893],{},"모두 일치",": 데이터 타입·NULL 여부·기본값·자동 타임스탬프 모두 동일.",[84,27896,27897,9194,27900],{},[21,27898,27899],{},"인덱스\u002FFK는 라이브 쪽이 더 정교",[81,27901,27902,27915,27927,27935],{},[84,27903,27904,27906,27907,27910,27911,27914],{},[32,27905,24543],{},": 라이브 ",[32,27908,27909],{},"idx_export_company_state(company_id, job_state, requested_at) + idx_export_user(user_id, requested_at) + FK fk_export_company → TB_COMPANY + FK fk_export_user → TB_USER",". 초안 안은 단일 ",[32,27912,27913],{},"idx_export_company_user(company_id, user_id, requested_at) + idx_export_state(job_state, requested_at)","만 있었음.",[84,27916,27917,27906,27919,27922,27923,27926],{},[32,27918,26287],{},[32,27920,27921],{},"idx_flowdef_company_status(company_id, status, created_at) + FK fk_flowdef_company → TB_COMPANY",". 초안은 ",[32,27924,27925],{},"idx_flow_def_company(company_id, created_at)","만.",[84,27928,27929,27906,27931,27934],{},[32,27930,26291],{},[32,27932,27933],{},"idx_flowrun_company_state(company_id, run_state, started_at) + FK fk_flowrun_company → TB_COMPANY + FK fk_flowrun_def → TB_FLOW_DEFINITION + 보조 키 fk_flowrun_def",". 초안은 인덱스 2개만, FK 없음.",[84,27936,27937,27906,27939,27942],{},[32,27938,26295],{},[32,27940,27941],{},"idx_fsr_run(flow_run_id, node_order) + idx_fsr_dispatch(dispatch_request_id) + FK fk_fsr_run → TB_FLOW_RUN",". 초안은 단일 인덱스만, FK 없음.",[84,27944,27945,27946,27948,27949,4703],{},"4 테이블 모두 행 수 ",[21,27947,5354],{}," → 빈 신규 생성. ",[21,27950,27951],{},"즉, 출처는 사전 작업(SG whitelist + 직결 또는 다른 운영 경로)",[76,27953,27955],{"id":27954},"_25-라이브-워커-e2e-검증-4-호출-모두-통과","2.5 라이브 워커 e2e 검증 (4 호출 모두 통과)",[101,27957,27958,27967],{},[104,27959,27960],{},[107,27961,27962,27965],{},[110,27963,27964],{},"호출",[110,27966,22362],{},[126,27968,27969,27984,28003,28015],{},[107,27970,27971,27977],{},[131,27972,27973,27976],{},[32,27974,27975],{},"GET \u002Fexport-jobs"," (auth)",[131,27978,27979,27980,27983],{},"200 — ",[32,27981,27982],{},"{data:[], nextCursor:null}"," · 449ms",[107,27985,27986,27994],{},[131,27987,27988,51,27991],{},[32,27989,27990],{},"POST \u002Fexport-jobs",[32,27992,27993],{},"{resourceType:\"history_sms\", params:{from,to}}",[131,27995,27996,27997,7505,28000,28002],{},"201 — id=1 \u002F ",[32,27998,27999],{},"jobState:\"pending\"",[32,28001,25690],{}," 등록 +30일 자동 계산 · 306ms",[107,28004,28005,28010],{},[131,28006,28007,27976],{},[32,28008,28009],{},"GET \u002Fflow-definitions",[131,28011,27979,28012,28014],{},[32,28013,27982],{}," · 400ms",[107,28016,28017,28023],{},[131,28018,28019,28022],{},[32,28020,28021],{},"POST \u002Fflow-definitions"," (alimtalk→sms on_fail 5분 폴백)",[131,28024,28025,28026,4536,28028,28031,28032,28035],{},"201 — id=1 \u002F nodes JSON 보존 \u002F ",[32,28027,23129],{},[32,28029,28030],{},"updatedAt"," 자동 \u002F ",[32,28033,28034],{},"deletedAt:null"," · 563ms",[18,28037,28038,28039,28042],{},"→ ",[21,28040,28041],{},"라이브 워커 + 4 신규 라우트 + 라이브 DB","가 한 통으로 정상. CRUD ✅. 처리 worker \u002F 실행 엔진은 여전히 미.",[76,28044,28046],{"id":28045},"_26-테스트-데이터-cleanup","2.6 테스트 데이터 cleanup",[81,28048,28049,28081,28084,28100],{},[84,28050,28051,28052,28054,28055,4536,28058,28061,28062,28065,28066,4536,28069,28061,28072,28074,28075,28074,28077,28080],{},"검증 과정에서 생성: ",[32,28053,20302],{},"(loginid ",[32,28056,28057],{},"hd-check-…",[32,28059,28060],{},"ddl-…",") 2건 \u002F ",[32,28063,28064],{},"TB_COMPANY","(name ",[32,28067,28068],{},"hyperdrive-check-…",[32,28070,28071],{},"ddl-live-check-…",[32,28073,24543],{}," id=1 \u002F ",[32,28076,26287],{},[32,28078,28079],{},"TB_TERMS_AGREEMENT"," 0건(현재 약관 미배포로 자동 생성 없음).",[84,28082,28083],{},"단일 트랜잭션 묶음 없이 순서대로 DELETE — FK 제약을 만족하도록 자식 → 부모 순.",[84,28085,28086,28087,7505,28090,7505,28093,7505,28096,28099],{},"사후 카운트: ",[32,28088,28089],{},"TB_EXPORT_JOB=0",[32,28091,28092],{},"TB_FLOW_DEFINITION=0",[32,28094,28095],{},"leftover_users=0",[32,28097,28098],{},"leftover_companies=0"," ✅.",[84,28101,28102,28103,7505,28106,28109],{},"AUTO_INCREMENT 잔류: ",[32,28104,28105],{},"TB_EXPORT_JOB.AUTO_INCREMENT=2",[32,28107,28108],{},"TB_FLOW_DEFINITION.AUTO_INCREMENT=2"," (정상 — 다음 INSERT는 id=2부터 시작).",[76,28111,28113,28114,28116],{"id":28112},"_27-0002_export_flowsql-라이브-정본-동기화","2.7 ",[32,28115,24556],{}," 라이브 정본 동기화",[81,28118,28119,28155,28158],{},[84,28120,28121,28122,28125,28126,28129,28130,4536,28133,4536,28136,4536,28139,4536,28142,4536,28145,28148,28149,4536,28152,11941],{},"초안 SQL 파일을 라이브 ",[32,28123,28124],{},"SHOW CREATE TABLE"," 결과 기준으로 갱신 — 인덱스명 변경(",[32,28127,28128],{},"idx_export_company_state"," 등) + FK 6개 추가(",[32,28131,28132],{},"fk_export_company",[32,28134,28135],{},"fk_export_user",[32,28137,28138],{},"fk_flowdef_company",[32,28140,28141],{},"fk_flowrun_company",[32,28143,28144],{},"fk_flowrun_def",[32,28146,28147],{},"fk_fsr_run",") + 코멘트 일치(",[32,28150,28151],{},"'history_sms, contacts 등'",[32,28153,28154],{},"'[{order, channel, template_id, condition, delay_minutes}]'",[84,28156,28157],{},"CLAUDE.md §5 \"파티션 테이블 — FK 미사용\" 원칙은 유지 — 이번 4 테이블은 모두 비파티션이라 FK 적용 가능.",[84,28159,28160],{},"파일 헤더에 \"2026-06-01 현행화 — 라이브(Aurora) 정본과 동기화: FK 6개 + 의미 있는 인덱스명\" 명시.",[76,28162,28164],{"id":28163},"_28-산출물","2.8 산출물",[81,28166,28167,28173,28176,28182],{},[84,28168,28169,28172],{},[32,28170,28171],{},"malgn-noti-api: src\u002Fdb\u002Fmigrations\u002F0002_export_flow.sql"," — 라이브 정본 동기화 (1 file, 인덱스명 변경 + FK 6 추가 + 코멘트 일치).",[84,28174,28175],{},"라이브 DB — 변경 없음(이미 적용된 정본 그대로 + cleanup으로 빈 상태 복원).",[84,28177,28178,28179,28181],{},"라이브 Worker — 변경 없음(이미 배포 #8 ",[32,28180,24564],{},"이 4 라우트를 정상 노출 중).",[84,28183,28184,28187],{},[32,28185,28186],{},"malgn-noti: doc\u002FWBS.md"," 갱신 — 5-2-11\u002F12 🟢 (CRUD ✅, 처리 worker \u002F 실행 엔진 미) \u002F 5-5-4 ⛔→✅ (DDL 적용 확인).",[76,28189,28191],{"id":28190},"_29-다음-단계-알려진-한계","2.9 다음 단계 \u002F 알려진 한계",[81,28193,28194,28213,28225,28234],{},[84,28195,28196,89,28199,28201,28202,28205,28206,4361,28209,28212],{},[21,28197,28198],{},"Drizzle schema.ts vs 라이브 인덱스\u002FFK",[32,28200,21654],{},"의 export\u002Fflow 테이블 정의는 인덱스\u002FFK를 선언하지 않음(컬럼만). 런타임 동작에는 영향 없지만, ",[32,28203,28204],{},"drizzle-kit introspect"," 또는 schema에서 명시적으로 ",[32,28207,28208],{},"index()",[32,28210,28211],{},"foreignKey()","를 선언해 정합화하는 게 위생적. 후속.",[84,28214,28215,89,28218,28220,28221,28224],{},[21,28216,28217],{},"운영 절차 갱신",[32,28219,20120],{}," 1105 같은 dev\u002Fpreview 장애가 또 발생할 때를 대비, CLAUDE.md §12 후보 중 ",[21,28222,28223],{},"Cloudflare Tunnel(cloudflared) → Aurora"," 셋업 검토. 별도 작업.",[84,28226,28227,28230,28231,28233],{},[21,28228,28229],{},"SG 정책 재검토"," — 사전 작업에서 어떤 IP 대역이 화이트리스트됐는지 한 번 정리. ",[32,28232,27648],{}," 계정의 권한 범위(CREATE TABLE 가능 여부)도 운영 문서화 필요.",[84,28235,28236,89,28239,28241,28242,28244,28245,4536,28247,28249],{},[21,28237,28238],{},"처리 worker 미",[32,28240,23355],{}," 처리 워커(R2 업로드 + presigned URL) \u002F ",[32,28243,15268],{}," 실행 엔진 + ",[32,28246,26291],{},[32,28248,26295],{}," 천이 — 둘 다 별도 마일스톤.",[73,28251],{},[11,28253,28255,28256,28259],{"id":28254},"_3-schemats-정합화-exportflow-4-테이블-인덱스fk-명시화","§3. ",[32,28257,28258],{},"schema.ts"," 정합화 — export\u002Fflow 4 테이블 인덱스·FK 명시화",[76,28261,27637],{"id":28262},"한-줄-1",[18,28264,28265,28266,28268,28269,28275,28276,4473,28279,28282,28283,28286,28287,28289],{},"§2에서 ",[32,28267,24556],{}," 파일을 라이브 Aurora 정본에 맞춰 동기화했지만, ",[21,28270,28271,28272,28274],{},"Drizzle ORM의 ",[32,28273,21654],{},"는 컬럼만 정의되어 있고 인덱스\u002FFK는 0건"," — 코드↔라이브 drift 8건이 남아 있던 상태. 이를 라이브 정본 기준으로 명시화 (인덱스 6 + FK 6, 총 12 항목). ",[21,28277,28278],{},"컬럼 정의는 일절 변경하지 않음",[21,28280,28281],{},"다른 테이블은 손대지 않음","(서비스 중). 런타임 동작에 영향 0, Worker 재배포 불필요. typecheck 통과 + ",[32,28284,28285],{},"git diff"," 1 file +22 -4 — ",[32,28288,28258],{}," 외 변경 0 확인.",[76,28291,28293],{"id":28292},"_31-작업-범위-4-테이블만","3.1 작업 범위 — 4 테이블만",[18,28295,28296,28297,4703],{},"원칙: \"다른 테이블은 현재 서비스 중이라 함부로 건들면 안 된다\" — export\u002Fflow 4 테이블 ",[21,28298,28299],{},"한정",[101,28301,28302,28315],{},[104,28303,28304],{},[107,28305,28306,28309,28312],{},[110,28307,28308],{},"테이블",[110,28310,28311],{},"추가 인덱스",[110,28313,28314],{},"추가 FK",[126,28316,28317,28345,28364,28388],{},[107,28318,28319,28325,28333],{},[131,28320,28321,28324],{},[32,28322,28323],{},"exportJob"," (TB_EXPORT_JOB)",[131,28326,28327,4339,28330],{},[32,28328,28329],{},"idx_export_company_state(company_id, job_state, requested_at)",[32,28331,28332],{},"idx_export_user(user_id, requested_at)",[131,28334,28335,6219,28337,4420,28340,6219,28342],{},[32,28336,28132],{},[32,28338,28339],{},"company.id",[32,28341,28135],{},[32,28343,28344],{},"user.id",[107,28346,28347,28353,28358],{},[131,28348,28349,28352],{},[32,28350,28351],{},"flowDefinition"," (TB_FLOW_DEFINITION)",[131,28354,28355],{},[32,28356,28357],{},"idx_flowdef_company_status(company_id, status, created_at)",[131,28359,28360,6219,28362],{},[32,28361,28138],{},[32,28363,28339],{},[107,28365,28366,28372,28377],{},[131,28367,28368,28371],{},[32,28369,28370],{},"flowRun"," (TB_FLOW_RUN)",[131,28373,28374],{},[32,28375,28376],{},"idx_flowrun_company_state(company_id, run_state, started_at)",[131,28378,28379,6219,28381,4420,28383,6219,28385],{},[32,28380,28141],{},[32,28382,28339],{},[32,28384,28144],{},[32,28386,28387],{},"flowDefinition.id",[107,28389,28390,28396,28404],{},[131,28391,28392,28395],{},[32,28393,28394],{},"flowStepRun"," (TB_FLOW_STEP_RUN)",[131,28397,28398,4339,28401],{},[32,28399,28400],{},"idx_fsr_run(flow_run_id, node_order)",[32,28402,28403],{},"idx_fsr_dispatch(dispatch_request_id)",[131,28405,28406,6219,28408],{},[32,28407,28147],{},[32,28409,28410],{},"flowRun.id",[18,28412,28413,28414,28417,28418,28420],{},"총 ",[21,28415,28416],{},"인덱스 6 + FK 6 = 12 항목",". 모두 라이브 ",[32,28419,28124],{}," 출력과 1:1 일치.",[76,28422,28424],{"id":28423},"_32-drizzle-문법","3.2 Drizzle 문법",[18,28426,28427,28428,28431,28432,28435,28436,28439,28440,28443],{},"각 ",[32,28429,28430],{},"mysqlTable(name, columns)"," 호출에 2번째 인자 콜백 ",[32,28433,28434],{},"(t) => ({...})","을 추가하는 방식. 컬럼은 그대로, 콜백에 ",[32,28437,28438],{},"index(...).on(...)"," 및 ",[32,28441,28442],{},"foreignKey({...})"," 선언.",[5251,28445,28447],{"className":7962,"code":28446,"language":7964,"meta":4208,"style":4208},"export const exportJob = mysqlTable('TB_EXPORT_JOB', {\n  \u002F\u002F ... 컬럼 (변경 없음)\n}, t => ({\n  idxCompanyState: index('idx_export_company_state').on(t.companyId, t.jobState, t.requestedAt),\n  idxUser: index('idx_export_user').on(t.userId, t.requestedAt),\n  fkCompany: foreignKey({ name: 'fk_export_company', columns: [t.companyId], foreignColumns: [company.id] }),\n  fkUser: foreignKey({ name: 'fk_export_user', columns: [t.userId], foreignColumns: [user.id] }),\n}))\n",[32,28448,28449,28470,28475,28489,28510,28529,28546,28561],{"__ignoreMap":4208},[7968,28450,28451,28453,28455,28458,28460,28463,28465,28468],{"class":5110,"line":7970},[7968,28452,7974],{"class":7973},[7968,28454,9400],{"class":7973},[7968,28456,28457],{"class":8892}," exportJob",[7968,28459,9210],{"class":7973},[7968,28461,28462],{"class":7980}," mysqlTable",[7968,28464,6100],{"class":7984},[7968,28466,28467],{"class":7993},"'TB_EXPORT_JOB'",[7968,28469,9301],{"class":7984},[7968,28471,28472],{"class":5110,"line":4212},[7968,28473,28474],{"class":8398},"  \u002F\u002F ... 컬럼 (변경 없음)\n",[7968,28476,28477,28480,28483,28486],{"class":5110,"line":4209},[7968,28478,28479],{"class":7984},"}, ",[7968,28481,28482],{"class":8939},"t",[7968,28484,28485],{"class":7973}," =>",[7968,28487,28488],{"class":7984}," ({\n",[7968,28490,28491,28494,28497,28499,28502,28504,28507],{"class":5110,"line":4219},[7968,28492,28493],{"class":7984},"  idxCompanyState: ",[7968,28495,28496],{"class":7980},"index",[7968,28498,6100],{"class":7984},[7968,28500,28501],{"class":7993},"'idx_export_company_state'",[7968,28503,5606],{"class":7984},[7968,28505,28506],{"class":7980},"on",[7968,28508,28509],{"class":7984},"(t.companyId, t.jobState, t.requestedAt),\n",[7968,28511,28512,28515,28517,28519,28522,28524,28526],{"class":5110,"line":8291},[7968,28513,28514],{"class":7984},"  idxUser: ",[7968,28516,28496],{"class":7980},[7968,28518,6100],{"class":7984},[7968,28520,28521],{"class":7993},"'idx_export_user'",[7968,28523,5606],{"class":7984},[7968,28525,28506],{"class":7980},[7968,28527,28528],{"class":7984},"(t.userId, t.requestedAt),\n",[7968,28530,28531,28534,28537,28540,28543],{"class":5110,"line":8301},[7968,28532,28533],{"class":7984},"  fkCompany: ",[7968,28535,28536],{"class":7980},"foreignKey",[7968,28538,28539],{"class":7984},"({ name: ",[7968,28541,28542],{"class":7993},"'fk_export_company'",[7968,28544,28545],{"class":7984},", columns: [t.companyId], foreignColumns: [company.id] }),\n",[7968,28547,28548,28551,28553,28555,28558],{"class":5110,"line":8310},[7968,28549,28550],{"class":7984},"  fkUser: ",[7968,28552,28536],{"class":7980},[7968,28554,28539],{"class":7984},[7968,28556,28557],{"class":7993},"'fk_export_user'",[7968,28559,28560],{"class":7984},", columns: [t.userId], foreignColumns: [user.id] }),\n",[7968,28562,28563],{"class":5110,"line":8321},[7968,28564,28565],{"class":7984},"}))\n",[18,28567,28568,28569,4473,28571,28573],{},"import에 ",[32,28570,28536],{},[32,28572,28496],{}," 2개 추가 (drizzle-orm\u002Fmysql-core).",[76,28575,28577],{"id":28576},"_33-효과","3.3 효과",[81,28579,28580,28589,28595,28601,28610],{},[84,28581,28582,28588],{},[21,28583,28584,28587],{},[32,28585,28586],{},"drizzle-kit introspect\u002Fgenerate"," drift 해소"," — 라이브와 코드 사이 인덱스\u002FFK 12건 불일치가 0으로.",[84,28590,28591,28594],{},[21,28592,28593],{},"신규 마이그레이션 안전"," — 누가 schema.ts 기준으로 새 마이그레이션 만들 때 \"FK·인덱스 DROP\" SQL이 생성되는 사고 방지.",[84,28596,28597,28600],{},[21,28598,28599],{},"신규 환경 부트스트랩 일관성"," — 새 환경에서 schema.ts → 마이그레이션 → DB 적용 시 동일한 정합 보장.",[84,28602,28603,28606,28607,28609],{},[21,28604,28605],{},"PR 가독성"," — \"이 테이블에 어떤 인덱스가 있는가?\" 답이 한 곳(",[32,28608,28258],{},")에 모임.",[84,28611,28612,28615],{},[21,28613,28614],{},"타입 안전성"," — FK 명시로 join 쿼리 작성 시 관계 자동 추론.",[76,28617,28619],{"id":28618},"_34-안전-가드","3.4 안전 가드",[81,28621,28622,28628,28639,28647,28653],{},[84,28623,28624,28627],{},[21,28625,28626],{},"컬럼 정의 변경 0"," — 1번째 인자 객체는 1바이트도 안 건드림.",[84,28629,28630,89,28633,15217,28636,28638],{},[21,28631,28632],{},"다른 테이블 변경 0",[32,28634,28635],{},"git diff --name-only",[32,28637,21654],{}," 단일 파일만 변경됨을 사전 확인.",[84,28640,28641,89,28644,28646],{},[21,28642,28643],{},"typecheck 통과",[32,28645,10425],{}," (tsc --noEmit) 에러 0.",[84,28648,28649,28652],{},[21,28650,28651],{},"런타임 영향 0"," — Drizzle 쿼리 빌더는 이 콜백을 마이그레이션\u002F툴체인 단에서만 사용. 런타임 DML\u002FSELECT에 변화 없음.",[84,28654,28655,28658,28659,28661],{},[21,28656,28657],{},"Worker 재배포 불필요"," — 배포 #8(",[32,28660,24564],{},") 그대로 라이브 정상.",[76,28663,28665],{"id":28664},"_35-산출물","3.5 산출물",[81,28667,28668,28674],{},[84,28669,28670,28673],{},[32,28671,28672],{},"malgn-noti-api: 0475bd2 db(schema): export\u002Fflow 4 테이블 인덱스·FK 명시화 (라이브 정합)"," (1 file, +22 -4).",[84,28675,28676],{},"라이브 DB·Worker — 변경 없음.",[76,28678,28680],{"id":28679},"_36-다음-단계-알려진-한계","3.6 다음 단계 \u002F 알려진 한계",[81,28682,28683,28689],{},[84,28684,28685,28688],{},[21,28686,28687],{},"49 테이블 전체 점검은 별도 작업"," — schema.ts의 나머지 46 테이블도 동일하게 인덱스\u002FFK 미선언 가능성 큼. 단 서비스 중이라 한 번에 큰 정합 작업은 위험. 추후 별도 마일스톤에서 테이블별로 점진적 점검.",[84,28690,28691,28696],{},[21,28692,28693,28695],{},[32,28694,28204],{}," 한 번 돌려보기"," — 라이브에서 schema 자동 생성해 우리 수기 정의와 diff를 보면 다른 drift도 발견 가능. Aurora 직결 경로 운영 절차 정착 후 진행.",[73,28698],{},[11,28700,28702,28703,4536,28705,4536,28707,28709],{"id":28701},"_4-사용자단-인증-백엔드-연동-authsignupauthloginme-실-api-연동-배포-49","§4. 사용자단 인증 백엔드 연동 — ",[32,28704,23258],{},[32,28706,23261],{},[32,28708,3987],{}," 실 API 연동 (배포 #49)",[76,28711,27637],{"id":28712},"한-줄-2",[18,28714,28715,28716,28719,28720,28723,28724,28726,28727,28730,28731,28733,28734,28736,28737,28739,28740,28743,28744,28747,28748,28750,28751,28754,28755,4536,28757,28759,28760,5606],{},"사용자단의 모든 화면이 목업 데이터로 동작하던 상태에서 ",[21,28717,28718],{},"인증·계정 영역을 첫 번째로 실 API에 연결",". JWT를 ",[32,28721,28722],{},"auth-token"," 쿠키에 저장, ",[32,28725,9248],{}," $fetch 래퍼가 자동으로 ",[32,28728,28729],{},"Authorization: Bearer"," 주입, 글로벌 미들웨어는 쿠키 존재만으로 1차 가드, 클라이언트 부트스트랩 플러그인이 ",[32,28732,3987],{},"로 스토어 풀 컨텍스트 페치. 회원가입은 Step 4(본인 인증) → Step 5(완료) 전이에서 실 API 호출 + 토큰 저장 + 자동 로그인 → ",[32,28735,11278],{}," 이동, 로그인은 ",[32,28738,22750],{}," 쿠키(",[32,28741,28742],{},"last-company-id",", 1년) 자동 사용 + 없으면 필드 노출. 5 파일 수정 + 1 파일 신규(",[32,28745,28746],{},"plugins\u002Fauth.client.ts","), typecheck 통과, 로컬 + 프로덕션 모두 e2e 검증(쿠키 동봉 시 ",[32,28749,11278],{}," 200, 없으면 ",[32,28752,28753],{},"\u002Flogin?redirect=\u002Fhome"," 리다이렉트, ",[32,28756,9335],{},[32,28758,17640],{}," 200). Cloudflare Pages 배포 #49 (alias ",[32,28761,28762],{},"9be4ff61.malgn-noti.pages.dev",[76,28764,28766],{"id":28765},"_41-api-계약-사전-확인-변경-없음","4.1 API 계약 사전 확인 (변경 없음)",[18,28768,28769,28772,28773,28775],{},[32,28770,28771],{},"malgn-noti-api\u002Fsrc\u002Froutes\u002Fauth.ts"," (배포 #8 ",[32,28774,24564],{}," 그대로):",[101,28777,28778,28790],{},[104,28779,28780],{},[107,28781,28782,28784,28787],{},[110,28783,22556],{},[110,28785,28786],{},"요청",[110,28788,28789],{},"응답",[126,28791,28792,28809,28825],{},[107,28793,28794,28798,28804],{},[131,28795,28796],{},[32,28797,22968],{},[131,28799,28800,28803],{},[32,28801,28802],{},"{ companyName, loginid, password, name?, email?, phone? }"," (Zod)",[131,28805,28806],{},[32,28807,28808],{},"201 { data: { user: {id, loginid, name, role}, company: {id, name}, token } }",[107,28810,28811,28815,28820],{},[131,28812,28813],{},[32,28814,22974],{},[131,28816,28817,28803],{},[32,28818,28819],{},"{ companyId: number, loginid, password }",[131,28821,28822],{},[32,28823,28824],{},"200 { data: { user, company:{id}, token } }",[107,28826,28827,28832,28834],{},[131,28828,28829,28831],{},[32,28830,21820],{}," (Bearer)",[131,28833,322],{},[131,28835,28836],{},[32,28837,28838],{},"200 { data: { user, company, ctxRole } }",[18,28840,28841,47,28844,28846,28847,21731,28849,28851],{},[21,28842,28843],{},"핵심 제약",[32,28845,20087],{},"는 회사 스코프 내 unique (composite UNIQUE on company_id, loginid) → ",[32,28848,23261],{},[32,28850,22750],{},"를 필수로 받음. 사용자가 자신의 companyId를 항상 알기 어려우므로 UX 보정 필요.",[76,28853,28855],{"id":28854},"_42-결정-사항","4.2 결정 사항",[81,28857,28858,28866,28876,28897],{},[84,28859,28860,47,28863,28865],{},[21,28861,28862],{},"JWT 저장 위치",[32,28864,28722],{}," 쿠키 (maxAge 7일, sameSite=lax, secure는 PROD에서만). 백엔드가 Set-Cookie를 안 쓰고 응답 본문에 토큰을 담아 보내므로 일반 쿠키(HttpOnly 아님). 향후 백엔드가 Set-Cookie + HttpOnly + SameSite=Strict로 응답하면 그쪽으로 이관.",[84,28867,28868,47,28873,28875],{},[21,28869,28870,28872],{},[32,28871,22750],{}," 자동 사용",[32,28874,28742],{}," 쿠키(1년)에 회원가입\u002F로그인 시 저장 → 다음 로그인 폼에서 자동 사용. 새 브라우저\u002F쿠키 삭제 시에만 \"고객사 ID\" 필드 노출.",[84,28877,28878,28881,28882,28884,28885,28888,28889,28892,28893,28896],{},[21,28879,28880],{},"SSR 안전성",": 미들웨어는 쿠키 존재만 확인 → 통과\u002F리다이렉트만 결정. ",[32,28883,3987],{}," 호출(스토어 페치)은 ",[21,28886,28887],{},"클라이언트 플러그인","으로 분리 — Pinia store action 안에서 ",[32,28890,28891],{},"useCookie()"," 호출이 SSR 미들웨어 컨텍스트를 잃어 ",[32,28894,28895],{},"\"composable was called outside of …\""," 에러를 내는 문제 회피(실제 발생 → 수정 후 통과).",[84,28898,28899,28902,28903,28905],{},[21,28900,28901],{},"회원가입 흐름",": Step 4(본인 인증 완료) → \"가입 완료\" 클릭 → 실 API 호출 → 성공 시 Step 5(완료) 노출 + 자동 로그인 + 발급 고객사 ID 표시 → \"대시보드로 이동\" 클릭 시 ",[32,28904,11278],{},". 실패 시 토스트 + 단계 유지.",[76,28907,28909],{"id":28908},"_43-코드-변경-6-파일","4.3 코드 변경 (6 파일)",[101,28911,28912,28921],{},[104,28913,28914],{},[107,28915,28916,28919],{},[110,28917,28918],{},"파일",[110,28920,7525],{},[126,28922,28923,28949,28994,29012,29027,29062,29095],{},[107,28924,28925,28930],{},[131,28926,28927],{},[32,28928,28929],{},"app\u002Fcomposables\u002FuseApi.ts",[131,28931,28932,4536,28935,28938,28939,28942,28943,28945,28946,28948],{},[32,28933,28934],{},"useAuthToken()",[32,28936,28937],{},"useLastCompanyId()"," 쿠키 헬퍼 export. $fetch ",[32,28940,28941],{},"onRequest","에서 토큰 자동 ",[32,28944,28729],{}," 주입. 401 응답 시 토큰 클리어 + 스토어 클리어 + ",[32,28947,9335],{}," 이동.",[107,28950,28951,28956],{},[131,28952,28953],{},[32,28954,28955],{},"app\u002Fstores\u002Fauth.ts",[131,28957,28958,4536,28961,28964,28965,28967,28968,4420,28971,4420,28974,4420,28977,28980,28981,28983,28984,28986,28987,28989,28990,28993],{},[32,28959,28960],{},"AuthUser",[32,28962,28963],{},"AuthCompany"," 타입 신규 (",[32,28966,28771],{}," 응답 형상 그대로). ",[32,28969,28970],{},"signup()",[32,28972,28973],{},"login()",[32,28975,28976],{},"fetchMe()",[32,28978,28979],{},"logout()"," 액션. ",[32,28982,4552],{},"은 응답을 즉시 store에 hydrate해 isAuthed=true. ",[32,28985,22088],{},"은 응답 hydrate + ",[32,28988,3987],{},"로 풀 컨텍스트 보강. ",[32,28991,28992],{},"fetchMe","는 토큰 만료 시 토큰 클리어 후 false 반환.",[107,28995,28996,29001],{},[131,28997,28998],{},[32,28999,29000],{},"app\u002Fmiddleware\u002Fauth.global.ts",[131,29002,29003,29004,29007,29008,29011],{},"토큰 쿠키 존재 여부만 확인 (SSR 안전). 없으면 ",[32,29005,29006],{},"\u002Flogin?redirect=…"," 리다이렉트. ",[32,29009,29010],{},"meta.auth === false","는 그대로 통과.",[107,29013,29014,29020],{},[131,29015,29016,29019],{},[32,29017,29018],{},"app\u002Fplugins\u002Fauth.client.ts"," (신규)",[131,29021,29022,29023,29026],{},"클라이언트 부트스트랩 1회 — 토큰 쿠키가 있고 store가 비어 있으면 ",[32,29024,29025],{},"auth.fetchMe()"," 호출. SSR 미들웨어 컨텍스트 손실 문제를 피해 클라이언트 측에서 처리.",[107,29028,29029,29033],{},[131,29030,29031],{},[32,29032,17601],{},[131,29034,29035,4536,29038,29040,29041,29043,29044,29047,29048,21731,29051,29054,29055,29058,29059,29061],{},[32,29036,29037],{},"useAuthStore()",[32,29039,28937],{}," 사용. ",[32,29042,28742],{}," 쿠키가 있으면 자동 사용, 없으면 \"고객사 ID\" 필드(",[32,29045,29046],{},"v-if=\"needCompanyId\"",") 노출. ",[32,29049,29050],{},"onLogin()",[32,29052,29053],{},"auth.login()"," 호출 → 성공 시 ",[32,29056,29057],{},"redirect"," 쿼리(",[32,29060,11278],{}," 기본)로 이동. 401 응답은 \"아이디 또는 비밀번호가 올바르지 않습니다\" 토스트.",[107,29063,29064,29068],{},[131,29065,29066],{},[32,29067,18107],{},[131,29069,29070,13739,29073,29076,29077,29080,29081,12916,29083,29054,29086,29089,29090,6219,29093,4703],{},[32,29071,29072],{},"goNext()",[32,29074,29075],{},"step.value === 4","일 때 ",[32,29078,29079],{},"submitSignup()"," 호출(이전: 단순 step 증가만). ",[32,29082,29079],{},[32,29084,29085],{},"auth.signup({companyName, loginid: email, password, email, name, phone})",[32,29087,29088],{},"step.value = 5","로 완료 화면 노출, 실패 시 409 응답을 \"이미 가입된 이메일입니다\" 안내. Step 5는 발급 고객사 ID 표시 + \"대시보드로 이동\" 버튼이 ",[32,29091,29092],{},"finish()",[32,29094,11278],{},[107,29096,29097,29101],{},[131,29098,29099],{},[32,29100,4405],{},[131,29102,29103,29106,29107,6219,29110,29113,29114,29117],{},[32,29104,29105],{},"runtimeConfig.public.apiBaseUrl"," 기본값을 ",[32,29108,29109],{},"'\u002Fapi'",[32,29111,29112],{},"'https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev'","로 변경. ",[32,29115,29116],{},"NUXT_PUBLIC_API_BASE_URL","로 그대로 override 가능.",[76,29119,29121],{"id":29120},"_44-발견된-ssr-이슈-우회-의미-있는-발견","4.4 발견된 SSR 이슈 + 우회 (의미 있는 발견)",[18,29123,29124,29125,13735,29127,29130],{},"첫 시도(",[32,29126,11029],{},[32,29128,29129],{},"await auth.fetchMe()"," 호출)는 500 에러:",[5251,29132,29135],{"className":29133,"code":29134,"language":5256},[5254],"[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin,\n       Nuxt hook, Nuxt middleware, or Vue setup function.\n  at useCookie (...cookie.js:38:19)\n  at useAuthToken (...\u002FuseApi.ts:15:45)\n  at Proxy.fetchMe (...\u002Fauth.ts:70:47)\n  at ...\u002Fauth.global.ts:15:104\n",[32,29136,29134],{"__ignoreMap":4208},[18,29138,29139,29140,29142,29143,29145,29146,29148],{},"원인 — Pinia store action(",[32,29141,28992],{},") 내부에서 ",[32,29144,28891],{},"(via ",[32,29147,28934],{},")를 호출하면, await 경계를 넘으면서 Nuxt instance 컨텍스트가 끊김. 동기 미들웨어 함수 안에서는 통하지만 store action 안에서는 컨텍스트 보장이 약함.",[18,29150,29151,29152,29154,29155,29157],{},"해결 — SSR 미들웨어는 쿠키 존재만 확인하고 통과 결정. ",[32,29153,3987],{}," 검증은 클라이언트 부트스트랩 플러그인에서 1회만 호출. 토큰이 위조\u002F만료면 fetchMe가 토큰을 클리어하고 false 반환 → 다음 라우트 가드에서 ",[32,29156,9335],{},"으로 리다이렉트. SSR 비용 0 + 안전.",[76,29159,29161],{"id":29160},"_45-검증-로컬-프로덕션","4.5 검증 (로컬 + 프로덕션)",[18,29163,29164],{},"API 통한 e2e 4건 × 2환경(로컬 dev + 프로덕션 Pages):",[101,29166,29167,29178],{},[104,29168,29169],{},[107,29170,29171,29173,29176],{},[110,29172,27964],{},[110,29174,29175],{},"기대",[110,29177,22362],{},[126,29179,29180,29195,29207,29221,29234],{},[107,29181,29182,29188,29193],{},[131,29183,29184,29187],{},[32,29185,29186],{},"GET \u002Fhome"," (토큰 쿠키 없음)",[131,29189,29190,29191],{},"302 → ",[32,29192,28753],{},[131,29194,3869],{},[107,29196,29197,29202,29205],{},[131,29198,29199,29201],{},[32,29200,29186],{}," (토큰 쿠키 동봉)",[131,29203,29204],{},"200 (통과)",[131,29206,3869],{},[107,29208,29209,29217,29219],{},[131,29210,29211,6685,29214,4343],{},[32,29212,29213],{},"GET \u002Flogin",[32,29215,29216],{},"meta.auth: false",[131,29218,21542],{},[131,29220,3869],{},[107,29222,29223,29230,29232],{},[131,29224,29225,6685,29228,4343],{},[32,29226,29227],{},"GET \u002Fsignup",[32,29229,29216],{},[131,29231,21542],{},[131,29233,3869],{},[107,29235,29236,29242,29244],{},[131,29237,29238,6685,29240,4343],{},[32,29239,54],{},[32,29241,19861],{},[131,29243,21542],{},[131,29245,29246],{},"✅ (회귀 없음)",[18,29248,29249,29250,29252,29253,29255],{},"토큰 자체는 ",[32,29251,23258],{}," 라이브 호출로 발급 → 쿠키 동봉으로 SSR 진입 → 미들웨어 통과 확인. 클라이언트 측 ",[32,29254,3987],{}," 호출은 브라우저 환경 필요라 별도 수동 점검 필요(다음 단계).",[237,29257,29259],{"id":29258},"_451-발견된-ssr-컨텍스트-버그첫-미들웨어-버전-우회-후-통과-확인","4.5.1 발견된 SSR 컨텍스트 버그(첫 미들웨어 버전) — 우회 후 통과 확인",[81,29261,29262,29270],{},[84,29263,29264,29265,13735,29267,29269],{},"1차 시도: ",[32,29266,11029],{},[32,29268,29129],{}," 직접 호출 → 500 (위 §4.4).",[84,29271,29272,29273,29275],{},"2차 시도: 미들웨어 단순화 + ",[32,29274,28746],{}," 신설 → 500 → 200 회복 확인.",[76,29277,29279],{"id":29278},"_46-배포-49","4.6 배포 #49",[81,29281,29282,29289,29294],{},[84,29283,29284,27486,29286,29288],{},[32,29285,11195],{},[32,29287,13302],{}," 프리셋. login\u002Fsignup 청크 + auth plugin 청크 새로 생성.",[84,29290,29291,4703],{},[32,29292,29293],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"user-side auth integration\"",[84,29295,27508,29296,29299,29300,29302],{},[32,29297,29298],{},"https:\u002F\u002F9be4ff61.malgn-noti.pages.dev",". 프로덕션 ",[32,29301,10317],{}," 갱신.",[76,29304,29306],{"id":29305},"_47-산출물","4.7 산출물",[81,29308,29309,29315,29333,29339,29342],{},[84,29310,29311,29314],{},[32,29312,29313],{},"malgn-noti: 사용자단 인증 백엔드 연동 (배포 #49)"," — 6 파일 수정 + 1 신규.",[84,29316,13651,29317,4420,29320,4420,29323,4420,29326,4420,29328,4420,29330,4703],{},[26,29318,28929],{"href":29319},"..\u002F..\u002Fapp\u002Fcomposables\u002FuseApi.ts",[26,29321,28955],{"href":29322},"..\u002F..\u002Fapp\u002Fstores\u002Fauth.ts",[26,29324,29000],{"href":29325},"..\u002F..\u002Fapp\u002Fmiddleware\u002Fauth.global.ts",[26,29327,17601],{"href":17600},[26,29329,18107],{"href":18106},[26,29331,4405],{"href":29332},"..\u002F..\u002Fnuxt.config.ts",[84,29334,29335,29336,4703],{},"신규: ",[26,29337,29018],{"href":29338},"..\u002F..\u002Fapp\u002Fplugins\u002Fauth.client.ts",[84,29340,29341],{},"WBS 갱신: 5-3-15 ⚪ → 🟢 (인증·계정 실 API 연동 완료, 발송·이력 등 나머지 점진 교체).",[84,29343,29344],{},"라이브 API\u002FDB — 변경 없음(쓰기는 검증 과정의 임시 계정 4건, 모두 cleanup).",[76,29346,29348],{"id":29347},"_48-알려진-한계-다음-단계","4.8 알려진 한계 \u002F 다음 단계",[81,29350,29351,29365,29373,29379,29405,29416],{},[84,29352,29353,29360,29361,29364],{},[21,29354,29355,11686,29357,29359],{},[32,29356,23261],{},[32,29358,22750],{}," 요구"," — 새 브라우저에서 사용자가 자신의 ID를 외워야 함. 후속에서 ",[32,29362,29363],{},"\u002Fauth\u002Flogin-by-email"," 등 이메일 → 회사 lookup 라우트 추가 고려.",[84,29366,29367,29372],{},[21,29368,29369,29371],{},[32,29370,3987],{}," SSR 검증 분리"," — 토큰이 유효한지 라우트 진입 시점에 서버에서 확인하지 않으므로, 만료된 토큰이라도 1회는 페이지가 로드되고 그 뒤 클라이언트 fetchMe에서 401 처리. 보안상 큰 문제는 아니지만 첫 페인트 후 리다이렉트가 깜빡일 수 있음.",[84,29374,29375,29378],{},[21,29376,29377],{},"HttpOnly 미적용"," — 토큰이 JS에서 읽힘. XSS 발생 시 토큰 탈취 가능. 백엔드가 Set-Cookie + HttpOnly로 응답하도록 확장 시 클라이언트 코드 단순화 + 보안 강화.",[84,29380,29381,29391,29392,4473,29395,4473,29398,4473,29401,29404],{},[21,29382,29383,29384,29387,29388,29390],{},"OTP 인증(",[32,29385,29386],{},"TB_VERIFICATION",")·약관 동의(",[32,29389,28079],{},")·서비스 담당자 초대"," — signup 라우트는 이를 적재하지 않음. 프런트에서 입력은 받지만 백엔드는 무시. 후속 라우트(",[32,29393,29394],{},"\u002Fauth\u002Fverify-email",[32,29396,29397],{},"\u002Fauth\u002Fverify-phone",[32,29399,29400],{},"\u002Fauth\u002Fagree-terms",[32,29402,29403],{},"\u002Fmanager-invites",") 구현 필요.",[84,29406,29407,29410,29411,4473,29413,29415],{},[21,29408,29409],{},"나머지 화면 연동"," — 발송 6채널·이력·주소록·발신정보·템플릿·캠페인·크레딧·문의·나의 페이지 — 모두 여전히 목업. 화면별 도메인 API(",[32,29412,21826],{},[32,29414,21849],{}," 등)로 점진 교체.",[84,29417,29418,29421,29422,29425,29426,29428],{},[21,29419,29420],{},"로그아웃 UX"," — 현재 ",[32,29423,29424],{},"useAuthStore().logout()","만 정의. GNB의 로그아웃 버튼은 ",[32,29427,8210],{},"에서 데모용 ref만 토글 중 — 실 호출로 교체 필요.",[73,29430],{},[11,29432,29434,29435,4536,29438,29441],{"id":29433},"_5-이메일-otp-인증-authemail-codesendverify-신설-signupvue-실-api-연동-배포-950","§5. 이메일 OTP 인증 — ",[32,29436,29437],{},"\u002Fauth\u002Femail-code\u002Fsend",[32,29439,29440],{},"\u002Fverify"," 신설 + signup.vue 실 API 연동 (배포 #9·#50)",[76,29443,27637],{"id":29444},"한-줄-3",[18,29446,29447,29448,5069,29450,29453,29454,29457,29458,11686,29460,4361,29463,29466,29467,4361,29470,29473,29474,29477,29478,29481],{},"§4의 알려진 한계 #4(이메일 OTP 미연동, 화면용 토스트만 동작)를 해소. 백엔드에 OTP 발송·검증 라우트 2개 추가(TB_VERIFICATION 적재 + SHA-256 코드 해시 + TTL 10분·재발송 시 직전 코드 만료·5회 시도 제한·소비 후 재사용 차단), Drizzle ",[32,29449,28258],{},[32,29451,29452],{},"verification"," 정의(라이브 정본과 인덱스 일치), OpenAPI 4지점 갱신(2 paths + 3 schemas), Workers 배포 #9(Version ",[32,29455,29456],{},"83f32a61...","). 프런트 ",[26,29459,14163],{"href":18106},[32,29461,29462],{},"sendIdCode",[32,29464,29465],{},"confirmIdCode","를 실 API 호출로 교체 + 버튼 로딩 상태(",[32,29468,29469],{},"sendingCode",[32,29471,29472],{},"verifyingCode",") + 재발송 라벨 + 에러 메시지 표준화. NHN_MOCK=1 환경에서만 응답에 ",[32,29475,29476],{},"mockCode"," 노출(개발 편의 — production 자동 차단). Pages 배포 #50 (alias ",[32,29479,29480],{},"c2100890.malgn-noti.pages.dev","). 라이브 e2e 6 시나리오 모두 통과 (발송·잘못된 코드 401·올바른 코드 200·소비 후 재시도 401·재발송 신규 코드·DB 행 검증).",[76,29483,29485],{"id":29484},"_51-결정-사항","5.1 결정 사항",[81,29487,29488,29503,29512,29526,29535,29545,29559,29574],{},[84,29489,29490,47,29492,29494,29495,29498,29499,29502],{},[21,29491,16953],{},[32,29493,29386],{}," (이미 라이브) 활용. ",[32,29496,29497],{},"code_hash","로 평문 코드 저장 회피. 해시는 ",[32,29500,29501],{},"SHA-256(target|purpose|code)"," — Web Crypto API 네이티브.",[84,29504,29505,29508,29509,4703],{},[21,29506,29507],{},"TTL",": 10분. ",[32,29510,29511],{},"expires_at = now + 10*60*1000",[84,29513,29514,29517,29518,29521,29522,29525],{},[21,29515,29516],{},"재발송",": 같은 ",[32,29519,29520],{},"(email, purpose)","의 미소비·미만료 레코드를 즉시 만료 처리(",[32,29523,29524],{},"expires_at = now",") → 직전 코드로 검증 불가.",[84,29527,29528,29531,29532,5606],{},[21,29529,29530],{},"시도 제한",": 5회 초과 시 즉시 만료(",[32,29533,29534],{},"OTP_MAX_ATTEMPTS = 5",[84,29536,29537,29540,29541,29544],{},[21,29538,29539],{},"소비",": 검증 성공 시 ",[32,29542,29543],{},"consumed_at = now"," → 같은 코드 재사용 차단.",[84,29546,29547,47,29550,7505,29552,7505,29555,29558],{},[21,29548,29549],{},"purpose enum",[32,29551,4552],{},[32,29553,29554],{},"reset_password",[32,29556,29557],{},"change_email",". signup 외 흐름은 후속 라우트가 활용.",[84,29560,29561,47,29566,29569,29570,29573],{},[21,29562,29563,29565],{},[32,29564,29476],{}," 노출",[32,29567,29568],{},"c.env.NHN_MOCK === '1'","일 때만 응답에 ",[32,29571,29572],{},"mockCode: code"," 포함. production은 secret 미설정이면 자동으로 노출 안 됨. real NHN 자격증명 등록 후 secret도 영구 제거.",[84,29575,29576,29581],{},[21,29577,29578,29580],{},[32,29579,23258],{},"에 강제 검증 미추가",": 후속 결정 사항(검증 게이트 도입 시점)이 필요하므로 본 단계에서는 백엔드 호환성 유지 + 프런트가 UX 차원에서 검증 강제. 이전 e2e 테스트·기존 통합 사용처에 영향 없음.",[76,29583,29585,29586,4343],{"id":29584},"_52-코드-변경-백엔드-malgn-noti-api","5.2 코드 변경 (백엔드 — ",[32,29587,50],{},[101,29589,29590,29598],{},[104,29591,29592],{},[107,29593,29594,29596],{},[110,29595,28918],{},[110,29597,7525],{},[126,29599,29600,29616,29633,29669,29693],{},[107,29601,29602,29607],{},[131,29603,29604],{},[26,29605,21654],{"href":29606},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fdb\u002Fschema.ts",[131,29608,29609,29611,29612,29615],{},[32,29610,29452],{}," 테이블 신규 정의 (8 컬럼 + ",[32,29613,29614],{},"idx_verif_target(target_type, target, purpose, expires_at)"," — 라이브 정본과 1:1).",[107,29617,29618,29623],{},[131,29619,29620],{},[26,29621,21753],{"href":29622},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Flib\u002Ferrors.ts",[131,29624,29625,29628,29629,29632],{},[32,29626,29627],{},"errors.unauthenticated()","에 default 메시지 파라미터 추가 (",[32,29630,29631],{},"(msg = 'Authentication required')","). 호환성 유지 + OTP 라우트에서 한국어 메시지 첨부 가능.",[107,29634,29635,29640],{},[131,29636,29637],{},[26,29638,22961],{"href":29639},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Froutes\u002Fauth.ts",[131,29641,29642,29643,29646,29647,29650,29651,4473,29654,29657,29658,4339,29661,29664,29665,29668],{},"헬퍼 4개 추가: ",[32,29644,29645],{},"generateOtpCode()"," (Web Crypto getRandomValues 4 bytes → 6 digits), ",[32,29648,29649],{},"hashOtpCode()"," (SHA-256), ",[32,29652,29653],{},"purposeLabel()",[32,29655,29656],{},"buildEmailBody()"," (HTML 템플릿). 라우트 2개: ",[32,29659,29660],{},"POST \u002Fauth\u002Femail-code\u002Fsend",[32,29662,29663],{},"POST \u002Fauth\u002Femail-code\u002Fverify",". NHN Email 어댑터 호출(",[32,29666,29667],{},"sendEmail(null, ...)",") — 자격증명 미설정 시 어댑터 내부에서 mock fallback.",[107,29670,29671,29676],{},[131,29672,29673],{},[26,29674,22139],{"href":29675},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fopenapi.ts",[131,29677,29678,29679,4536,29681,29683,29684,4536,29687,4536,29690,5606],{},"2 paths(",[32,29680,29437],{},[32,29682,29440],{},") + 3 schemas(",[32,29685,29686],{},"EmailCodeSendRequest",[32,29688,29689],{},"EmailCodeSendResponse",[32,29691,29692],{},"EmailCodeVerifyRequest",[107,29694,29695,29699],{},[131,29696,29697],{},[32,29698,11253],{},[131,29700,29701],{},"변경 없음.",[76,29703,29705,29706,4343],{"id":29704},"_53-코드-변경-프런트-malgn-noti","5.3 코드 변경 (프런트 — ",[32,29707,7664],{},[101,29709,29710,29718],{},[104,29711,29712],{},[107,29713,29714,29716],{},[110,29715,28918],{},[110,29717,7525],{},[126,29719,29720],{},[107,29721,29722,29726],{},[131,29723,29724],{},[26,29725,18107],{"href":18106},[131,29727,29728,6219,29731,29733,29734,29736,29737,29739,29740,7505,29743,7505,29745,29748,29749,6219,29752,29754,29755,4536,29758,4536,29761,29764,29765,29767,29768,4703],{},[32,29729,29730],{},"sendIdCode()",[32,29732,29660],{}," async. 응답 ",[32,29735,29476],{}," 있으면 토스트에 노출(개발 편의). ",[32,29738,29469],{}," 로딩 ref. 버튼 라벨 ",[32,29741,29742],{},"발송 중…",[32,29744,29516],{},[32,29746,29747],{},"인증코드 발송"," 3-상태. ",[32,29750,29751],{},"confirmIdCode()",[32,29753,29663],{}," async. 백엔드 한국어 에러 메시지(",[32,29756,29757],{},"인증코드가 만료되었거나 …",[32,29759,29760],{},"시도 횟수를 초과했습니다 …",[32,29762,29763],{},"인증코드가 올바르지 않습니다",")를 그대로 토스트에. ",[32,29766,29472],{}," 로딩 + 버튼 라벨 ",[32,29769,29770],{},"확인 중…",[76,29772,29774],{"id":29773},"_54-라이브-e2e-검증-production","5.4 라이브 e2e 검증 (Production)",[18,29776,29777,29779],{},[32,29778,25204],{}," secret을 production에 일시 적용 → 6 시나리오 검증 → secret 즉시 제거.",[101,29781,29782,29793],{},[104,29783,29784],{},[107,29785,29786,29788,29791],{},[110,29787,3846],{},[110,29789,29790],{},"시나리오",[110,29792,22362],{},[126,29794,29795,29812,29831,29845,29858,29875],{},[107,29796,29797,29799,29810],{},[131,29798,4636],{},[131,29800,29801,29803,29804,4339,29807],{},[32,29802,29660],{}," → 200 + ",[32,29805,29806],{},"mockCode: \"092004\"",[32,29808,29809],{},"expiresAt",[131,29811,3869],{},[107,29813,29814,29816,29829],{},[131,29815,4649],{},[131,29817,29818,29821,29822,29825,29826],{},[32,29819,29820],{},"POST \u002Fverify"," 잘못된 코드(",[32,29823,29824],{},"\"000000\"",") → 401 ",[32,29827,29828],{},"인증코드가 올바르지 않습니다.",[131,29830,3869],{},[107,29832,29833,29835,29843],{},[131,29834,4662],{},[131,29836,29837,29839,29840],{},[32,29838,29820],{}," 올바른 코드 → 200 + ",[32,29841,29842],{},"{verified:true}",[131,29844,3869],{},[107,29846,29847,29849,29856],{},[131,29848,4678],{},[131,29850,29851,29852,29855],{},"같은 코드 재시도 → 401 ",[32,29853,29854],{},"인증코드가 만료되었거나 발급된 적이 없습니다."," (consumed)",[131,29857,3869],{},[107,29859,29860,29862,29873],{},[131,29861,4691],{},[131,29863,29864,29865,29868,29869,29872],{},"재발송 → 새 코드(",[32,29866,29867],{},"461872",") + 직전 코드(",[32,29870,29871],{},"092004",") 즉시 만료",[131,29874,3869],{},[107,29876,29877,29879,29882],{},[131,29878,4708],{},[131,29880,29881],{},"DB 행 점검 — id=1 attempts=1+consumed_at, id=2 신규+expires_at 신규",[131,29883,3869],{},[18,29885,29886,29887,29889,29890,6219,29893,29896,29897,29899],{},"검증 후 ",[32,29888,26781],{}," secret ",[32,29891,29892],{},"wrangler secret delete NHN_MOCK",[32,29894,29895],{},"wrangler secret list"," 응답에 JWT_SECRET만 잔존 확인. 재호출 시 응답에서 ",[32,29898,29476],{}," 사라짐 확인.",[18,29901,29902],{},"검증 과정에서 생성된 TB_VERIFICATION 임시 행은 즉시 cleanup.",[76,29904,29906],{"id":29905},"_55-배포-950","5.5 배포 #9·#50",[81,29908,29909,29922],{},[84,29910,29911,47,29914,20705,29916,29918,29919,4703],{},[21,29912,29913],{},"API (Workers)",[32,29915,10425],{},[32,29917,18672],{}," → Version ",[32,29920,29921],{},"83f32a61-ca2c-4094-ae21-0cfcb174f26c",[84,29923,29924,47,29927,6219,29929,29932,29933,4703],{},[21,29925,29926],{},"사용자단 (Pages)",[32,29928,11195],{},[32,29930,29931],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti --branch=main --commit-dirty=true --commit-message \"email OTP integration\""," → alias ",[32,29934,29935],{},"https:\u002F\u002Fc2100890.malgn-noti.pages.dev",[76,29937,29939],{"id":29938},"_56-산출물","5.6 산출물",[81,29941,29942,29945,29948,29951,29954],{},[84,29943,29944],{},"API: 4 파일 수정 — schema.ts + errors.ts + auth.ts + openapi.ts.",[84,29946,29947],{},"사용자단: 1 파일 수정 — signup.vue.",[84,29949,29950],{},"SIGNUP.md §8 #4 → ✅ (이메일) + #4b 휴대폰 미연동으로 재구성.",[84,29952,29953],{},"라이브 DB — TB_VERIFICATION에 라이브 행이 실 사용자 가입 시점부터 적재 시작 (검증용 임시 행은 cleanup).",[84,29955,29956,29957,29959],{},"라이브 Worker — 변경 #9 적용. ",[32,29958,20116],{}," 정상.",[76,29961,29963],{"id":29962},"_57-알려진-한계-다음-단계","5.7 알려진 한계 \u002F 다음 단계",[81,29965,29966,29983,29995,30007,30015],{},[84,29967,29968,29971,29972,21731,29975,29978,29979,29982],{},[21,29969,29970],{},"실제 이메일은 발송되지 않음"," — TB_NHN_CREDENTIAL 비어 있어 ",[32,29973,29974],{},"sendEmail",[32,29976,29977],{},"(creds=null, mockMode=false)"," → 어댑터 내부 mock fallback. 사용자가 가입 시 코드 자체는 발급되지만 메일함에 도착 0. ",[21,29980,29981],{},"자격증명 등록은 별도 작업","(NHN Cloud 콘솔에서 채널별 appKey 발급 → TB_NHN_CREDENTIAL 적재 + envelope 암호화). 그 전까지는 NHN_MOCK secret을 운영자가 일시 적용해 mockCode 확인 가능.",[84,29984,29985,29988,29989,4536,29992,29994],{},[21,29986,29987],{},"휴대폰 OTP 미연동"," — Step 4(휴대폰 본인 인증)는 여전히 화면 더미. 인증 사업자(PASS·NICE 등) 선정 후 어댑터 + ",[32,29990,29991],{},"\u002Fauth\u002Fphone-code\u002Fsend",[32,29993,29440],{}," 동일 패턴 신설.",[84,29996,29997,30002,30003,30006],{},[21,29998,29999,30001],{},[32,30000,23258],{}," 강제 검증 미적용"," — 정책 결정 후 적용 가능(",[32,30004,30005],{},"emailVerificationToken"," 필수화 또는 signup 직전 TB_VERIFICATION 조회).",[84,30008,30009,30014],{},[21,30010,30011,30013],{},[32,30012,28258],{}," 인덱스 누락 점검"," — 본 단계에서 verification만 인덱스\u002FFK 명시화. 다른 테이블은 §3에서 export\u002Fflow 4 테이블만 처리한 상태 그대로.",[84,30016,30017,30020],{},[21,30018,30019],{},"Rate limit"," — IP·이메일별 분 단위 발송 제한 미적용. 후속 작업으로 Cloudflare KV 또는 Durable Objects 카운터 도입 검토.",[73,30022],{},[8560,30024,30025],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":4208,"searchDepth":4209,"depth":4209,"links":30027},[30028,30029,30030,30031,30038,30039,30040,30041,30042,30043,30044,30045,30046,30047,30048,30050,30051,30052,30053,30054,30055,30056,30057,30058,30059,30060,30061,30062,30063,30064,30067,30068,30069,30070,30071,30072,30074,30076,30077,30078,30079],{"id":10635,"depth":4212,"text":10636},{"id":24568,"depth":4212,"text":24569},{"id":26966,"depth":4212,"text":26967},{"id":27051,"depth":4212,"text":27052,"children":30032},[30033,30035,30037],{"id":27055,"depth":4209,"text":30034},"3.1 doc\u002FWBS.md — 텍스트 정본 (신규)",{"id":27130,"depth":4209,"text":30036},"3.2 app\u002Fpages\u002Fwbs.vue — 공개 라이브 카탈로그 (신규)",{"id":27422,"depth":4209,"text":27423},{"id":27478,"depth":4212,"text":27479},{"id":10916,"depth":4212,"text":10916},{"id":13153,"depth":4212,"text":12598},{"id":27636,"depth":4212,"text":27637},{"id":27671,"depth":4212,"text":27672},{"id":27765,"depth":4212,"text":27766},{"id":27829,"depth":4212,"text":27830},{"id":27874,"depth":4212,"text":27875},{"id":27954,"depth":4212,"text":27955},{"id":28045,"depth":4212,"text":28046},{"id":28112,"depth":4212,"text":30049},"2.7 0002_export_flow.sql 라이브 정본 동기화",{"id":28163,"depth":4212,"text":28164},{"id":28190,"depth":4212,"text":28191},{"id":28262,"depth":4212,"text":27637},{"id":28292,"depth":4212,"text":28293},{"id":28423,"depth":4212,"text":28424},{"id":28576,"depth":4212,"text":28577},{"id":28618,"depth":4212,"text":28619},{"id":28664,"depth":4212,"text":28665},{"id":28679,"depth":4212,"text":28680},{"id":28712,"depth":4212,"text":27637},{"id":28765,"depth":4212,"text":28766},{"id":28854,"depth":4212,"text":28855},{"id":28908,"depth":4212,"text":28909},{"id":29120,"depth":4212,"text":29121},{"id":29160,"depth":4212,"text":29161,"children":30065},[30066],{"id":29258,"depth":4209,"text":29259},{"id":29278,"depth":4212,"text":29279},{"id":29305,"depth":4212,"text":29306},{"id":29347,"depth":4212,"text":29348},{"id":29444,"depth":4212,"text":27637},{"id":29484,"depth":4212,"text":29485},{"id":29584,"depth":4212,"text":30073},"5.2 코드 변경 (백엔드 — malgn-noti-api)",{"id":29704,"depth":4212,"text":30075},"5.3 코드 변경 (프런트 — malgn-noti)",{"id":29773,"depth":4212,"text":29774},{"id":29905,"depth":4212,"text":29906},{"id":29938,"depth":4212,"text":29939},{"id":29962,"depth":4212,"text":29963},{},"\u002Fhistory\u002Fhistory.20260601",{"title":26851,"description":4208},"history\u002Fhistory.20260601","3PzEYB6TSxOmwoBGbfh0CAvctYdbA4Buu7de5zZ6ajQ",{"id":30086,"title":30087,"body":30088,"description":4208,"extension":4257,"meta":39597,"navigation":4259,"path":39598,"seo":39599,"stem":39600,"__hash__":39601},"docs\u002Fhistory\u002Fhistory.20260602.md","2026-06-02 — WBS 3 트랙 분리 + 로그인 UX(고객사 ID 제거) + loginid 전역 UNIQUE + 휴대폰 OTP + 토스트 가시성 + NICE 통합인증 인프라",{"type":8,"value":30089,"toc":39426},[30090,30093,30095,30162,30164,30168,30170,30193,30197,30200,30204,30278,30284,30288,30291,30302,30310,30314,30419,30423,30441,30445,30451,30489,30491,30498,30500,30529,30535,30539,30603,30617,30624,30696,30700,30775,30778,30782,30818,30822,30846,30850,30870,30872,30878,30880,30923,30927,31001,31004,31008,31014,31036,31040,31091,31095,31161,31165,31232,31236,31300,31304,31338,31342,31398,31402,31405,31419,31421,31425,31427,31442,31446,31454,31526,31538,31541,31545,31552,31595,31602,31609,31615,31623,31745,31749,31756,31760,31802,31806,31837,31841,31848,31850,31854,31857,31876,31878,31954,31958,31964,31971,32039,32045,32070,32084,32091,32177,32184,32251,32258,32267,32315,32318,32322,32329,32334,32370,32383,32393,32396,32400,32504,32507,32513,32516,32560,32564,32625,32629,32689,32691,32702,32705,32739,32743,32753,32852,32882,32895,32899,32991,32995,33196,33199,33301,33306,33310,33386,33393,33397,33428,33432,33473,33475,33479,33482,33513,33517,33587,33604,33608,33614,33644,33650,33654,33658,33687,33691,33708,33712,33747,33751,33754,33775,33778,33801,33804,33840,33844,33959,33962,33966,34009,34013,34076,34078,34085,34088,34118,34125,34131,34490,34516,34520,34523,34600,34603,34713,34720,34724,34809,34812,34816,34829,34833,34867,34869,34876,34879,34912,34919,34925,34960,34969,35011,35014,35020,35026,35224,35227,35255,35260,35283,35293,35300,35309,35314,35331,35334,35339,35343,35391,35395,35415,35419,35464,35466,35473,35476,35503,35507,35586,35590,35648,35652,35711,35716,35740,35744,35754,35758,35795,35797,35804,35807,35901,35912,35933,35983,35994,35998,36033,36037,36047,36117,36121,36126,36228,36236,36243,36250,36345,36358,36365,36369,36411,36418,36434,36556,36560,36641,36644,36648,36690,36694,36738,36740,36744,36747,36835,36842,36919,36952,36956,36963,37078,37110,37114,37118,37159,37168,37172,37176,37185,37189,37198,37209,37220,37224,37232,37236,37248,37259,37271,37275,37378,37392,37396,37471,37474,37478,37515,37519,37562,37564,37568,37571,37634,37638,37685,37698,37710,37714,37718,37916,37921,37925,38071,38080,38089,38093,38127,38130,38134,38142,38274,38278,38314,38318,38325,38339,38342,38344,38348,38351,38372,38376,38433,38437,38490,38494,38507,38511,38518,38520,38524,38527,38550,38554,38576,38583,38587,38592,38638,38641,38670,38674,38718,38722,38789,38792,38796,38825,38829,38859,38861,38865,38868,38895,38899,38901,38906,38933,38942,38945,38951,38957,38968,38970,38994,38998,39046,39053,39057,39067,39071,39075,39095,39099,39113,39117,39125,39130,39206,39209,39263,39266,39269,39299,39308,39312,39322,39326,39369,39373,39423],[11,30091,30087],{"id":30092},"_2026-06-02-wbs-3-트랙-분리-로그인-ux고객사-id-제거-loginid-전역-unique-휴대폰-otp-토스트-가시성-nice-통합인증-인프라",[76,30094,10636],{"id":10635},[18,30096,30097,30098,30101,30102,51,30105,30108,30109,51,30112,30115,30116,30119,30120,30123,30124,4536,30126,30128,30129,30132,30133,51,30136,30139,30140,30143,30144,30147,30148,30150,30151,30154,30155,30157,30158,30161],{},"이번주 회원·인증 트랙 첫 날 5건 처리. ",[21,30099,30100],{},"(§1)"," WBS 3 트랙 분리(5-3A UI \u002F 5-3M 매트릭스 \u002F 5-3C 연동) — 진척 과대평가 문제 해소, Step 5 55%→40%. ",[21,30103,30104],{},"(§2)",[32,30106,30107],{},"POST \u002Fauth\u002Flogin-by-email"," 신설 + 로그인 화면 \"고객사 ID\" 필드 완전 제거 (Workers #10 \u002F Pages #52). ",[21,30110,30111],{},"(§3)",[32,30113,30114],{},"TB_USER.loginid"," 전역 UNIQUE 정합화 — ",[32,30117,30118],{},"0003"," 라이브 적용, 복수 매치 경로 + 회사 선택 UI 제거 (Workers #11 \u002F Pages #53). ",[21,30121,30122],{},"(§4)"," 휴대폰 OTP 라우트 (",[32,30125,29991],{},[32,30127,29440],{},") + signup.vue Step 4 SMS OTP 연동 + 로그인 401 처리 정합화(",[32,30130,30131],{},"\u002Fauth\u002F*"," 호출은 자동 리다이렉트 안 함) + 회원가입 완료 화면 고객사 ID 노출 제거 + 토스트 위치(오른쪽 위) + 크기 강화(17px). NHN_MOCK secret 적용 — 자격증명 발급 전 mock 통과. (Workers #12 \u002F Pages #54~#58). ",[21,30134,30135],{},"(§5)",[21,30137,30138],{},"NICE 통합인증(휴대폰 본인확인)"," 인프라 — ",[32,30141,30142],{},"doc\u002FNICE_AUTH.md"," 신규 정본 + ",[32,30145,30146],{},"0004_user_nice_auth.sql"," 라이브 적용(TB_NICE_AUTH + TB_USER에 ci\u002Fbirthdate\u002Fgender\u002Fnational_info\u002Fmobile_co + UNIQUE ci) + NICE 어댑터(mock\u002Freal, AES-256-GCM + PBKDF2 + HMAC) + 3 라우트(init\u002Fcallback\u002Fstatus) + ",[32,30149,23258],{}," 확장(niceSession 검증·CI 중복 차단·NICE 결과로 이름·휴대폰·생년월일 덮어쓰기) + signup.vue Step 4 통째로 NICE 흐름으로 교체(\"본인 인증하기\" 버튼 + 폴링 + 결과 표시) + NICE_MOCK secret 적용. (Workers #13 \u002F Pages #60). 라이브 e2e 모두 통과. ",[21,30152,30153],{},"이메일 인증창"," 차단 UX 버그 발견·수정: useApi 401 핸들러가 모든 401을 \u002Flogin으로 리다이렉트해서 가입 도중 OTP 잘못 입력하면 페이지 이동되던 문제 → ",[32,30156,30131],{}," 호출의 401은 호출자가 처리하도록 분리. ",[21,30159,30160],{},"NHN 자격증명 미등록",": 메일 실 발송 0 — 가입 흐름은 NHN_MOCK + NICE_MOCK secret 켜진 mock 모드로 통과.",[73,30163],{},[11,30165,30167],{"id":30166},"_1-wbs-구조-개편-사용자단을-3-트랙ui-api-연동으로-분리-배포-51","§1. WBS 구조 개편 — 사용자단을 **3 트랙(UI \u002F API \u002F 연동)**으로 분리 (배포 #51)",[76,30169,27637],{"id":27636},[18,30171,30172,30173,30176,30177,30180,30181,30184,30185,4339,30187,30189,30190,5606],{},"\"화면 UI는 그렸지만 백엔드 연동은 안 됐는데 ✅로 표시돼 진척이 과대평가되는\" 문제를 해소. WBS의 5-3을 ",[21,30174,30175],{},"5-3A 화면 UI 구성","(목업 데이터로 페이지만 그리기) + ",[21,30178,30179],{},"5-3M 매트릭스","(도메인별 UI\u002FAPI\u002F연동 한눈에) + ",[21,30182,30183],{},"5-3C 화면 ↔ API 연동","(실 데이터 흐름) 3 트랙으로 분리. 5-3-15의 단일 \"백엔드 연동\" 항목을 16개 도메인별 5-3C-1~16으로 펼침(인증·계정 + 이메일 OTP 2개만 ✅, 나머지 14개 ⚪). 5-2 API 항목들은 그대로 두되 5-3M 매트릭스에서 도메인 단위로 매핑. Step 5 진척률을 55% → 40%로 재산정(연동 트랙 약 7%만 완료 반영) → 전체 가중평균 45% → 38%. ",[32,30186,42],{},[32,30188,26889],{}," 양쪽 동기, Pages 배포 #51(alias ",[32,30191,30192],{},"bca573ce.malgn-noti.pages.dev",[76,30194,30196],{"id":30195},"_11-문제","1.1 문제",[18,30198,30199],{},"5-3 항목들의 ✅는 사실상 모두 \"UI 화면을 목업 데이터로 그렸다\" 단계까지를 의미했는데, WBS만 보면 \"발송·이력·주소록 등이 모두 완료\"처럼 보였다. 6\u002F1 (어제 history.20260601.md) §4·§5에서 \"인증·계정만 실 API 연동 완료\"로 5-3-15(백엔드 연동)를 추가했지만, 도메인별 단위가 아니라 한 항목으로 묶여 있어 어디까지 됐고 어디가 안 됐는지가 가시화되지 않음.",[76,30201,30203],{"id":30202},"_12-해결-3-트랙-분리","1.2 해결 — 3 트랙 분리",[101,30205,30206,30221],{},[104,30207,30208],{},[107,30209,30210,30213,30216,30218],{},[110,30211,30212],{},"트랙",[110,30214,30215],{},"항목 ID",[110,30217,4629],{},[110,30219,30220],{},"✅의 기준",[126,30222,30223,30241,30260],{},[107,30224,30225,30230,30235,30238],{},[131,30226,30227],{},[21,30228,30229],{},"A. 화면 UI 구성",[131,30231,30232],{},[32,30233,30234],{},"5-3A-*",[131,30236,30237],{},"목업 데이터로 페이지 그리기",[131,30239,30240],{},"라우트가 라이브, 화면이 렌더링",[107,30242,30243,30248,30254,30257],{},[131,30244,30245],{},[21,30246,30247],{},"B. API 엔드포인트",[131,30249,30250,30253],{},[32,30251,30252],{},"5-2-*"," (기존)",[131,30255,30256],{},"백엔드 라우트 구현",[131,30258,30259],{},"라우트가 라이브, e2e 검증",[107,30261,30262,30267,30272,30275],{},[131,30263,30264],{},[21,30265,30266],{},"C. 화면 ↔ API 연동",[131,30268,30269,29019],{},[32,30270,30271],{},"5-3C-*",[131,30273,30274],{},"실 데이터 흐름 + 상태 관리 + 에러 처리",[131,30276,30277],{},"UI가 실 API를 호출, 응답이 화면에 반영",[18,30279,30280,30281,30283],{},"추가로 ",[21,30282,30179],{}," — 각 도메인을 한 행에 UI\u002FAPI\u002F연동 3 칸으로 정렬해 어디까지 됐는지 한눈에. 25 도메인 × 3 트랙 = 75 칸.",[76,30285,30287],{"id":30286},"_13-5-3c-펼침-16-항목","1.3 5-3C 펼침 (16 항목)",[18,30289,30290],{},"5-3-15 단일 항목 → 도메인별 16 항목:",[81,30292,30293,30296],{},[84,30294,30295],{},"✅ 2개: 5-3C-1 (인증·계정), 5-3C-1a (이메일 OTP)",[84,30297,30298,30299,30301],{},"⚪ 14개: 로그아웃·비밀번호 재설정·login-by-email·약관 동의·companyType·",[32,30300,3987],{}," 갱신·비밀번호 변경·2FA·멀티 계정·계약·발송 6채널·이력\u002F통계·주소록 등 CRUD·결제·문의",[18,30303,30304,30305,30309],{},"우선순위는 ",[26,30306,30308],{"href":30307},"..\u002FMEMBERSHIP","doc\u002FMEMBERSHIP.md"," §8과 일치 — P0 3, P1 4, P2 4, P3 3.",[76,30311,30313],{"id":30312},"_14-진척률-재산정","1.4 진척률 재산정",[101,30315,30316,30332],{},[104,30317,30318],{},[107,30319,30320,30323,30326,30329],{},[110,30321,30322],{},"Step",[110,30324,30325],{},"기존",[110,30327,30328],{},"재산정",[110,30330,30331],{},"사유",[126,30333,30334,30346,30357,30368,30379,30398],{},[107,30335,30336,30339,30341,30343],{},[131,30337,30338],{},"1 준비",[131,30340,143],{},[131,30342,143],{},[131,30344,30345],{},"변동 없음",[107,30347,30348,30351,30353,30355],{},[131,30349,30350],{},"2 정책",[131,30352,143],{},[131,30354,143],{},[131,30356,30345],{},[107,30358,30359,30362,30364,30366],{},[131,30360,30361],{},"3 기획",[131,30363,184],{},[131,30365,184],{},[131,30367,30345],{},[107,30369,30370,30373,30375,30377],{},[131,30371,30372],{},"4 디자인",[131,30374,179],{},[131,30376,179],{},[131,30378,30345],{},[107,30380,30381,30386,30390,30395],{},[131,30382,30383],{},[21,30384,30385],{},"5 개발",[131,30387,30388],{},[21,30389,143],{},[131,30391,30392],{},[21,30393,30394],{},"40%",[131,30396,30397],{},"UI(거의 완료) + API(60%) + 연동(7%)을 가중평균. UI는 7주의 작업이고 연동·API가 더 큰 비중을 차지하므로 단순 평균",[107,30399,30400,30405,30409,30414],{},[131,30401,30402],{},[21,30403,30404],{},"전체 가중평균",[131,30406,30407],{},[21,30408,219],{},[131,30410,30411],{},[21,30412,30413],{},"38%",[131,30415,30416],{},[32,30417,30418],{},"0.10×55 + 0.15×55 + 0.20×35 + 0.10×20 + 0.45×40 ≈ 37.75",[76,30420,30422],{"id":30421},"_15-산출물","1.5 산출물",[81,30424,30425,30430,30435],{},[84,30426,30427,30429],{},[26,30428,42],{"href":27064}," — 5-3 섹션 전면 개편 (5-3A·5-3M·5-3C). 진척률 스냅샷 갱신. 가중평균 45→38.",[84,30431,30432,30434],{},[26,30433,26889],{"href":27139}," — group 라벨 '사용자단 화면' → '사용자단 화면 UI (목업)', 5-3-15 삭제 + 5-3C-* 16 신규, stage-5 progress 55→40 + summary 갱신.",[84,30436,30437,30438,30440],{},"Pages 배포 #51 (alias ",[32,30439,30192],{},"). 라이브 그렙으로 17개 5-3C 항목 + 새 그룹 라벨 2종 노출 확인.",[76,30442,30444],{"id":30443},"_16-다음-작업-이번주-회원인증-트랙","1.6 다음 작업 (이번주 회원·인증 트랙)",[18,30446,30447,30450],{},[26,30448,30449],{"href":30307},"MEMBERSHIP.md"," §8 P0 3건 + P1 4건이 이번주 본격 작업. 가장 빠른 영향 순:",[6674,30452,30453,30459,30465,30473,30479],{},[84,30454,30455,30458],{},[21,30456,30457],{},"5-3C-2 로그아웃 GNB 실 연결"," (30분, 의존 0)",[84,30460,30461,30464],{},[21,30462,30463],{},"5-3C-3 비밀번호 재설정"," (2~3시간, OTP 인프라 재활용)",[84,30466,30467,30472],{},[21,30468,30469,30470],{},"5-3C-4 ",[32,30471,29363],{}," (1~2시간, companyId UX 개선)",[84,30474,30475,30478],{},[21,30476,30477],{},"5-3C-5 약관 동의 적재"," (1~2시간)",[84,30480,30481,30488],{},[21,30482,30483,30484,30487],{},"5-3C-6 ",[32,30485,30486],{},"companyType"," 전달·저장"," (2~3시간) + 5-3C-6 따라가는 개인 유형 화면 분기 (30분)",[73,30490],{},[11,30492,30494,30495,30497],{"id":30493},"_2-로그인-ux-개선-post-authlogin-by-email-고객사-id-필드-제거-배포-1052","§2. 로그인 UX 개선 — ",[32,30496,30107],{}," + 고객사 ID 필드 제거 (배포 #10·#52)",[76,30499,27637],{"id":28262},[18,30501,30502,30503,30505,30506,30508,30509,30512,30513,13735,30515,30518,30519,30521,30522,30525,30526,5606],{},"(어제) §4의 알려진 한계(\"로그인이 ",[32,30504,22750],{},"를 요구해 사용자가 자신의 회사 ID를 외워야 함\")를 해소. 백엔드 ",[32,30507,30107],{}," 신설 — 이메일(또는 아이디) + 비밀번호만으로 회사 자동 찾기, 단일 매치 시 즉시 토큰 발급, 같은 이메일로 여러 회사에 가입된 경우 ",[32,30510,30511],{},"multipleCompanies: true + companies[]"," 반환. 프런트 ",[32,30514,14160],{},[21,30516,30517],{},"고객사 ID 필드를 완전히 제거",", 복수 매치 시 회사 선택 카드 UI 노출 → 선택 시 기존 ",[32,30520,23261],{},"으로 명시적 로그인. 라이브 e2e 5 시나리오 통과(단일\u002F복수\u002F잘못된 비번\u002F없는 이메일\u002F같은 이메일 2회사). Workers 배포 #10(Version ",[32,30523,30524],{},"a6197cc7-0f01-4612-aa10-5271f7c494a1","), Pages 배포 #52(alias ",[32,30527,30528],{},"292da05d.malgn-noti.pages.dev",[76,30530,30532,30533],{"id":30531},"_21-백엔드-post-authlogin-by-email","2.1 백엔드 — ",[32,30534,30107],{},[18,30536,30537,9148],{},[32,30538,22961],{},[81,30540,30541,30558,30565,30568,30585,30594],{},[84,30542,20153,30543,89,30546,30548,30549,26162,30551,30553,30554,30557],{},[32,30544,30545],{},"{email, password}",[32,30547,12969],{}," 필드명이지만 실제로는 ",[32,30550,20087],{},[32,30552,12969],{}," 컬럼 매치 (회원가입 마법사가 ",[32,30555,30556],{},"loginid = email","로 발급하므로 둘 다 검색)",[84,30559,30560,30561,30564],{},"검색: ",[32,30562,30563],{},"WHERE user.status=1 AND company.status=1 AND (user.loginid = ? OR user.email = ?)"," + INNER JOIN company",[84,30566,30567],{},"각 row별로 PBKDF2 비번 검증 (서로 다른 회사·다른 비밀번호 가능)",[84,30569,30570,30573,30574,30576,30577,30580,30581,30584],{},[21,30571,30572],{},"단일 매치",": 기존 ",[32,30575,23261],{},"과 동일 형식의 ",[32,30578,30579],{},"AuthResponse"," 반환 + 토큰 발급 + ",[32,30582,30583],{},"lastLoginAt"," 갱신",[84,30586,30587,47,30590,30593],{},[21,30588,30589],{},"복수 매치",[32,30591,30592],{},"{multipleCompanies: true, companies: [{id, name}, ...]}"," 반환 (토큰 발급 안 함)",[84,30595,30596,30599,30600,30602],{},[21,30597,30598],{},"매치 0 또는 비번 모두 불일치",": 401 ",[32,30601,22430],{}," (계정 enumeration 방지)",[18,30604,30605,30606,4473,30609,30612,30613,30616],{},"OpenAPI: 신규 path 1 + 신규 schema 2(",[32,30607,30608],{},"LoginByEmailRequest",[32,30610,30611],{},"MultipleCompaniesResponse","). 응답 schema는 ",[32,30614,30615],{},"oneOf: [AuthResponse, MultipleCompaniesResponse]"," — 두 가지 가능 형태 명시.",[76,30618,30620,30621,30623],{"id":30619},"_22-프런트-loginindexvue-개편","2.2 프런트 — ",[32,30622,14160],{}," 개편",[81,30625,30626,30645,30664,30680,30686],{},[84,30627,30628,30631,30632,4536,30635,4536,30638,30641,30642,30644],{},[21,30629,30630],{},"고객사 ID 필드 완전 제거",". 5\u002F27 §12에서 도입한 ",[32,30633,30634],{},"companyIdInput",[32,30636,30637],{},"needCompanyId",[32,30639,30640],{},"effectiveCompanyId"," 로직 모두 삭제. ",[32,30643,28742],{}," 쿠키도 더 이상 로그인 폼에서 사용하지 않음(다만 인증 후 hydrateFromAuth에서 갱신은 유지 — 이전 가입 흔적 보존).",[84,30646,30647,30652,30653,30656,30657,30660,30661,30663],{},[21,30648,30649],{},[32,30650,30651],{},"stores\u002Fauth.ts.loginByEmail()"," 액션 신규 — 반환값 ",[32,30654,30655],{},"null"," = 단일 매치 (로그인 완료) \u002F ",[32,30658,30659],{},"{id,name}[]"," = 복수 매치 (호출자가 회사 선택 후 ",[32,30662,28973],{}," 재호출).",[84,30665,30666,47,30669,30672,30673,30676,30677,30679],{},[21,30667,30668],{},"복수 매치 UI",[32,30670,30671],{},"companyChoices"," ref가 비어있지 않으면 일반 폼 대신 회사 선택 카드 리스트 노출. 카드 클릭 시 ",[32,30674,30675],{},"chooseCompany(companyId)"," → 기존 ",[32,30678,28973],{}," 호출. \"다시 입력\" 버튼으로 초기 폼 복귀.",[84,30681,30682,30685],{},[21,30683,30684],{},"에러 처리",": 401 응답 → \"아이디 또는 비밀번호가 올바르지 않습니다.\" 토스트. 그 외 → \"로그인 중 오류가 발생했습니다.\"",[84,30687,30688,30691,30692,30695],{},[21,30689,30690],{},"이메일 placeholder",": \"아이디를 입력해 주세요\" → \"가입 시 사용한 이메일을 입력해 주세요\" + ",[32,30693,30694],{},"inputmode=\"email\""," 힌트.",[76,30697,30699],{"id":30698},"_23-라이브-e2e-production","2.3 라이브 e2e (Production)",[101,30701,30702,30712],{},[104,30703,30704],{},[107,30705,30706,30708,30710],{},[110,30707,3846],{},[110,30709,29790],{},[110,30711,22362],{},[126,30713,30714,30723,30732,30743,30752,30764],{},[107,30715,30716,30718,30721],{},[131,30717,4636],{},[131,30719,30720],{},"signup → company.id=12 발급",[131,30722,3869],{},[107,30724,30725,30727,30730],{},[131,30726,4649],{},[131,30728,30729],{},"login-by-email 단일 매치 → 200 + token (169자)",[131,30731,3869],{},[107,30733,30734,30736,30741],{},[131,30735,4662],{},[131,30737,30738,30739],{},"잘못된 비밀번호 → 401 ",[32,30740,22430],{},[131,30742,3869],{},[107,30744,30745,30747,30750],{},[131,30746,4678],{},[131,30748,30749],{},"존재하지 않는 이메일 → 401 (계정 enumeration 방지)",[131,30751,3869],{},[107,30753,30754,30756,30762],{},[131,30755,4691],{},[131,30757,30758,30759],{},"같은 이메일로 2번째 회사 signup → login-by-email → ",[32,30760,30761],{},"{multipleCompanies:true, companies:[{id:12,name:...}, {id:13,name:...}]}",[131,30763,3869],{},[107,30765,30766,30768,30773],{},[131,30767,4708],{},[131,30769,21519,30770,30772],{},[32,30771,9335],{}," 페이지 그렙 — \"고객사 ID\" 0건 \u002F \"가입 시 사용한 이메일\" 1건",[131,30774,3869],{},[18,30776,30777],{},"검증 과정의 임시 계정(company.id 12·13) 2건은 SG 재개방 시 cleanup 예정.",[76,30779,30781],{"id":30780},"_24-산출물","2.4 산출물",[81,30783,30784,30796,30805,30815],{},[84,30785,30786,30787,30789,30790,30792,30793,30795],{},"API: 3 파일 수정 — ",[32,30788,22961],{},"(+85) · ",[32,30791,22139],{},"(+25) · ",[32,30794,21654],{}," (변동 없음 — verification 정의는 §5에서 이미 반영).",[84,30797,30798,30799,30801,30802,30804],{},"사용자단: 2 파일 수정 — ",[32,30800,28955],{},"(+20) · ",[32,30803,17601],{},"(전면 개편, +90\u002F-30).",[84,30806,30807,30808,30811,30812,4703],{},"Workers 배포 #10 Version ",[32,30809,30810],{},"a6197cc7...",", Pages 배포 #52 alias ",[32,30813,30814],{},"292da05d",[84,30816,30817],{},"WBS 5-3C-4 ⚪ → ✅. doc\u002FMEMBERSHIP.md §8 P0 #3 완료(로그아웃·재설정 다음).",[76,30819,30821],{"id":30820},"_25-보안-노트","2.5 보안 노트",[81,30823,30824,30834,30840],{},[84,30825,30826,47,30829,26162,30831,30833],{},[21,30827,30828],{},"로그인 가능한 입력",[32,30830,20087],{},[32,30832,12969],{}," 컬럼 매치. 같은 사용자가 두 컬럼에 다른 값을 가질 수 있다면(현재 회원가입 마법사는 둘 다 email로 채움) 둘 다로 로그인 가능. 운영상 의도된 동작.",[84,30835,30836,30839],{},[21,30837,30838],{},"enumeration 방지",": 잘못된 이메일·잘못된 비밀번호 모두 동일한 401 메시지(\"Authentication required\") — 응답 내용으로 이메일 존재 여부를 알 수 없음.",[84,30841,30842,30845],{},[21,30843,30844],{},"타이밍",": 매치 row 수만큼 PBKDF2를 돌리므로 row 수가 많으면 응답 시간이 살짝 길어짐. 복수 매치는 실제로는 드물지만, 한 이메일을 의도적으로 많이 등록해 DoS 가능. 후속 rate limit 작업과 함께 검토.",[76,30847,30849],{"id":30848},"_26-알려진-한계","2.6 알려진 한계",[81,30851,30852,30864],{},[84,30853,30854,30859,30860,30863],{},[21,30855,30856,30858],{},[32,30857,28742],{}," 쿠키 잔존",": 더 이상 로그인 폼에서 사용하지 않으나, ",[32,30861,30862],{},"hydrateFromAuth","에서 여전히 갱신. 후속에서 제거 또는 다른 용도로 활용 검토.",[84,30865,30866,30869],{},[21,30867,30868],{},"회원가입에서 loginid ≠ email로 가입한 사용자",": 현재 마법사 외 경로(예: 운영자단 강제 가입)로 만들어진 사용자는 이메일이 비어 있을 수 있어 login-by-email로 로그인 불가. 운영자단 흐름이 생기면 정책 정의 필요.",[73,30871],{},[11,30873,28255,30875,30877],{"id":30874},"_3-tb_userloginid-전역-unique-정책-정합화-배포-1153",[32,30876,30114],{}," 전역 UNIQUE — 정책 정합화 (배포 #11·#53)",[76,30879,27637],{"id":28712},[18,30881,30882,30883,30886,30887,30890,30891,30894,30895,30898,30899,30902,30903,30906,30907,30910,30911,30913,30914,30916,30917,30919,30920,30922],{},"(§2에서) 도입한 ",[32,30884,30885],{},"login-by-email","의 \"복수 매치\" 경로는 사실 ",[32,30888,30889],{},"UNIQUE (company_id, loginid)"," 복합 제약 때문에 같은 loginid가 회사별로 따로 존재할 수 있다는 가정에서 나왔는데, 사용자 정책 결정으로 ",[21,30892,30893],{},"loginid는 회사와 무관하게 전체 시스템에서 유일해야 함","으로 정리. DDL 마이그레이션 ",[32,30896,30897],{},"0003_user_loginid_global_unique.sql"," 라이브 적용(",[32,30900,30901],{},"uq_user_company_loginid"," DROP → ",[32,30904,30905],{},"uq_user_loginid"," ADD), schema.ts에 ",[32,30908,30909],{},".unique('uq_user_loginid')"," 명시, 백엔드 ",[32,30912,29363],{},"의 복수 매치 분기 제거, OpenAPI에서 ",[32,30915,30611],{}," 스키마 삭제, 프런트 ",[32,30918,30651],{}," 반환 타입 단순화 + ",[32,30921,14160],{},"에서 회사 선택 카드 UI 80여 라인 제거. 라이브 e2e 4 시나리오 통과(signup 정상 \u002F 같은 loginid 재시도 409 \u002F login-by-email 단일 토큰 \u002F multipleCompanies 응답 사라짐). 사전 테스트 데이터 cleanup 8개 회사 + 12개 사용자(어제 검증용 임시 계정).",[76,30924,30926],{"id":30925},"_31-정책-변경","3.1 정책 변경",[101,30928,30929,30941],{},[104,30930,30931],{},[107,30932,30933,30935,30938],{},[110,30934,6889],{},[110,30936,30937],{},"변경 전",[110,30939,30940],{},"변경 후",[126,30942,30943,30960,30974,30989],{},[107,30944,30945,30948,30954],{},[131,30946,30947],{},"TB_USER UNIQUE",[131,30949,30950,30953],{},[32,30951,30952],{},"(company_id, loginid)"," 복합",[131,30955,30956,30959],{},[32,30957,30958],{},"(loginid)"," 단독",[107,30961,30962,30965,30968],{},[131,30963,30964],{},"같은 이메일로 여러 회사 가입",[131,30966,30967],{},"가능",[131,30969,30970,30973],{},[21,30971,30972],{},"불가"," — signup 시 409 conflict",[107,30975,30976,30981,30984],{},[131,30977,30978,30980],{},[32,30979,30885],{}," 응답 분기",[131,30982,30983],{},"단일\u002F복수",[131,30985,30986],{},[21,30987,30988],{},"단일만",[107,30990,30991,30994,30997],{},[131,30992,30993],{},"회사 선택 UI",[131,30995,30996],{},"복수 매치 시 카드 리스트",[131,30998,30999],{},[21,31000,17434],{},[18,31002,31003],{},"이로써 \"한 이메일 = 한 회사 = 한 로그인\"이 보장됨. 멀티 계정(주계정·보조계정)은 같은 회사 내 다른 loginid로 처리.",[76,31005,31007],{"id":31006},"_32-ddl-마이그레이션-라이브-적용-완료","3.2 DDL 마이그레이션 (라이브 적용 완료)",[18,31009,31010,9148],{},[26,31011,31013],{"href":31012},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0003_user_loginid_global_unique.sql","src\u002Fdb\u002Fmigrations\u002F0003_user_loginid_global_unique.sql",[5251,31015,31019],{"className":31016,"code":31017,"language":31018,"meta":4208,"style":4208},"language-sql shiki shiki-themes github-light github-dark","ALTER TABLE TB_USER\n  DROP INDEX uq_user_company_loginid,\n  ADD UNIQUE KEY uq_user_loginid (loginid);\n","sql",[32,31020,31021,31026,31031],{"__ignoreMap":4208},[7968,31022,31023],{"class":5110,"line":7970},[7968,31024,31025],{},"ALTER TABLE TB_USER\n",[7968,31027,31028],{"class":5110,"line":4212},[7968,31029,31030],{},"  DROP INDEX uq_user_company_loginid,\n",[7968,31032,31033],{"class":5110,"line":4209},[7968,31034,31035],{},"  ADD UNIQUE KEY uq_user_loginid (loginid);\n",[237,31037,31039],{"id":31038},"적용-순서-sg-열린-짧은-윈도우-활용","적용 순서 (SG 열린 짧은 윈도우 활용)",[6674,31041,31042,31065,31070],{},[84,31043,31044,31047,31048],{},[21,31045,31046],{},"사전 cleanup"," — 어제부터 누적된 검증용 임시 계정 정리:\n",[81,31049,31050,31055,31060],{},[84,31051,31052,31054],{},[32,31053,20302],{}," 6 → 4 (lbe 중복 2건 + hd-check + ddl 등)",[84,31056,31057,31059],{},[32,31058,28064],{}," 그에 맞춰 정리",[84,31061,31062,31064],{},[32,31063,29386],{}," 0건",[84,31066,31067,31069],{},[21,31068,24245],{}," — mysql CLI 직결로 ALTER 실행, exit=0",[84,31071,31072,9194,31075],{},[21,31073,31074],{},"사후 검증",[81,31076,31077,31084],{},[84,31078,31079,31080,31083],{},"인덱스 확인: ",[32,31081,31082],{},"uq_user_loginid (loginid)"," 단독 노출",[84,31085,31086,31087,31090],{},"중복 INSERT 시도: ",[32,31088,31089],{},"Duplicate entry … for key 'TB_USER.uq_user_loginid'"," 1062 에러 → ✅ 동작",[76,31092,31094],{"id":31093},"_33-코드-변경-백엔드","3.3 코드 변경 (백엔드)",[101,31096,31097,31105],{},[104,31098,31099],{},[107,31100,31101,31103],{},[110,31102,28918],{},[110,31104,7525],{},[126,31106,31107,31120,31140],{},[107,31108,31109,31113],{},[131,31110,31111],{},[26,31112,21654],{"href":29606},[131,31114,31115,31116,31119],{},"TB_USER 정의에 ",[32,31117,31118],{},"loginid: varchar(...).notNull().unique('uq_user_loginid')"," 추가 + 헤더 코멘트",[107,31121,31122,31126],{},[131,31123,31124],{},[26,31125,22961],{"href":29639},[131,31127,31128,31131,31132,31135,31136,31139],{},[32,31129,31130],{},"\u002Flogin-by-email"," 단순화 — ",[32,31133,31134],{},"for of"," 다중 verify 루프 → ",[32,31137,31138],{},".limit(1)"," 단일 select + 단일 password check. 복수 매치 분기 + multipleCompanies 응답 코드 삭제",[107,31141,31142,31146],{},[131,31143,31144],{},[26,31145,22139],{"href":29675},[131,31147,31148,31150,31151,31153,31154,31157,31158,31160],{},[32,31149,30611],{}," 스키마 삭제. ",[32,31152,31130],{}," 응답 ",[32,31155,31156],{},"oneOf"," → 단일 ",[32,31159,30579],{},"로 단순화. 설명 갱신(\"loginid 전역 UNIQUE — 최대 1건 매치\").",[76,31162,31164],{"id":31163},"_34-코드-변경-프런트","3.4 코드 변경 (프런트)",[101,31166,31167,31175],{},[104,31168,31169],{},[107,31170,31171,31173],{},[110,31172,28918],{},[110,31174,7525],{},[126,31176,31177,31191],{},[107,31178,31179,31183],{},[131,31180,31181],{},[26,31182,28955],{"href":29322},[131,31184,31185,31188,31189],{},[32,31186,31187],{},"loginByEmail()"," 반환 타입 `Promise\u003CCompany",[7968,31190],{},[107,31192,31193,31197],{},[131,31194,31195],{},[26,31196,17601],{"href":17600},[131,31198,31199,31201,31202,31205,31206,7505,31209,31212,31213,4536,31216,4536,31219,4536,31222,4536,31225,4536,31228,31231],{},[32,31200,30671],{}," ref \u002F ",[32,31203,31204],{},"showCompanyPicker"," computed \u002F ",[32,31207,31208],{},"chooseCompany()",[32,31210,31211],{},"cancelCompanyPick()"," 함수 + 회사 선택 카드 템플릿 + 관련 스타일 (",[32,31214,31215],{},".picker-desc",[32,31217,31218],{},".company-list",[32,31220,31221],{},".company-card",[32,31223,31224],{},".company-name",[32,31226,31227],{},".company-id",[32,31229,31230],{},".company-arrow",") 모두 삭제. 화면은 단일 폼만.",[76,31233,31235],{"id":31234},"_35-라이브-e2e-production","3.5 라이브 e2e (Production)",[101,31237,31238,31248],{},[104,31239,31240],{},[107,31241,31242,31244,31246],{},[110,31243,3846],{},[110,31245,29790],{},[110,31247,22362],{},[126,31249,31250,31263,31276,31289],{},[107,31251,31252,31254,31261],{},[131,31253,4636],{},[131,31255,31256,31257,31260],{},"signup → ",[32,31258,31259],{},"{user, company, token}"," 정상 (company.id=14, user.id=16)",[131,31262,3869],{},[107,31264,31265,31267,31274],{},[131,31266,4649],{},[131,31268,31269,31270,31273],{},"같은 loginid로 두 번째 signup → 409 ",[32,31271,31272],{},"conflict"," \"loginid \"…\" 이미 사용 중\"",[131,31275,3869],{},[107,31277,31278,31280,31287],{},[131,31279,4662],{},[131,31281,31282,31283,31286],{},"login-by-email 정상 매치 → 200 + 단일 토큰. 응답에 ",[32,31284,31285],{},"multipleCompanies"," 키 없음",[131,31288,3869],{},[107,31290,31291,31293,31298],{},[131,31292,4678],{},[131,31294,31295,31296,30959],{},"cleanup 후 인덱스 확인 — UNIQUE 인덱스 ",[32,31297,31082],{},[131,31299,3869],{},[76,31301,31303],{"id":31302},"_36-산출물","3.6 산출물",[81,31305,31306,31315,31324,31332],{},[84,31307,31308,47,31311,31314],{},[21,31309,31310],{},"DDL",[32,31312,31313],{},"malgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0003_user_loginid_global_unique.sql"," 신규 + 라이브 적용",[84,31316,31317,31320,31321],{},[21,31318,31319],{},"API",": 3 파일 수정 — schema.ts · auth.ts · openapi.ts. Workers 배포 #11 Version ",[32,31322,31323],{},"f7f42855-1d40-4397-9405-df8bfa8124ee",[84,31325,31326,31328,31329],{},[21,31327,680],{},": 2 파일 수정 — stores\u002Fauth.ts(-25) · login\u002Findex.vue(-80). Pages 배포 #53 alias ",[32,31330,31331],{},"f150ea0a.malgn-noti.pages.dev",[84,31333,31334,31337],{},[21,31335,31336],{},"데이터 정리",": 어제~오늘 누적된 검증용 임시 회사 8 + 사용자 12 + verification 미소비분 cleanup",[76,31339,31341],{"id":31340},"_37-영향-분석-다른-코드에-미치는-영향","3.7 영향 분석 — 다른 코드에 미치는 영향",[101,31343,31344,31353],{},[104,31345,31346],{},[107,31347,31348,31350],{},[110,31349,6889],{},[110,31351,31352],{},"영향",[126,31354,31355,31365,31374,31382,31390],{},[107,31356,31357,31362],{},[131,31358,3830,31359,31361],{},[32,31360,23261],{}," (companyId+loginid)",[131,31363,31364],{},"그대로 동작 — companyId가 제약을 더 좁히지만 결과는 같음",[107,31366,31367,31371],{},[131,31368,31369],{},[32,31370,23258],{},[131,31372,31373],{},"catch 블록의 \"Duplicate entry\" 메시지 매핑 그대로 (에러 메시지 자체가 회사·loginid 어느 키든 같은 형태)",[107,31375,31376,31379],{},[131,31377,31378],{},"멀티계정(주·보조 사용자)",[131,31380,31381],{},"같은 회사 내에서 서로 다른 loginid를 사용 — 영향 없음",[107,31383,31384,31387],{},[131,31385,31386],{},"운영자단 강제 가입",[131,31388,31389],{},"미구현 — 정책 정의 시 전역 UNIQUE 전제로 시작",[107,31391,31392,31395],{},[131,31393,31394],{},"OTP \u002F 비밀번호 재설정",[131,31396,31397],{},"email\u002Floginid 기반 lookup — 단일 매치 보장으로 단순화 가능 (후속)",[76,31399,31401],{"id":31400},"_38-다음-단계","3.8 다음 단계",[18,31403,31404],{},"지금 정책이 정리됐으니 다음 P0 항목들이 한층 단순해집니다:",[81,31406,31407,31414],{},[84,31408,31409,89,31411,31413],{},[21,31410,30463],{},[32,31412,12969],{},"로 lookup하면 단일 사용자 → 토큰 발급도 단순. OTP 인프라 재활용 → 2시간 이내 가능.",[84,31415,31416,31418],{},[21,31417,30457],{}," — 정책 변경과 무관, 30분.",[73,31420],{},[11,31422,31424],{"id":31423},"_4-휴대폰-sms-otp-로그인-401-처리-가입-완료-id-노출-제거-토스트-가시성-배포-12-5458","§4. 휴대폰 SMS OTP + 로그인 401 처리 + 가입 완료 ID 노출 제거 + 토스트 가시성 (배포 #12 \u002F #54~#58)",[76,31426,27637],{"id":29444},[18,31428,31429,31430,31433,31434,31437,31438,31441],{},"이메일 OTP 인프라(",[32,31431,31432],{},"(어제) §5",") 후속 — ",[21,31435,31436],{},"휴대폰 SMS OTP","를 같은 패턴으로 추가하여 signup.vue Step 4를 실 API로 일관 연결 + 가입 도중 발견된 4개 UX 이슈(401 자동 리다이렉트, 가입 완료 화면의 고객사 ID 노출, 토스트 위치, 토스트 크기) 정리. Workers 배포 #12(Version ",[32,31439,31440],{},"84056c86...","), Pages 배포 #54~#58. 자체 SMS OTP는 단순 휴대폰 보유 검증으로 유지 — 본인 확인(이름·CI 등)은 §5 NICE로 분리.",[76,31443,31445],{"id":31444},"_41-휴대폰-otp-라우트-이메일과-동일-패턴","4.1 휴대폰 OTP 라우트 — 이메일과 동일 패턴",[18,31447,31448,4339,31451,9148],{},[32,31449,31450],{},"POST \u002Fauth\u002Fphone-code\u002Fsend",[32,31452,31453],{},"POST \u002Fauth\u002Fphone-code\u002Fverify",[81,31455,31456,31467,31470,31473,31485,31495,31504,31513],{},[84,31457,31458,5069,31460,31463,31464,4343],{},[32,31459,29386],{},[32,31461,31462],{},"target_type='phone'"," 적재 (이메일은 ",[32,31465,31466],{},"'email'",[84,31468,31469],{},"SHA-256(target|purpose|code) 해시 — 평문 코드 저장 금지",[84,31471,31472],{},"TTL 10분 · 재발송 시 직전 코드 만료 · 5회 시도 제한 · 소비 후 재사용 차단",[84,31474,31475,31477,31478,7505,31480,7505,31482],{},[32,31476,15158],{}," enum 확장: ",[32,31479,4552],{},[32,31481,29554],{},[32,31483,31484],{},"change_phone",[84,31486,31487,31488,6219,31491,31494],{},"휴대폰 번호 정규화: 입력값에서 숫자만 추출(",[32,31489,31490],{},"010-1234-5678",[32,31492,31493],{},"01012345678",") — 같은 사용자의 다른 표기를 같은 코드 한 건으로 매핑",[84,31496,31497,31498,31500,31501,31503],{},"SMS 발송은 NHN SMS 어댑터 (mock\u002Freal). ",[32,31499,25204],{}," 또는 자격증명 미설정 시 mock fallback. mock 모드면 응답에 ",[32,31502,29476],{}," 노출(개발 편의)",[84,31505,31506,31509,31510,31512],{},[32,31507,31508],{},"OtpPurpose"," 타입 확장 + ",[32,31511,29653],{}," 4개 분기",[84,31514,31515,31518,31519,31522,31523,4343],{},[32,31516,31517],{},"EMAIL_FROM"," 외 ",[32,31520,31521],{},"SMS_FROM"," env var 추가 (기본 ",[32,31524,31525],{},"01000000000",[18,31527,31528,31529,4536,31532,4536,31535,5606],{},"OpenAPI 4지점 추가(2 paths + 2 schemas ",[32,31530,31531],{},"PhoneCodeSendRequest",[32,31533,31534],{},"PhoneCodeSendResponse",[32,31536,31537],{},"PhoneCodeVerifyRequest",[18,31539,31540],{},"라이브 e2e 5+1 시나리오 통과: 발송 mockCode 노출 \u002F 잘못된 코드 401 \u002F 올바른 코드 200 \u002F 소비 후 재시도 401 \u002F 하이픈 포함 입력 정규화 \u002F 이메일 OTP도 같이 회복.",[76,31542,31544],{"id":31543},"_42-프런트-signupvue-step-4-실-api-연동-nice-도입-전-중간-단계","4.2 프런트 signup.vue Step 4 — 실 API 연동 (NICE 도입 전 중간 단계)",[18,31546,31547,31548,31551],{},"기존 화면 더미(",[32,31549,31550],{},"codeSent.value=true"," 토스트만) → 실 호출:",[81,31553,31554,31576,31587],{},[84,31555,31556,6219,31559,31561,31562,31565,31566,31568,31569,7505,31571,7505,31573,4343],{},[32,31557,31558],{},"sendCode()",[32,31560,31450],{}," async + ",[32,31563,31564],{},"sendingPhone"," 로딩 + ",[32,31567,29476],{}," 응답 시 토스트 노출 + 버튼 라벨 3-상태(",[32,31570,29742],{},[32,31572,29516],{},[32,31574,31575],{},"인증번호 받기",[84,31577,31578,6219,31581,31561,31583,31586],{},[32,31579,31580],{},"confirmCode()",[32,31582,31453],{},[32,31584,31585],{},"verifyingPhone"," 로딩 + 백엔드 한국어 에러 메시지 그대로 토스트",[84,31588,31589,31592,31593,4343],{},[32,31590,31591],{},"fullPhoneE164"," computed — 하이픈 제거(",[32,31594,31493],{},[18,31596,31597,31598,31601],{},"이 작업은 §5 NICE 도입 시점에 ",[21,31599,31600],{},"다시 통째로 교체됨","(NICE가 휴대폰 인증을 대신 수행). 백엔드 휴대폰 OTP 라우트는 비밀번호 재설정·휴대폰 번호 변경 등 후속 흐름에서 그대로 재활용.",[76,31603,31605,31606,31608],{"id":31604},"_43-useapits-401-처리-분리-auth는-호출자가-처리","4.3 useApi.ts 401 처리 분리 — ",[32,31607,30131],{},"는 호출자가 처리",[18,31610,31611,31612,31614],{},"가입 중 이메일 OTP 잘못 입력 → 401 → useApi 핸들러가 ",[32,31613,9335],{},"으로 리다이렉트 → 사용자가 코드 재입력 못 함 → 가입 흐름 차단.",[18,31616,31617,31618,51,31620,9148],{},"수정 ",[26,31619,28929],{"href":29319},[32,31621,31622],{},"onResponseError",[5251,31624,31626],{"className":7962,"code":31625,"language":7964,"meta":4208,"style":4208},"const url = typeof request === 'string' ? request : (request as { url?: string }).url ?? ''\n\n\u002F\u002F \u002Fauth\u002F* 라우트의 401은 정상적인 \"잘못된 자격증명·OTP\" → 호출자가 처리해야 함\nif (url.includes('\u002Fauth\u002F')) return\n\n\u002F\u002F 인증되지 않은 상태에서 보호 라우트 호출 → 의미 있는 리다이렉트 아님\nif (!useAuthToken().value) return\n\n\u002F\u002F 인증된 상태 + 보호 라우트 401 → 토큰 만료 → \u002Flogin\n",[32,31627,31628,31679,31683,31688,31710,31714,31719,31736,31740],{"__ignoreMap":4208},[7968,31629,31630,31632,31635,31637,31640,31643,31646,31649,31652,31654,31656,31659,31661,31663,31666,31668,31670,31673,31676],{"class":5110,"line":7970},[7968,31631,9204],{"class":7973},[7968,31633,31634],{"class":8892}," url",[7968,31636,9210],{"class":7973},[7968,31638,31639],{"class":7973}," typeof",[7968,31641,31642],{"class":7984}," request ",[7968,31644,31645],{"class":7973},"===",[7968,31647,31648],{"class":7993}," 'string'",[7968,31650,31651],{"class":7973}," ?",[7968,31653,31642],{"class":7984},[7968,31655,9148],{"class":7973},[7968,31657,31658],{"class":7984}," (request ",[7968,31660,25863],{"class":7973},[7968,31662,27200],{"class":7984},[7968,31664,31665],{"class":8939},"url",[7968,31667,9160],{"class":7973},[7968,31669,9151],{"class":8892},[7968,31671,31672],{"class":7984}," }).url ",[7968,31674,31675],{"class":7973},"??",[7968,31677,31678],{"class":7993}," ''\n",[7968,31680,31681],{"class":5110,"line":4212},[7968,31682,8288],{"emptyLinePlaceholder":4259},[7968,31684,31685],{"class":5110,"line":4209},[7968,31686,31687],{"class":8398},"\u002F\u002F \u002Fauth\u002F* 라우트의 401은 정상적인 \"잘못된 자격증명·OTP\" → 호출자가 처리해야 함\n",[7968,31689,31690,31693,31696,31699,31701,31704,31707],{"class":5110,"line":4219},[7968,31691,31692],{"class":7973},"if",[7968,31694,31695],{"class":7984}," (url.",[7968,31697,31698],{"class":7980},"includes",[7968,31700,6100],{"class":7984},[7968,31702,31703],{"class":7993},"'\u002Fauth\u002F'",[7968,31705,31706],{"class":7984},")) ",[7968,31708,31709],{"class":7973},"return\n",[7968,31711,31712],{"class":5110,"line":8291},[7968,31713,8288],{"emptyLinePlaceholder":4259},[7968,31715,31716],{"class":5110,"line":8301},[7968,31717,31718],{"class":8398},"\u002F\u002F 인증되지 않은 상태에서 보호 라우트 호출 → 의미 있는 리다이렉트 아님\n",[7968,31720,31721,31723,31725,31728,31731,31734],{"class":5110,"line":8310},[7968,31722,31692],{"class":7973},[7968,31724,6685],{"class":7984},[7968,31726,31727],{"class":7973},"!",[7968,31729,31730],{"class":7980},"useAuthToken",[7968,31732,31733],{"class":7984},"().value) ",[7968,31735,31709],{"class":7973},[7968,31737,31738],{"class":5110,"line":8321},[7968,31739,8288],{"emptyLinePlaceholder":4259},[7968,31741,31742],{"class":5110,"line":8332},[7968,31743,31744],{"class":8398},"\u002F\u002F 인증된 상태 + 보호 라우트 401 → 토큰 만료 → \u002Flogin\n",[76,31746,31748],{"id":31747},"_44-회원가입-완료-화면-고객사-id-노출-제거","4.4 회원가입 완료 화면 — 고객사 ID 노출 제거",[18,31750,31751,31752,31755],{},"§2~§3 이후 로그인 시 companyId 외울 필요 없음 → 가입 완료 화면 ",[32,31753,31754],{},"발급된 고객사 ID: {id}"," 라인 제거. 시안 정책상 내부 식별자는 외부 노출하지 않음.",[76,31757,31759],{"id":31758},"_45-토스트-가시성-강화","4.5 토스트 가시성 강화",[81,31761,31762,31774,31780,31794],{},[84,31763,31764,31766,31767,11686,31770,31773],{},[21,31765,4328],{},": 좌하단 → 오른쪽 위 (",[32,31768,31769],{},"app.vue",[32,31771,31772],{},"\u003CUApp :toaster=\"{position:'top-right', expand:true, duration:5000}\">"," props로 직접 지정)",[84,31775,31776,31779],{},[21,31777,31778],{},"크기",": 폭 380→440px, 본문 폰트 15→17px, 패딩 16\u002F18→20\u002F24px, 최소 높이 56→68px, 모서리 12px, 그림자 강화, 타이틀 17px\u002F700, 아이콘 26px",[84,31781,31782,31783,31786,31787,4536,31790,4536,31792,4343],{},"Sonner 표준 셀렉터(",[32,31784,31785],{},"[data-sonner-toast]",") + Nuxt UI 내부 클래스 보강 셀렉터(",[32,31788,31789],{},"> div",[32,31791,18],{},[32,31793,7968],{},[84,31795,31796,11686,31798,31801],{},[32,31797,7959],{},[32,31799,31800],{},"ui.toaster"," 설정은 타입(슬롯\u002Fvariant)이 달라 제거, UApp props로 단일화",[76,31803,31805],{"id":31804},"_46-배포-검증","4.6 배포 + 검증",[81,31807,31808,31814],{},[84,31809,31810,31811],{},"Workers #12 Version ",[32,31812,31813],{},"84056c86-09ff-4d2f-a9cc-4c63365fc630",[84,31815,31816,31817,31820,31821,31824,31825,31828,31829,31832,31833,31836],{},"Pages #54(",[32,31818,31819],{},"bf71cd8e",") · #55(",[32,31822,31823],{},"bfd64bcc"," 401 처리) · #56(",[32,31826,31827],{},"eecef0a0"," ID 제거) · #57(",[32,31830,31831],{},"4800d506"," 토스트 1차) · #58(",[32,31834,31835],{},"683c5976"," UApp props) — 누적 5번",[76,31838,31840],{"id":31839},"_47-nhn_mock-secret-임시-적용","4.7 NHN_MOCK secret 임시 적용",[18,31842,31843,31844,31847],{},"라이브 검증 + 실 사용자(",[32,31845,31846],{},"dotype@malgnsoft.com",") 가입을 위해 production에 NHN_MOCK secret을 일시 적용. mockCode가 응답에 노출되어 사용자가 메일 없이도 6자리 코드를 토스트로 확인 가능. 자격증명 등록 시 secret 영구 제거 예정.",[73,31849],{},[11,31851,31853],{"id":31852},"_5-nice-통합인증휴대폰-본인확인-인프라-배포-13-60","§5. NICE 통합인증(휴대폰 본인확인) 인프라 (배포 #13 \u002F #60)",[76,31855,27637],{"id":31856},"한-줄-4",[18,31858,31859,31860,31863,31864,31867,31868,31871,31872,31875],{},"§4에서 자체 SMS OTP로 가입 흐름을 통과시켰지만, 이는 \"휴대폰 보유\"만 검증하지 \"본인 확인\"이 아님. 사용자 요청으로 ",[21,31861,31862],{},"NICE 통합인증(M=휴대폰 본인확인)"," 인프라를 통째로 구축. 정본 문서 ",[26,31865,30142],{"href":31866},"..\u002FNICE_AUTH"," 신규 작성 → 라이브 DDL 0004 적용(TB_NICE_AUTH + TB_USER에 ci\u002Fbirthdate\u002Fgender\u002Fnational_info\u002Fmobile_co + UNIQUE ci) → NICE 어댑터(mock\u002Freal, AES-256-GCM + PBKDF2 + HMAC) → 3 라우트(init\u002Fcallback\u002Fstatus) → \u002Fauth\u002Fsignup 확장(niceSession 검증·CI 중복 차단·NICE 결과로 이름·휴대폰·생년월일 덮어쓰기) → signup.vue Step 4 통째로 NICE 흐름으로 교체(\"본인 인증하기\" 버튼 + 폴링 + 결과 표시) → NICE_MOCK secret 적용으로 자격증명 발급 전 mock 통과. Workers 배포 #13(Version ",[32,31869,31870],{},"2ab47c1f...","), Pages 배포 #60 (alias ",[32,31873,31874],{},"c9577894","). 라이브 e2e 6 시나리오 통과.",[76,31877,29485],{"id":29484},[81,31879,31880,31886,31892,31912,31921],{},[84,31881,31882,31885],{},[21,31883,31884],{},"NICE 통합인증 휴대폰(M)"," 만 1차 — 금융·공동·아이핀(F\u002FU\u002FI)은 후속 확장.",[84,31887,31888,31891],{},[21,31889,31890],{},"자체 SMS OTP는 유지"," — 비밀번호 재설정·이메일 변경 등 단순 검증 영역. 본인 확인은 NICE.",[84,31893,31894,31897,31898,7505,31901,7505,31904,31907,31908,31911],{},[21,31895,31896],{},"mock 모드 우선"," — NICE 자격증명 발급 전이라 외부 호출 없이 동작. 가짜 결과: ",[32,31899,31900],{},"모의 사용자",[32,31902,31903],{},"19900101",[32,31905,31906],{},"01099998888"," \u002F CI는 ",[32,31909,31910],{},"MOCK_CI_\u003CrequestNo>","로 결정적 생성(같은 세션 = 같은 CI → 중복 가입 차단 테스트 가능).",[84,31913,31914,89,31917,31920],{},[21,31915,31916],{},"CI 중복 가입 차단",[32,31918,31919],{},"TB_USER.ci UNIQUE"," + signup 시 명시적 검사. \"이미 가입된 사용자입니다\" 안내 + 비밀번호 재설정 유도.",[84,31922,31923,31926,31927,4536,31929,31931,31932,4536,31934,31937,31938,4536,31941,4536,31944,4536,31947,4536,31950,31953],{},[21,31924,31925],{},"NICE 결과 우선"," — niceSession이 있으면 signup body의 ",[32,31928,14992],{},[32,31930,25536],{}," 대신 NICE 검증값(",[32,31933,14992],{},[32,31935,31936],{},"mobile_no",") 사용 + ",[32,31939,31940],{},"birthdate",[32,31942,31943],{},"gender",[32,31945,31946],{},"national_info",[32,31948,31949],{},"ci",[32,31951,31952],{},"mobile_co"," 적재.",[76,31955,31957],{"id":31956},"_52-ddl-0004-라이브-적용-완료","5.2 DDL 0004 (라이브 적용 완료)",[18,31959,31960,9148],{},[26,31961,31963],{"href":31962},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0004_nice_auth.sql","src\u002Fdb\u002Fmigrations\u002F0004_nice_auth.sql",[18,31965,31966,31967,31970],{},"§A ",[32,31968,31969],{},"TB_NICE_AUTH"," 신설 (17 컬럼):",[81,31972,31973,31993,32000,32020,32030],{},[84,31974,31975,31976,31978,31979,31982,31983,4339,31986,4339,31989,31992],{},"세션 키: ",[32,31977,27203],{}," PK + ",[32,31980,31981],{},"request_no"," UNIQUE + ",[32,31984,31985],{},"transaction_id",[32,31987,31988],{},"ticket",[32,31990,31991],{},"iterators"," (복호화에 필요)",[84,31994,31995,31996,31999],{},"상태: ",[32,31997,31998],{},"state"," (pending\u002Fcompleted\u002Ffailed\u002Fexpired\u002Fconsumed)",[84,32001,32002,32003,4361,32005,4361,32007,4361,32009,4361,32011,4361,32013,4361,32016,4361,32018],{},"결과: ",[32,32004,14992],{},[32,32006,31940],{},[32,32008,31943],{},[32,32010,31946],{},[32,32012,31949],{},[32,32014,32015],{},"di",[32,32017,31952],{},[32,32019,31936],{},[84,32021,32022,32023,4361,32025,4361,32028],{},"시간: ",[32,32024,25690],{},[32,32026,32027],{},"created_at",[32,32029,25157],{},[84,32031,32032,32033,4420,32036],{},"인덱스: ",[32,32034,32035],{},"(state, created_at)",[32,32037,32038],{},"(ci)",[18,32040,32041,32042,32044],{},"§B ",[32,32043,20302],{}," 5 컬럼 추가:",[81,32046,32047,32064],{},[84,32048,32049,4420,32052,4420,32055,4420,32058,4420,32061],{},[32,32050,32051],{},"birthdate VARCHAR(8)",[32,32053,32054],{},"gender CHAR(1)",[32,32056,32057],{},"national_info CHAR(1)",[32,32059,32060],{},"ci VARCHAR(255)",[32,32062,32063],{},"mobile_co VARCHAR(10)",[84,32065,32066,32069],{},[32,32067,32068],{},"UNIQUE KEY uq_user_ci (ci)"," — 중복 가입 차단",[18,32071,32072,32073,32076,32077,5069,32080,32083],{},"라이브 적용 검증: ",[32,32074,32075],{},"SHOW CREATE TABLE TB_NICE_AUTH"," 정상, ",[32,32078,32079],{},"TB_USER.ci",[32,32081,32082],{},"uq_user_ci"," 인덱스 단독.",[76,32085,32087,32088],{"id":32086},"_53-nice-어댑터-srcadaptersniceauthts","5.3 NICE 어댑터 — ",[32,32089,32090],{},"src\u002Fadapters\u002Fnice\u002Fauth.ts",[81,32092,32093,32106,32123,32139,32148,32161,32174],{},[84,32094,32095,32098,32099,32102,32103,4343],{},[32,32096,32097],{},"requestToken(creds, requestNo)"," → POST ",[32,32100,32101],{},"\u002Fauth\u002Ftoken"," (Basic auth + ",[32,32104,32105],{},"client_credentials",[84,32107,32108,32098,32111,6685,32114,4339,32117,4339,32120,4343],{},[32,32109,32110],{},"requestAuthUrl(creds, accessToken, requestNo)",[32,32112,32113],{},"\u002Fauth\u002Furl",[32,32115,32116],{},"svc_types: ['M']",[32,32118,32119],{},"return_url",[32,32121,32122],{},"close_url",[84,32124,32125,32098,32128,32131,32132,4339,32135,32138],{},[32,32126,32127],{},"requestResult(accessToken, webTxId, txId, requestNo)",[32,32129,32130],{},"\u002Fauth\u002Fresult"," (암호화된 ",[32,32133,32134],{},"enc_data",[32,32136,32137],{},"integrity_value"," 수신)",[84,32140,32141,89,32144,32147],{},[32,32142,32143],{},"deriveKeys(ticket, txId, iters)",[21,32145,32146],{},"PBKDF2-HMAC-SHA256"," 64 bytes 유도 → 대칭키 32 bytes + HMAC키 32 bytes (offset 48)",[84,32149,32150,32153,32154,4339,32157,32160],{},[32,32151,32152],{},"decryptResult(raw, ticket, txId, iters)"," — Web Crypto ",[32,32155,32156],{},"crypto.subtle.deriveBits",[32,32158,32159],{},"decrypt({name:'AES-GCM', iv, tagLength:128})"," + HMAC-SHA256 무결성 검증",[84,32162,32163,32166,32167,4473,32170,32173],{},[32,32164,32165],{},"mockNiceResult(requestNo)"," — 결정적 가짜 결과 (",[32,32168,32169],{},"name='모의 사용자'",[32,32171,32172],{},"ci='MOCK_CI_\u003CrequestNo>'",", …)",[84,32175,32176],{},"Workers 표준 Web Crypto만 사용 — 외부 라이브러리 0",[76,32178,32180,32181],{"id":32179},"_54-라우트-srcroutesnicets","5.4 라우트 — ",[32,32182,32183],{},"src\u002Froutes\u002Fnice.ts",[101,32185,32186,32195],{},[104,32187,32188],{},[107,32189,32190,32192],{},[110,32191,22556],{},[110,32193,32194],{},"동작",[126,32196,32197,32221,32238],{},[107,32198,32199,32204],{},[131,32200,32201],{},[32,32202,32203],{},"POST \u002Fauth\u002Fnice\u002Finit",[131,32205,32206,32207,32210,32211,32214,32215,32217,32218],{},"mock: 즉시 ",[32,32208,32209],{},"completed"," 상태로 가짜 결과 적재 → ",[32,32212,32213],{},"{sessionId, authUrl:null, mockMode:true}",". real: token + url 호출 후 ",[32,32216,27043],{}," 적재 → ",[32,32219,32220],{},"{sessionId, authUrl, mockMode:false}",[107,32222,32223,32228],{},[131,32224,32225],{},[32,32226,32227],{},"POST \u002Fauth\u002Fnice\u002Fcallback",[131,32229,32230,32231,32234,32235,32237],{},"NICE의 form\u002Fjson ",[32,32232,32233],{},"web_transaction_id"," 수신 → 가장 최근 ",[32,32236,27043],{}," 세션 → result 호출 + 복호화 + DB 업데이트 → HTML 응답(팝업 자동 닫기)",[107,32239,32240,32245],{},[131,32241,32242],{},[32,32243,32244],{},"GET \u002Fauth\u002Fnice\u002Fstatus?session=…",[131,32246,32247,32248,32250],{},"프런트 폴링 — state 조회. ",[32,32249,32209],{},"면 name\u002Fbirthdate\u002Fgender\u002Fnational_info\u002Fmobile_co\u002Fmobile_no 노출 (ci는 서버에서만 보유)",[76,32252,32254,32255,32257],{"id":32253},"_55-authsignup-확장","5.5 ",[32,32256,23258],{}," 확장",[18,32259,32260,5069,32263,32266],{},[32,32261,32262],{},"signupB",[32,32264,32265],{},"niceSession?: string"," 추가. 있으면:",[6674,32268,32269,32277,32283,32289,32294,32302,32309],{},[84,32270,32271,13735,32273,32276],{},[32,32272,31969],{},[32,32274,32275],{},"requestNo = niceSession"," 단건 조회",[84,32278,32279,32282],{},[32,32280,32281],{},"state === 'completed'"," 검증 (consumed\u002Ffailed\u002Fexpired면 401)",[84,32284,32285,32288],{},[32,32286,32287],{},"expires_at > now"," 검증",[84,32290,32291,32293],{},[32,32292,31949],{}," 중복 검사 — 있으면 409 \"이미 가입된 사용자\"",[84,32295,32296,32297,4536,32299,32301],{},"signup 시 NICE 결과(",[32,32298,14992],{},[32,32300,31936],{},")로 입력값 덮어쓰기 + birthdate\u002Fgender\u002Fnational_info\u002Fci\u002Fmobile_co 적재",[84,32303,32304,32305,32308],{},"signup 성공 후 ",[32,32306,32307],{},"niceAuth.state = 'consumed'"," 처리 → 재사용 차단",[84,32310,32311,32312,32314],{},"catch 블록의 Duplicate entry 감지 — ",[32,32313,32082],{}," 매치 시 별도 안내",[18,32316,32317],{},"OpenAPI 4지점(2 paths + 4 schemas) + SignupRequest에 niceSession 필드.",[76,32319,32321],{"id":32320},"_56-프런트-signupvue-step-4-통째로-교체","5.6 프런트 signup.vue Step 4 통째로 교체",[18,32323,32324,32325,32328],{},"기존: 통신사 select + 이름 + 주민번호 + 내외국인 + 휴대폰 3분할 + 인증번호 입력 → 6개 필드\n신규: ",[21,32326,32327],{},"\"본인 인증하기\" 큰 버튼 1개"," + 상태 표시",[18,32330,32331,9148],{},[32,32332,32333],{},"startNiceAuth()",[6674,32335,32336,32343,32354,32361],{},[84,32337,32338,6219,32340],{},[32,32339,32203],{},[32,32341,32342],{},"{sessionId, authUrl, mockMode}",[84,32344,32345,32346,32349,32350,32353],{},"mockMode면 즉시 ",[32,32347,32348],{},"pollNiceStatus()"," 1회 호출 → ",[32,32351,32352],{},"state='completed'"," + 결과 표시",[84,32355,32356,32357,32360],{},"real이면 ",[32,32358,32359],{},"window.open(authUrl, ...)"," + 5초마다 status 폴링 (최대 5분)",[84,32362,32363,32364,4339,32367],{},"결과 표시: ",[32,32365,32366],{},"\u003C이름>님 본인 인증이 완료되었습니다. \u003C휴대폰> · \u003C통신사>",[32,32368,32369],{},"verified=true",[18,32371,32372,32374,32375,32378,32379,32382],{},[32,32373,29079],{}," 확장: ",[32,32376,32377],{},"niceSession","을 signup body에 전달. NICE 결과의 name·휴대폰을 우선 사용. 409 응답 + ",[32,32380,32381],{},"이미 가입된 사용자"," 메시지 분기.",[18,32384,32385,51,32387,32390,32391,14770],{},[32,32386,11040],{},[32,32388,32389],{},"SignupPayload"," 타입에 ",[32,32392,32265],{},[18,32394,32395],{},"기존 입력 필드(통신사·이름·주민번호·휴대폰)와 관련 ref\u002Ffunction들은 다른 곳에서 의존성 없어 UI에서 자연 제거됨(스크립트 ref는 leftover로 남아 있으나 미사용).",[76,32397,32399],{"id":32398},"_57-라이브-e2e-검증-6-시나리오","5.7 라이브 e2e 검증 (6 시나리오)",[101,32401,32402,32412],{},[104,32403,32404],{},[107,32405,32406,32408,32410],{},[110,32407,3846],{},[110,32409,29790],{},[110,32411,22362],{},[126,32413,32414,32428,32442,32468,32480,32489],{},[107,32415,32416,32418,32426],{},[131,32417,4636],{},[131,32419,32420,32423,32424],{},[32,32421,32422],{},"\u002Fauth\u002Fnice\u002Finit"," → mock 응답 ",[32,32425,32213],{},[131,32427,3869],{},[107,32429,32430,32432,32440],{},[131,32431,4649],{},[131,32433,32434,6219,32437],{},[32,32435,32436],{},"\u002Fauth\u002Fnice\u002Fstatus?session=…",[32,32438,32439],{},"{state:'completed', name:'모의 사용자', mobile_no:'01099998888', …}",[131,32441,3869],{},[107,32443,32444,32446,32466],{},[131,32445,4662],{},[131,32447,32448,32450,32451,4536,32453,4536,32456,4536,32459,4536,32462,32465],{},[32,32449,23258],{}," with niceSession → 201 + DB에 ",[32,32452,32169],{},[32,32454,32455],{},"birthdate='19900101'",[32,32457,32458],{},"gender='1'",[32,32460,32461],{},"ci='MOCK_CI_…'",[32,32463,32464],{},"mobile_co='SKT'"," 정확 매핑",[131,32467,3869],{},[107,32469,32470,32472,32478],{},[131,32471,4678],{},[131,32473,32474,32475,29855],{},"같은 niceSession 재사용 → 401 ",[32,32476,32477],{},"NICE 본인 인증이 완료되지 않았습니다",[131,32479,3869],{},[107,32481,32482,32484,32487],{},[131,32483,4691],{},[131,32485,32486],{},"새 niceSession (다른 mock CI) → 정상 가입",[131,32488,3869],{},[107,32490,32491,32493,32502],{},[131,32492,4708],{},[131,32494,32495,32496,4473,32499,32501],{},"DB: ",[32,32497,32498],{},"TB_NICE_AUTH.state='consumed'",[32,32500,32079],{}," UNIQUE 정상 동작",[131,32503,3869],{},[18,32505,32506],{},"검증 데이터 cleanup 완료 (TB_USER · TB_COMPANY · TB_NICE_AUTH 0건 잔존).",[76,32508,32510,32511],{"id":32509},"_58-정본-문서-docnice_authmd","5.8 정본 문서 — ",[32,32512,30142],{},[18,32514,32515],{},"12 섹션 \u002F ~14KB:",[6674,32517,32518,32521,32524,32527,32530,32533,32536,32539,32542,32548,32555,32558],{},[84,32519,32520],{},"자체 SMS OTP vs NICE 비교",[84,32522,32523],{},"인증 수단 종류 (M\u002FF\u002FU\u002FI — 우리는 M 우선)",[84,32525,32526],{},"전체 시퀀스 5단계 (ASCII 도식)",[84,32528,32529],{},"엔드포인트 3종",[84,32531,32532],{},"단계별 명세 + JSON 예시",[84,32534,32535],{},"AES-256-GCM + PBKDF2 (Workers Web Crypto)",[84,32537,32538],{},"응답 데이터 (name·birthdate·gender·CI·DI·mobile_co·mobile_no)",[84,32540,32541],{},"우리 적용 계획",[84,32543,32544,32547],{},[21,32545,32546],{},"인프라 고려사항"," — Workers 동적 IP vs NICE 화이트리스트 요구 (협상 또는 자체 프록시 EC2 필요)",[84,32549,32550,32551,32554],{},"NICE 계약 절차 7단계 (1",[20367,32552,32553],{},"4 사용자, 5","7 김도형)",[84,32556,32557],{},"알려진 한계 (외국인·법인 대표자·PASS·CI 중복 검사 등)",[84,32559,4189],{},[76,32561,32563],{"id":32562},"_59-산출물","5.9 산출물",[81,32565,32566,32591,32610,32616,32622],{},[84,32567,32568,32569,32572,32573,4536,32576,4536,32579,26598,32582,4536,32584,4536,32587,4536,32589],{},"API: ",[32,32570,32571],{},"malgn-noti-api: b4d8f4b"," — 7 files +922 -11. 신규: ",[32,32574,32575],{},"nice\u002Fauth.ts",[32,32577,32578],{},"routes\u002Fnice.ts",[32,32580,32581],{},"0004_nice_auth.sql",[32,32583,28258],{},[32,32585,32586],{},"auth.ts",[32,32588,25376],{},[32,32590,25366],{},[84,32592,32593,32594,4536,32596,4536,32598,4536,32601,4536,32603,4536,32605,32607,32608,4343],{},"사용자단: 5 파일 수정(",[32,32595,14163],{},[32,32597,11040],{},[32,32599,32600],{},"useApi.ts",[32,32602,31769],{},[32,32604,7959],{},[32,32606,4771],{},") + 1 신규(",[32,32609,30142],{},[84,32611,32612,32613],{},"Workers 배포 #13 Version ",[32,32614,32615],{},"2ab47c1f-1d68-42d3-815c-117cab3fd71a",[84,32617,32618,32619],{},"Pages 배포 #60 alias ",[32,32620,32621],{},"c9577894.malgn-noti.pages.dev",[84,32623,32624],{},"WBS 5-3C-* 신규 항목: NICE 본인확인 인프라 ✅",[76,32626,32628],{"id":32627},"_510-알려진-한계-다음-단계","5.10 알려진 한계 \u002F 다음 단계",[81,32630,32631,32644,32653,32662,32674,32683],{},[84,32632,32633,32636,32637,4339,32640,32643],{},[21,32634,32635],{},"NICE 자격증명 미발급"," — 사용자 영업 작업 선행. 발급 후 ",[32,32638,32639],{},"wrangler secret put NICE_CLIENT_ID\u002FSECRET\u002FRETURN_URL",[32,32641,32642],{},"wrangler secret delete NICE_MOCK","로 real 모드 전환 가능.",[84,32645,32646,89,32649,32652],{},[21,32647,32648],{},"Workers 동적 outbound IP vs NICE 화이트리스트",[26,32650,32651],{"href":31866},"NICE_AUTH.md §9"," 참조. Cloudflare 대역 등록 협상 또는 자체 프록시 EC2 필요. NICE 계약 시점에 결정.",[84,32654,32655,32658,32659,32661],{},[21,32656,32657],{},"콜백 시 세션 매칭"," — 1차 구현은 \"가장 최근 pending 세션\" 휴리스틱. 동시 다중 가입은 드물지만 운영 단계에서 ",[32,32660,31998],{}," 파라미터로 명시화 검토.",[84,32663,32664,89,32667,32670,32671,32673],{},[21,32665,32666],{},"모바일웹 popup 차단",[32,32668,32669],{},"window.open","이 모바일 Safari에서 차단될 수 있음. ",[32,32672,29057],{}," 모드 옵션 검토.",[84,32675,32676,89,32679,32682],{},[21,32677,32678],{},"외국인 가입",[32,32680,32681],{},"national_info='1'"," 분기 UI 후속.",[84,32684,32685,32688],{},[21,32686,32687],{},"법인 대표자 본인 인증"," — 정책 결정 후 적용.",[73,32690],{},[11,32692,32694,32695,4339,32698,32701],{"id":32693},"_6-accountsettings-실-api-연동-patch-me-patch-mecompany-배포-14-61","§6. \u002Faccount\u002Fsettings 실 API 연동 — ",[32,32696,32697],{},"PATCH \u002Fme",[32,32699,32700],{},"PATCH \u002Fme\u002Fcompany"," (배포 #14 \u002F #61)",[76,32703,27637],{"id":32704},"한-줄-5",[18,32706,32707,32708,32710,32711,4339,32714,32716,32717,32719,32720,32724,32725,32728,32729,32731,32732,32735,32736,5606],{},"WBS 5-3C-7 (PATCH \u002Fme + \u002Faccount\u002Fsettings) 작업. 기존 백엔드 ",[32,32709,3987],{},"는 GET만 있었고 응답도 최소(8 필드)였는데, ",[21,32712,32713],{},"GET \u002Fme 응답을 TB_USER 13 + TB_COMPANY 14 컬럼 풀로 확장",[21,32715,32697],{},"(사용자 본인 — name·phone) + ",[21,32718,32700],{},"(회사 — companyPhone·billingEmail·adReceive, owner\u002Fadmin 권한) 신설. 프런트 ",[26,32721,32723],{"href":32722},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppMemberInfoPanel.vue","AppMemberInfoPanel.vue","는 전체 목업 데이터(",[32,32726,32727],{},"account.email='service@malgnsoft.com'"," 등)를 제거하고 ",[32,32730,29037],{}," 기반으로 모두 실 데이터로 교체 — 가입 정보 행은 회사 정보 자동 매핑, 광고성 메일 수신 토글은 즉시 PATCH(컨펌 모달 후), 저장하기는 변경된 필드만 한 번에 PATCH. 라이브 e2e 5건 통과. Workers 배포 #14(Version ",[32,32733,32734],{},"22368d14...","), Pages 배포 #61 (alias ",[32,32737,32738],{},"ea35651d.malgn-noti.pages.dev",[76,32740,32742],{"id":32741},"_61-백엔드-변경","6.1 백엔드 변경",[18,32744,32745,89,32749,32752],{},[26,32746,32748],{"href":32747},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Froutes\u002Fme.ts","src\u002Froutes\u002Fme.ts",[32,32750,32751],{},"readContext()"," 헬퍼로 GET·PATCH 공통 JOIN 쿼리 추출:",[5251,32754,32756],{"className":7962,"code":32755,"language":7964,"meta":4208,"style":4208},"me.use('*', requireAuth())\nme.get('\u002F', ...)                          \u002F\u002F 기존 + 풀 컬럼\nme.patch('\u002F', zValidator(json, patchMeB), ...)        \u002F\u002F name·phone\nme.patch('\u002Fcompany', zValidator(json, patchCompanyB), ...)  \u002F\u002F companyPhone·billingEmail·adReceive\n",[32,32757,32758,32777,32799,32826],{"__ignoreMap":4208},[7968,32759,32760,32763,32765,32767,32769,32771,32774],{"class":5110,"line":7970},[7968,32761,32762],{"class":7984},"me.",[7968,32764,21416],{"class":7980},[7968,32766,6100],{"class":7984},[7968,32768,21421],{"class":7993},[7968,32770,4473],{"class":7984},[7968,32772,32773],{"class":7980},"requireAuth",[7968,32775,32776],{"class":7984},"())\n",[7968,32778,32779,32781,32784,32786,32789,32791,32793,32796],{"class":5110,"line":4212},[7968,32780,32762],{"class":7984},[7968,32782,32783],{"class":7980},"get",[7968,32785,6100],{"class":7984},[7968,32787,32788],{"class":7993},"'\u002F'",[7968,32790,4473],{"class":7984},[7968,32792,9432],{"class":7973},[7968,32794,32795],{"class":7984},")                          ",[7968,32797,32798],{"class":8398},"\u002F\u002F 기존 + 풀 컬럼\n",[7968,32800,32801,32803,32806,32808,32810,32812,32815,32818,32820,32823],{"class":5110,"line":4209},[7968,32802,32762],{"class":7984},[7968,32804,32805],{"class":7980},"patch",[7968,32807,6100],{"class":7984},[7968,32809,32788],{"class":7993},[7968,32811,4473],{"class":7984},[7968,32813,32814],{"class":7980},"zValidator",[7968,32816,32817],{"class":7984},"(json, patchMeB), ",[7968,32819,9432],{"class":7973},[7968,32821,32822],{"class":7984},")        ",[7968,32824,32825],{"class":8398},"\u002F\u002F name·phone\n",[7968,32827,32828,32830,32832,32834,32837,32839,32841,32844,32846,32849],{"class":5110,"line":4219},[7968,32829,32762],{"class":7984},[7968,32831,32805],{"class":7980},[7968,32833,6100],{"class":7984},[7968,32835,32836],{"class":7993},"'\u002Fcompany'",[7968,32838,4473],{"class":7984},[7968,32840,32814],{"class":7980},[7968,32842,32843],{"class":7984},"(json, patchCompanyB), ",[7968,32845,9432],{"class":7973},[7968,32847,32848],{"class":7984},")  ",[7968,32850,32851],{"class":8398},"\u002F\u002F companyPhone·billingEmail·adReceive\n",[81,32853,32854,32865,32875],{},[84,32855,32856,32857,32860,32861,32864],{},"빈 PATCH(",[32,32858,32859],{},"{}",") → 400 ",[32,32862,32863],{},"validation_failed"," (변경할 필드가 없습니다)",[84,32866,32867,32870,32871,32874],{},[32,32868,32869],{},"\u002Fcompany"," PATCH는 ",[32,32872,32873],{},"role !== 'owner' && role !== 'admin'"," → 403 forbidden",[84,32876,32877,32878,32881],{},"응답은 모두 동일 형식(",[32,32879,32880],{},"{data: {user, company, ctxRole}}",")으로 통일 → 프런트가 변경 후 store 그대로 hydrate 가능",[18,32883,32884,32885,32887,32888,4536,32891,32894],{},"OpenAPI: ",[32,32886,22196],{}," schema 확장 + ",[32,32889,32890],{},"PatchMeRequest",[32,32892,32893],{},"PatchCompanyRequest"," 신규. paths 2 추가.",[76,32896,32898],{"id":32897},"_62-storesauthts-확장","6.2 stores\u002Fauth.ts 확장",[5251,32900,32902],{"className":7962,"code":32901,"language":7964,"meta":4208,"style":4208},"interface AuthUser {  \u002F\u002F +birthdate, gender, nationalInfo, mobileCo, memberType\n  ...\n}\ninterface AuthCompany {  \u002F\u002F +bizNo, bizType, ceoName, upTae, upJong, address, companyPhone, billingEmail, adReceive\n  ...\n}\n\nactions: {\n  async updateMe(patch: {name?, phone?}) { ... }\n  async updateCompany(patch: {companyPhone?, billingEmail?, adReceive?}) { ... }\n}\n",[32,32903,32904,32917,32922,32926,32938,32942,32946,32950,32958,32973,32987],{"__ignoreMap":4208},[7968,32905,32906,32908,32911,32914],{"class":5110,"line":7970},[7968,32907,27194],{"class":7973},[7968,32909,32910],{"class":7980}," AuthUser",[7968,32912,32913],{"class":7984}," {  ",[7968,32915,32916],{"class":8398},"\u002F\u002F +birthdate, gender, nationalInfo, mobileCo, memberType\n",[7968,32918,32919],{"class":5110,"line":4212},[7968,32920,32921],{"class":7973},"  ...\n",[7968,32923,32924],{"class":5110,"line":4209},[7968,32925,8987],{"class":7984},[7968,32927,32928,32930,32933,32935],{"class":5110,"line":4219},[7968,32929,27194],{"class":7973},[7968,32931,32932],{"class":7980}," AuthCompany",[7968,32934,32913],{"class":7984},[7968,32936,32937],{"class":8398},"\u002F\u002F +bizNo, bizType, ceoName, upTae, upJong, address, companyPhone, billingEmail, adReceive\n",[7968,32939,32940],{"class":5110,"line":8291},[7968,32941,32921],{"class":7973},[7968,32943,32944],{"class":5110,"line":8301},[7968,32945,8987],{"class":7984},[7968,32947,32948],{"class":5110,"line":8310},[7968,32949,8288],{"emptyLinePlaceholder":4259},[7968,32951,32952,32955],{"class":5110,"line":8321},[7968,32953,32954],{"class":7980},"actions",[7968,32956,32957],{"class":7984},": {\n",[7968,32959,32960,32963,32966,32969,32971],{"class":5110,"line":8332},[7968,32961,32962],{"class":7984},"  async ",[7968,32964,32965],{"class":7980},"updateMe",[7968,32967,32968],{"class":7984},"(patch: {name?, phone?}) { ",[7968,32970,9432],{"class":7973},[7968,32972,9317],{"class":7984},[7968,32974,32975,32977,32980,32983,32985],{"class":5110,"line":8343},[7968,32976,32962],{"class":7984},[7968,32978,32979],{"class":7980},"updateCompany",[7968,32981,32982],{"class":7984},"(patch: {companyPhone?, billingEmail?, adReceive?}) { ",[7968,32984,9432],{"class":7973},[7968,32986,9317],{"class":7984},[7968,32988,32989],{"class":5110,"line":8349},[7968,32990,8987],{"class":7984},[76,32992,32994],{"id":32993},"_63-appmemberinfopanelvue-전면-교체","6.3 AppMemberInfoPanel.vue 전면 교체",[101,32996,32997,33009],{},[104,32998,32999],{},[107,33000,33001,33003,33006],{},[110,33002,7712],{},[110,33004,33005],{},"기존 (목업)",[110,33007,33008],{},"신규 (실 데이터)",[126,33010,33011,33030,33051,33065,33080,33096,33110,33126,33140,33151,33162,33173],{},[107,33012,33013,33016,33022],{},[131,33014,33015],{},"데이터 로드",[131,33017,33018,33019],{},"하드코딩 ",[32,33020,33021],{},"account = {email: 'service@malgnsoft.com', ...}",[131,33023,33024,4339,33027],{},[32,33025,33026],{},"onMounted(auth.fetchMe)",[32,33028,33029],{},"computed u\u002Fc",[107,33031,33032,33035,33041],{},[131,33033,33034],{},"가입 정보 8행",[131,33036,33037,33040],{},[32,33038,33039],{},"INFO_ROWS"," 고정",[131,33042,33043,33046,33047,33050],{},[32,33044,33045],{},"c.value","의 bizNo\u002FbizType\u002FceoName\u002FupTae\u002FupJong\u002Faddress 자동 매핑, ",[32,33048,33049],{},"BIZ_TYPE_LABEL","로 한국어 표시",[107,33052,33053,33056,33059],{},[131,33054,33055],{},"사업자등록증 변경 버튼",[131,33057,33058],{},"항상 노출",[131,33060,33061,33064],{},[32,33062,33063],{},"c.bizType !== 'personal'"," 일 때만 (개인 유형은 노출 X)",[107,33066,33067,33070,33073],{},[131,33068,33069],{},"광고성 메일 수신 토글",[131,33071,33072],{},"로컬 ref 토글 + 토스트만",[131,33074,33075,33076,33079],{},"컨펌 모달 → ",[32,33077,33078],{},"auth.updateCompany({adReceive})"," 즉시 호출 + 토스트",[107,33081,33082,33085,33090],{},[131,33083,33084],{},"서비스 담당자 이름",[131,33086,33018,33087],{},[32,33088,33089],{},"'홍길동'",[131,33091,33092,33095],{},[32,33093,33094],{},"u.value.name"," (NICE 검증 결과)",[107,33097,33098,33101,33104],{},[131,33099,33100],{},"회사 전화번호 입력",[131,33102,33103],{},"로컬 ref",[131,33105,33106,33109],{},[32,33107,33108],{},"companyPhoneInput"," ref, watchEffect로 store에서 초기화",[107,33111,33112,33115,33117],{},[131,33113,33114],{},"휴대전화번호 3분할",[131,33116,33103],{},[131,33118,33119,13739,33122,33125],{},[32,33120,33121],{},"watchEffect",[32,33123,33124],{},"u.value.phone","에서 010\u002F3~4자리\u002F4자리로 자동 split",[107,33127,33128,33131,33134],{},[131,33129,33130],{},"결제 이메일 변경",[131,33132,33133],{},"로컬 ref 변경 + 토스트",[131,33135,33136,33137],{},"다이얼로그 → ",[32,33138,33139],{},"auth.updateCompany({billingEmail})",[107,33141,33142,33145,33148],{},[131,33143,33144],{},"서비스 담당자 이메일 변경",[131,33146,33147],{},"로컬 ref 변경",[131,33149,33150],{},"\"곧 지원됩니다\" 안내 — OTP 검증 흐름은 후속",[107,33152,33153,33156,33159],{},[131,33154,33155],{},"휴대폰 본인 인증",[131,33157,33158],{},"NICE 미연결 더미",[131,33160,33161],{},"그대로 더미 — NICE Step 4와 별개로 후속",[107,33163,33164,33167,33170],{},[131,33165,33166],{},"회원 탈퇴",[131,33168,33169],{},"로컬 토스트",[131,33171,33172],{},"\"곧 지원됩니다\" 안내 — 후속 라우트 필요",[107,33174,33175,33179,33182],{},[131,33176,33177],{},[21,33178,18582],{},[131,33180,33181],{},"토스트만",[131,33183,33184,4536,33187,33190,33191,4536,33193,33195],{},[32,33185,33186],{},"companyPhone",[32,33188,33189],{},"fullPhone"," 변경 감지 → ",[32,33192,32965],{},[32,33194,32979],{}," 병렬 호출",[18,33197,33198],{},"저장 로직:",[5251,33200,33202],{"className":7962,"code":33201,"language":7964,"meta":4208,"style":4208},"const tasks = []\nif (fullPhone !== u.phone) tasks.push(updateMe({phone: fullPhone}))\nif (companyPhoneInput !== c.companyPhone) tasks.push(updateCompany({companyPhone: companyPhoneInput}))\nif (tasks.length === 0) toast(변경 없음)\nelse await Promise.all(tasks) → 성공 토스트\n",[32,33203,33204,33216,33238,33259,33283],{"__ignoreMap":4208},[7968,33205,33206,33208,33211,33213],{"class":5110,"line":7970},[7968,33207,9204],{"class":7973},[7968,33209,33210],{"class":8892}," tasks",[7968,33212,9210],{"class":7973},[7968,33214,33215],{"class":7984}," []\n",[7968,33217,33218,33220,33223,33226,33229,33231,33233,33235],{"class":5110,"line":4212},[7968,33219,31692],{"class":7973},[7968,33221,33222],{"class":7984}," (fullPhone ",[7968,33224,33225],{"class":7973},"!==",[7968,33227,33228],{"class":7984}," u.phone) tasks.",[7968,33230,12973],{"class":7980},[7968,33232,6100],{"class":7984},[7968,33234,32965],{"class":7980},[7968,33236,33237],{"class":7984},"({phone: fullPhone}))\n",[7968,33239,33240,33242,33245,33247,33250,33252,33254,33256],{"class":5110,"line":4209},[7968,33241,31692],{"class":7973},[7968,33243,33244],{"class":7984}," (companyPhoneInput ",[7968,33246,33225],{"class":7973},[7968,33248,33249],{"class":7984}," c.companyPhone) tasks.",[7968,33251,12973],{"class":7980},[7968,33253,6100],{"class":7984},[7968,33255,32979],{"class":7980},[7968,33257,33258],{"class":7984},"({companyPhone: companyPhoneInput}))\n",[7968,33260,33261,33263,33266,33269,33272,33275,33277,33280],{"class":5110,"line":4219},[7968,33262,31692],{"class":7973},[7968,33264,33265],{"class":7984}," (tasks.",[7968,33267,33268],{"class":8892},"length",[7968,33270,33271],{"class":7973}," ===",[7968,33273,33274],{"class":8892}," 0",[7968,33276,21438],{"class":7984},[7968,33278,33279],{"class":7980},"toast",[7968,33281,33282],{"class":7984},"(변경 없음)\n",[7968,33284,33285,33288,33290,33293,33295,33298],{"class":5110,"line":8291},[7968,33286,33287],{"class":7973},"else",[7968,33289,9280],{"class":7973},[7968,33291,33292],{"class":8892}," Promise",[7968,33294,4703],{"class":7984},[7968,33296,33297],{"class":7980},"all",[7968,33299,33300],{"class":7984},"(tasks) → 성공 토스트\n",[18,33302,33303,33305],{},[32,33304,19296],{}," 스타일도 추가 (광고성 메일 수신 라디오 토글 — 기존 누락).",[76,33307,33309],{"id":33308},"_64-라이브-e2e-production","6.4 라이브 e2e (Production)",[101,33311,33312,33322],{},[104,33313,33314],{},[107,33315,33316,33318,33320],{},[110,33317,3846],{},[110,33319,27964],{},[110,33321,22362],{},[126,33323,33324,33335,33347,33359,33371],{},[107,33325,33326,33328,33332],{},[131,33327,4636],{},[131,33329,33330,28831],{},[32,33331,21820],{},[131,33333,33334],{},"200 + 풀 컨텍스트 (TB_USER 13 + TB_COMPANY 14 컬럼)",[107,33336,33337,33339,33344],{},[131,33338,4649],{},[131,33340,33341],{},[32,33342,33343],{},"PATCH \u002Fme {name:'김도형', phone:'010-1111-2222'}",[131,33345,33346],{},"200 + 갱신된 user 응답",[107,33348,33349,33351,33356],{},[131,33350,4662],{},[131,33352,33353],{},[32,33354,33355],{},"PATCH \u002Fme\u002Fcompany {companyPhone, billingEmail, adReceive:'reject'}",[131,33357,33358],{},"200 + 갱신된 company 응답",[107,33360,33361,33363,33368],{},[131,33362,4678],{},[131,33364,33365,33367],{},[32,33366,21820],{}," 재호출",[131,33369,33370],{},"name\u002Fphone\u002FcompanyPhone\u002FbillingEmail\u002FadReceive 모두 정확 반영",[107,33372,33373,33375,33380],{},[131,33374,4691],{},[131,33376,33377,33378],{},"빈 PATCH ",[32,33379,32859],{},[131,33381,33382,33383,33385],{},"400 ",[32,33384,32863],{},": 변경할 필드가 없습니다",[18,33387,33388,33389,33392],{},"테스트 사용자(",[32,33390,33391],{},"mep-test-…",") cleanup 완료.",[76,33394,33396],{"id":33395},"_65-산출물","6.5 산출물",[81,33398,33399,33413,33425],{},[84,33400,32568,33401,89,33404,33406,33407,33409,33410],{},[32,33402,33403],{},"malgn-noti-api: c8c…",[32,33405,32748],{}," 전면 개편(+150) · ",[32,33408,22139],{}," 갱신. Workers 배포 #14 Version ",[32,33411,33412],{},"22368d14-f0c8-4788-8b52-5cb4f6442cf3",[84,33414,33415,33416,33418,33419,33422,33423],{},"사용자단: ",[32,33417,28955],{}," 타입 확장 + 2 액션 \u002F ",[32,33420,33421],{},"app\u002Fcomponents\u002FAppMemberInfoPanel.vue"," 전면 교체. Pages 배포 #61 alias ",[32,33424,32738],{},[84,33426,33427],{},"WBS 5-3C-7 ⚪ → 🟢 (회원 정보 변경 — 저장하기·광고수신 즉시 변경·결제이메일 변경은 실 API, 서비스 담당자 이메일·휴대폰 본인 인증 변경은 후속)",[76,33429,33431],{"id":33430},"_66-알려진-한계-후속-작업","6.6 알려진 한계 \u002F 후속 작업",[81,33433,33434,33443,33449,33464],{},[84,33435,33436,33438,33439,33442],{},[21,33437,33144],{}," — OTP 검증 흐름 필요. 백엔드 ",[32,33440,33441],{},"POST \u002Fme\u002Femail-change\u002F{request,confirm}"," 신설 후 다이얼로그 연결.",[84,33444,33445,33448],{},[21,33446,33447],{},"휴대폰 본인 인증 변경"," — NICE 재인증 흐름 또는 SMS OTP. signup의 NICE Step 4와 유사한 패턴 재사용 가능.",[84,33450,33451,89,33453,26162,33456,33459,33460,33463],{},[21,33452,33166],{},[32,33454,33455],{},"DELETE \u002Fme",[32,33457,33458],{},"POST \u002Fme\u002Fwithdraw"," 신설 + soft-delete (",[32,33461,33462],{},"TB_USER.status = -1",") + 관련 데이터 정책 결정.",[84,33465,33466,33472],{},[21,33467,33468,33471],{},[32,33469,33470],{},"canEditCompany"," 권한 UX"," — 현재는 PATCH 호출 후 403 에러로 안내. 사전에 role 기반으로 UI 비활성화 검토.",[73,33474],{},[11,33476,33478],{"id":33477},"_7-사업자등록증-심사-승인-게이트-정책-정합화-배포-15-64","§7. 사업자등록증 심사 승인 게이트 — 정책 정합화 (배포 #15 \u002F #64)",[76,33480,27637],{"id":33481},"한-줄-6",[18,33483,33484,33485,4473,33488,33491,33492,33495,33496,33499,33500,4536,33502,33504,33505,33508,33509,33512],{},"새 정책: ",[21,33486,33487],{},"법인 사업자(corp) \u002F 개인 사업자(sole)는 가입 후 사업자등록증 심사 승인을 받아야 서비스 이용 및 가입 정보 수정 가능",[21,33489,33490],{},"개인(personal)은 즉시 사용 가능",". 그동안은 모든 가입자가 ",[32,33493,33494],{},"joinState='joined'"," 즉시 통과였는데, ",[32,33497,33498],{},"TB_COMPANY.approval_state"," 컬럼 + signup 자동 분기 + ",[32,33501,32697],{},[32,33503,32700],{}," 차단 + 프런트 배너·입력 disabled + 가입 완료 화면 분기로 인프라화. 0005 라이브 적용, 기존 5개 회사는 'approved' 기본값으로 호환성 유지. Workers 배포 #15(Version ",[32,33506,33507],{},"6e47d50b...","), Pages 배포 #64 (alias ",[32,33510,33511],{},"56e94e5b.malgn-noti.pages.dev","). 라이브 e2e 8 시나리오 통과(법인 가입 pending \u002F 수정 시도 403 \u002F 개인 가입 approved \u002F 개인 수정 통과 \u002F 운영자 승인 후 수정 통과 \u002F 반려 시뮬레이션 → 사유 노출 403 …).",[76,33514,33516],{"id":33515},"_71-정책-사용자-결정","7.1 정책 (사용자 결정)",[101,33518,33519,33535],{},[104,33520,33521],{},[107,33522,33523,33526,33529,33532],{},[110,33524,33525],{},"회사 유형",[110,33527,33528],{},"가입 직후 상태",[110,33530,33531],{},"서비스 이용",[110,33533,33534],{},"정보 수정",[126,33536,33537,33554,33570],{},[107,33538,33539,33545,33550,33552],{},[131,33540,33541,33544],{},[32,33542,33543],{},"corp"," 법인사업자",[131,33546,33547],{},[32,33548,33549],{},"approval_state='pending'",[131,33551,5788],{},[131,33553,5788],{},[107,33555,33556,33562,33566,33568],{},[131,33557,33558,33561],{},[32,33559,33560],{},"sole"," 개인사업자",[131,33563,33564],{},[32,33565,33549],{},[131,33567,5788],{},[131,33569,5788],{},[107,33571,33572,33578,33583,33585],{},[131,33573,33574,33577],{},[32,33575,33576],{},"personal"," 개인",[131,33579,33580],{},[32,33581,33582],{},"approval_state='approved'",[131,33584,3869],{},[131,33586,3869],{},[18,33588,33589,33590,26162,33596,33603],{},"운영자가 BackOffice에서 사업자등록증을 심사 → ",[21,33591,33592,33593,4343],{},"승인(",[32,33594,33595],{},"approved",[21,33597,33598,33599,33602],{},"반려(",[32,33600,33601],{},"rejected"," + 사유)"," 처리. 1차에서는 운영자 화면 미구현이라 라이브 DB 직접 UPDATE로 검증(후속에서 운영자단 화면 신설 예정).",[76,33605,33607],{"id":33606},"_72-ddl-0005-라이브-적용-완료","7.2 DDL 0005 (라이브 적용 완료)",[18,33609,33610,9148],{},[26,33611,33613],{"href":33612},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fdb\u002Fmigrations\u002F0005_company_approval.sql","src\u002Fdb\u002Fmigrations\u002F0005_company_approval.sql",[5251,33615,33617],{"className":31016,"code":33616,"language":31018,"meta":4208,"style":4208},"ALTER TABLE TB_COMPANY\n  ADD COLUMN company_type    VARCHAR(20) NULL  COMMENT 'corp\u002Fsole\u002Fpersonal' AFTER name,\n  ADD COLUMN approval_state  VARCHAR(20) NOT NULL DEFAULT 'approved'        AFTER company_type,\n  ADD COLUMN rejected_reason VARCHAR(255) NULL                              AFTER approval_state,\n  ADD KEY idx_company_approval (approval_state, created_at);\n",[32,33618,33619,33624,33629,33634,33639],{"__ignoreMap":4208},[7968,33620,33621],{"class":5110,"line":7970},[7968,33622,33623],{},"ALTER TABLE TB_COMPANY\n",[7968,33625,33626],{"class":5110,"line":4212},[7968,33627,33628],{},"  ADD COLUMN company_type    VARCHAR(20) NULL  COMMENT 'corp\u002Fsole\u002Fpersonal' AFTER name,\n",[7968,33630,33631],{"class":5110,"line":4209},[7968,33632,33633],{},"  ADD COLUMN approval_state  VARCHAR(20) NOT NULL DEFAULT 'approved'        AFTER company_type,\n",[7968,33635,33636],{"class":5110,"line":4219},[7968,33637,33638],{},"  ADD COLUMN rejected_reason VARCHAR(255) NULL                              AFTER approval_state,\n",[7968,33640,33641],{"class":5110,"line":8291},[7968,33642,33643],{},"  ADD KEY idx_company_approval (approval_state, created_at);\n",[18,33645,33646,33647,33649],{},"기존 5행 모두 자동으로 ",[32,33648,33595],{}," — 운영 데이터 호환성 유지.",[76,33651,33653],{"id":33652},"_73-백엔드-변경","7.3 백엔드 변경",[237,33655,33657],{"id":33656},"signup-확장","signup 확장",[81,33659,33660,33667,33678],{},[84,33661,33662,5069,33664,11190],{},[32,33663,32262],{},[32,33665,33666],{},"companyType: enum(corp\u002Fsole\u002Fpersonal).optional()",[84,33668,33669,33670,33673,33674,33677],{},"사업자(corp\u002Fsole) → ",[32,33671,33672],{},"approvalState='pending'",", 그 외 → ",[32,33675,33676],{},"'approved'"," 자동 분기",[84,33679,33680,33681,4536,33683,33686],{},"회사 row INSERT 시 ",[32,33682,30486],{},[32,33684,33685],{},"approvalState"," 함께 적재",[237,33688,33690],{"id":33689},"me-응답에-승인-정보-노출","\u002Fme 응답에 승인 정보 노출",[81,33692,33693],{},[84,33694,33695,33696,33699,33700,4420,33702,4420,33704,33707],{},"GET \u002F PATCH 응답의 ",[32,33697,33698],{},"company"," 객체에 ",[32,33701,30486],{},[32,33703,33685],{},[32,33705,33706],{},"rejectedReason"," 추가 (3 군데 응답 빌더 모두)",[237,33709,33711],{"id":33710},"patch-차단","PATCH 차단",[81,33713,33714,33740],{},[84,33715,33716,4536,33718,33720,33721,33723,33724,33727,33728],{},[32,33717,32697],{},[32,33719,32700],{}," 둘 다 핸들러 시작부에서 ",[32,33722,32751],{}," 호출 → ",[32,33725,33726],{},"approvalState !== 'approved'","면 403 + 상황별 메시지:\n",[81,33729,33730,33735],{},[84,33731,33732,33734],{},[32,33733,27043],{}," → \"사업자등록증 심사 승인 후 정보를 수정할 수 있습니다.\"",[84,33736,33737,33739],{},[32,33738,33601],{}," → \"심사가 반려되어 정보를 수정할 수 없습니다. 사유: …\"",[84,33741,33742,33743,33746],{},"발송·이력 등 다른 도메인 라우트 차단은 후속 (별도 미들웨어 ",[32,33744,33745],{},"requireApproved()","로 일관화 검토)",[76,33748,33750],{"id":33749},"_74-사용자단-변경","7.4 사용자단 변경",[237,33752,11040],{"id":33753},"storesauthts",[81,33755,33756,33769],{},[84,33757,33758,5069,33760,4420,33763,4420,33766,11190],{},[32,33759,28963],{},[32,33761,33762],{},"companyType?",[32,33764,33765],{},"approvalState?",[32,33767,33768],{},"rejectedReason?",[84,33770,33771,5069,33773,11190],{},[32,33772,32389],{},[32,33774,33762],{},[237,33776,14163],{"id":33777},"signupvue",[81,33779,33780,33790],{},[84,33781,33782,33785,33786,33789],{},[32,33783,33784],{},"auth.signup({...})"," 호출 시 ",[32,33787,33788],{},"companyType: userType.value || undefined"," 전달",[84,33791,33792,33793],{},"Step 5(가입 완료) 화면 분기:\n",[81,33794,33795,33798],{},[84,33796,33797],{},"사업자: \"사업자등록증 심사가 진행됩니다. 승인 완료 전에는 서비스 이용 및 정보 수정이 제한되며, 결과는 등록하신 휴대폰·이메일로 안내됩니다.\"",[84,33799,33800],{},"개인: \"지금부터 바로 서비스를 이용하실 수 있습니다.\"",[237,33802,32723],{"id":33803},"appmemberinfopanelvue",[81,33805,33806,33813,33821,33829,33837],{},[84,33807,33808,33809,33812],{},"상단 ",[21,33810,33811],{},"승인 상태 배너"," (pending=warning, rejected=danger). pending이면 \"사업자등록증 심사 중입니다 — 승인 완료 전까지 서비스 이용 및 회원 정보 수정이 제한됩니다.\" rejected면 반려 사유 + \"사업자등록증을 다시 제출해 주세요.\"",[84,33814,33815,33818,33819,4343],{},[32,33816,33817],{},"isLocked"," computed (",[32,33820,33726],{},[84,33822,33823,33824],{},"광고성 메일 수신 토글 2개 · 회사 전화번호 입력 · 휴대전화 select+input 2개 · 이메일 변경 버튼 2개(서비스 담당자·결제) · 휴대폰 인증 버튼 · 저장하기 버튼 — ",[21,33825,27744,33826],{},[32,33827,33828],{},":disabled=\"isLocked\"",[84,33830,33831,6219,33833,33836],{},[32,33832,33063],{},[32,33834,33835],{},"c.companyType !== 'personal'","로 조건 수정 (이전엔 bizType 사용)",[84,33838,33839],{},"배너 스타일: 좌측 24px 아이콘 + 우측 굵은 헤더 + 본문, warning\u002Fdanger 색상 변형",[76,33841,33843],{"id":33842},"_75-라이브-e2e-검증-8-시나리오","7.5 라이브 e2e 검증 (8 시나리오)",[101,33845,33846,33856],{},[104,33847,33848],{},[107,33849,33850,33852,33854],{},[110,33851,3846],{},[110,33853,29790],{},[110,33855,22362],{},[126,33857,33858,33872,33884,33896,33910,33922,33934,33946],{},[107,33859,33860,33862,33870],{},[131,33861,4636],{},[131,33863,33864,33865,6219,33867],{},"법인 가입 → ",[32,33866,3987],{},[32,33868,33869],{},"companyType='corp', approvalState='pending'",[131,33871,3869],{},[107,33873,33874,33876,33882],{},[131,33875,4649],{},[131,33877,33878,33881],{},[32,33879,33880],{},"PATCH \u002Fme {name}"," → 403 + \"사업자등록증 심사 승인 후 …\"",[131,33883,3869],{},[107,33885,33886,33888,33894],{},[131,33887,4662],{},[131,33889,33890,33893],{},[32,33891,33892],{},"PATCH \u002Fme\u002Fcompany {adReceive}"," → 403 + 동일 메시지",[131,33895,3869],{},[107,33897,33898,33900,33908],{},[131,33899,4678],{},[131,33901,33902,33903,6219,33905],{},"개인 가입 → ",[32,33904,3987],{},[32,33906,33907],{},"companyType='personal', approvalState='approved'",[131,33909,3869],{},[107,33911,33912,33914,33920],{},[131,33913,4691],{},[131,33915,33916,33917,33919],{},"개인 ",[32,33918,33880],{}," → 200 + 변경 반영",[131,33921,3869],{},[107,33923,33924,33926,33932],{},[131,33925,4708],{},[131,33927,33928,33929,33931],{},"운영자 DB 직접 UPDATE → ",[32,33930,33582],{}," (BackOffice 승인 시뮬레이션)",[131,33933,3869],{},[107,33935,33936,33938,33944],{},[131,33937,4724],{},[131,33939,33940,33941,33943],{},"승인 후 법인 ",[32,33942,32697],{}," 재시도 → 200 + 변경 반영",[131,33945,3869],{},[107,33947,33948,33950,33957],{},[131,33949,23932],{},[131,33951,33952,33953,33956],{},"반려 시뮬레이션 (",[32,33954,33955],{},"approval_state='rejected', rejected_reason='…'",") → PATCH 시도 → 403 + 사유 메시지 포함",[131,33958,3869],{},[18,33960,33961],{},"검증 데이터(법인-…·개인-… 4건) cleanup 완료.",[76,33963,33965],{"id":33964},"_76-산출물","7.6 산출물",[81,33967,33968,33990,34003],{},[84,33969,32568,33970,89,33973,33976,33977,33979,33980,33982,33983,33986,33987],{},[32,33971,33972],{},"malgn-noti-api: 7…",[32,33974,33975],{},"0005_company_approval.sql"," 신규 · ",[32,33978,28258],{}," company 확장 · ",[32,33981,32586],{}," signup 분기 · ",[32,33984,33985],{},"me.ts"," 응답+차단. Workers 배포 #15 Version ",[32,33988,33989],{},"6e47d50b-0225-41d9-8bc8-598045659df8",[84,33991,33415,33992,33994,33995,33997,33998,34000,34001],{},[32,33993,11040],{}," 타입 · ",[32,33996,14163],{}," companyType 전달 + Step 5 분기 · ",[32,33999,32723],{}," 배너 + isLocked + 모든 입력 disabled. Pages 배포 #64 alias ",[32,34002,33511],{},[84,34004,34005,34006,34008],{},"WBS 갱신: 5-3C-6(",[32,34007,30486],{}," 전달·저장 + 개인 유형 화면 분기) ⚪→🟢 + 새 항목 5-3C-17(승인 게이트) ✅",[76,34010,34012],{"id":34011},"_77-알려진-한계-후속-작업","7.7 알려진 한계 \u002F 후속 작업",[81,34014,34015,34025,34041,34054,34062,34070],{},[84,34016,34017,34020,34021,34024],{},[21,34018,34019],{},"운영자단 승인 화면 미구현"," — 현재 라이브 DB 직접 UPDATE로만 승인\u002F반려 가능. 운영자단(",[32,34022,34023],{},"\u002Fadmin\u002Fmember\u002Fcompany\u002F[id]",")에 승인·반려(사유 입력) UI 신설 필요. WBS 5-4-3.",[84,34026,34027,34030,34031,34033,34034,34037,34038,34040],{},[21,34028,34029],{},"발송·이력 등 다른 도메인 라우트 차단"," — 현재는 ",[32,34032,3987],{}," PATCH만 차단. 발송(",[32,34035,34036],{},"POST \u002Fsend\u002F*","), 캠페인, 발신정보 변경 등도 미승인 차단 필요. ",[32,34039,33745],{}," 미들웨어로 일관화 후 적용 권장. 후속.",[84,34042,34043,89,34046,4473,34048,4473,34050,34053],{},[21,34044,34045],{},"사용자단 다른 화면 disabled",[32,34047,18635],{},[32,34049,18609],{},[32,34051,34052],{},"\u002Fsend\u002F*"," 등도 isLocked일 때 차단\u002F안내 필요. 화면별 점검 후속.",[84,34055,34056,34030,34059,34061],{},[21,34057,34058],{},"GNB·홈 글로벌 안내",[32,34060,3991],{},"에만 배너. 모든 화면 상단(GNB)에 글로벌 안내 띠 검토.",[84,34063,34064,34069],{},[21,34065,34066,34068],{},[32,34067,33745],{}," 미들웨어 추출"," — 현재는 핸들러 내부 인라인. 도메인 라우트 전부 적용 시점에 별도 헬퍼로 분리.",[84,34071,34072,34075],{},[21,34073,34074],{},"이메일·SMS 자동 안내"," — 승인\u002F반려 처리 시 사용자에게 자동 발송. NHN 자격증명 등록 후 trigger.",[73,34077],{},[11,34079,34081,34082,34084],{"id":34080},"_8-승인-게이트-전-도메인-일관-적용-requireapproved-미들웨어-배포-16","§8. 승인 게이트 전 도메인 일관 적용 — ",[32,34083,33745],{}," 미들웨어 (배포 #16)",[76,34086,27637],{"id":34087},"한-줄-7",[18,34089,34090,34091,4536,34093,34095,34096,34102,34103,34106,34107,34110,34111,34113,34114,34117],{},"§7에서 ",[32,34092,32697],{},[32,34094,32700],{},"에만 인라인 차단했던 승인 게이트를 ",[21,34097,34098,34099,34101],{},"공용 미들웨어 ",[32,34100,33745],{},"로 추출","하고 ",[21,34104,34105],{},"18개 도메인 라우트에 일괄 적용",". 정책은 ",[32,34108,34109],{},"mutate-only"," — POST\u002FPATCH\u002FPUT\u002FDELETE만 차단(GET 조회는 통과). ",[32,34112,4073],{},"만 예외 — 승인 관련 문의는 미승인 상태에서도 작성 가능. 자동화 스크립트로 18 라우트의 import + use 라인을 일관 갱신, typecheck 통과, Workers 배포 #16(Version ",[32,34115,34116],{},"798bf6f5-bac2-4912-abdd-4af9718c1a93","). 라이브 e2e 6 시나리오 통과(GET 통과 \u002F POST 4건 403 \u002F 개인 가입은 정상 생성 \u002F 문의 작성은 차단 안 됨).",[76,34119,34121,34122],{"id":34120},"_81-미들웨어-srcmiddlewareapprovalts","8.1 미들웨어 — ",[32,34123,34124],{},"src\u002Fmiddleware\u002Fapproval.ts",[18,34126,34127,34130],{},[26,34128,34124],{"href":34129},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fmiddleware\u002Fapproval.ts"," 신규:",[5251,34132,34134],{"className":7962,"code":34133,"language":7964,"meta":4208,"style":4208},"const MUTATE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE'])\n\nexport function requireApproved(opts: { method?: 'mutate-only' | 'all' } = {}): MiddlewareHandler\u003CAuthEnv> {\n  const mode = opts.method ?? 'mutate-only'\n  return async (c, next) => {\n    if (mode === 'mutate-only' && !MUTATE_METHODS.has(c.req.method)) return next()\n    const { companyId } = authCtx(c)\n    const db = await getDb(c.env, c.executionCtx)\n    const rows = await db.select({...}).from(company).where(eq(company.id, companyId)).limit(1)\n    const row = rows[0]\n    if (!row) throw errors.notFound('company')\n    if (row.approvalState !== 'approved') {\n      throw errors.forbidden(\u002F* pending \u002F rejected 별 메시지 *\u002F)\n    }\n    await next()\n  }\n}\n",[32,34135,34136,34175,34179,34231,34249,34271,34308,34327,34344,34394,34410,34437,34451,34468,34473,34482,34486],{"__ignoreMap":4208},[7968,34137,34138,34140,34143,34145,34148,34151,34154,34157,34159,34162,34164,34167,34169,34172],{"class":5110,"line":7970},[7968,34139,9204],{"class":7973},[7968,34141,34142],{"class":8892}," MUTATE_METHODS",[7968,34144,9210],{"class":7973},[7968,34146,34147],{"class":7973}," new",[7968,34149,34150],{"class":7980}," Set",[7968,34152,34153],{"class":7984},"([",[7968,34155,34156],{"class":7993},"'POST'",[7968,34158,4473],{"class":7984},[7968,34160,34161],{"class":7993},"'PATCH'",[7968,34163,4473],{"class":7984},[7968,34165,34166],{"class":7993},"'PUT'",[7968,34168,4473],{"class":7984},[7968,34170,34171],{"class":7993},"'DELETE'",[7968,34173,34174],{"class":7984},"])\n",[7968,34176,34177],{"class":5110,"line":4212},[7968,34178,8288],{"emptyLinePlaceholder":4259},[7968,34180,34181,34183,34186,34189,34191,34194,34196,34198,34201,34203,34206,34208,34211,34213,34215,34218,34220,34223,34225,34228],{"class":5110,"line":4209},[7968,34182,7974],{"class":7973},[7968,34184,34185],{"class":7973}," function",[7968,34187,34188],{"class":7980}," requireApproved",[7968,34190,6100],{"class":7984},[7968,34192,34193],{"class":8939},"opts",[7968,34195,9148],{"class":7973},[7968,34197,27200],{"class":7984},[7968,34199,34200],{"class":8939},"method",[7968,34202,9160],{"class":7973},[7968,34204,34205],{"class":7993}," 'mutate-only'",[7968,34207,21177],{"class":7973},[7968,34209,34210],{"class":7993}," 'all'",[7968,34212,25860],{"class":7984},[7968,34214,8254],{"class":7973},[7968,34216,34217],{"class":7984}," {})",[7968,34219,9148],{"class":7973},[7968,34221,34222],{"class":7980}," MiddlewareHandler",[7968,34224,8241],{"class":7984},[7968,34226,34227],{"class":7980},"AuthEnv",[7968,34229,34230],{"class":7984},"> {\n",[7968,34232,34233,34236,34239,34241,34244,34246],{"class":5110,"line":4219},[7968,34234,34235],{"class":7973},"  const",[7968,34237,34238],{"class":8892}," mode",[7968,34240,9210],{"class":7973},[7968,34242,34243],{"class":7984}," opts.method ",[7968,34245,31675],{"class":7973},[7968,34247,34248],{"class":7993}," 'mutate-only'\n",[7968,34250,34251,34254,34257,34259,34261,34263,34265,34267,34269],{"class":5110,"line":8291},[7968,34252,34253],{"class":7973},"  return",[7968,34255,34256],{"class":7973}," async",[7968,34258,6685],{"class":7984},[7968,34260,21430],{"class":8939},[7968,34262,4473],{"class":7984},[7968,34264,21435],{"class":8939},[7968,34266,21438],{"class":7984},[7968,34268,9426],{"class":7973},[7968,34270,8887],{"class":7984},[7968,34272,34273,34276,34279,34281,34283,34286,34289,34292,34294,34297,34300,34303,34306],{"class":5110,"line":8301},[7968,34274,34275],{"class":7973},"    if",[7968,34277,34278],{"class":7984}," (mode ",[7968,34280,31645],{"class":7973},[7968,34282,34205],{"class":7993},[7968,34284,34285],{"class":7973}," &&",[7968,34287,34288],{"class":7973}," !",[7968,34290,34291],{"class":8892},"MUTATE_METHODS",[7968,34293,4703],{"class":7984},[7968,34295,34296],{"class":7980},"has",[7968,34298,34299],{"class":7984},"(c.req.method)) ",[7968,34301,34302],{"class":7973},"return",[7968,34304,34305],{"class":7980}," next",[7968,34307,9268],{"class":7984},[7968,34309,34310,34313,34315,34317,34319,34321,34324],{"class":5110,"line":8310},[7968,34311,34312],{"class":7973},"    const",[7968,34314,27200],{"class":7984},[7968,34316,22750],{"class":8892},[7968,34318,25860],{"class":7984},[7968,34320,8254],{"class":7973},[7968,34322,34323],{"class":7980}," authCtx",[7968,34325,34326],{"class":7984},"(c)\n",[7968,34328,34329,34331,34334,34336,34338,34341],{"class":5110,"line":8321},[7968,34330,34312],{"class":7973},[7968,34332,34333],{"class":8892}," db",[7968,34335,9210],{"class":7973},[7968,34337,9280],{"class":7973},[7968,34339,34340],{"class":7980}," getDb",[7968,34342,34343],{"class":7984},"(c.env, c.executionCtx)\n",[7968,34345,34346,34348,34351,34353,34355,34358,34361,34364,34366,34369,34371,34374,34377,34379,34382,34385,34388,34390,34392],{"class":5110,"line":8332},[7968,34347,34312],{"class":7973},[7968,34349,34350],{"class":8892}," rows",[7968,34352,9210],{"class":7973},[7968,34354,9280],{"class":7973},[7968,34356,34357],{"class":7984}," db.",[7968,34359,34360],{"class":7980},"select",[7968,34362,34363],{"class":7984},"({",[7968,34365,9432],{"class":7973},[7968,34367,34368],{"class":7984},"}).",[7968,34370,9490],{"class":7980},[7968,34372,34373],{"class":7984},"(company).",[7968,34375,34376],{"class":7980},"where",[7968,34378,6100],{"class":7984},[7968,34380,34381],{"class":7980},"eq",[7968,34383,34384],{"class":7984},"(company.id, companyId)).",[7968,34386,34387],{"class":7980},"limit",[7968,34389,6100],{"class":7984},[7968,34391,4636],{"class":8892},[7968,34393,9563],{"class":7984},[7968,34395,34396,34398,34401,34403,34406,34408],{"class":5110,"line":8343},[7968,34397,34312],{"class":7973},[7968,34399,34400],{"class":8892}," row",[7968,34402,9210],{"class":7973},[7968,34404,34405],{"class":7984}," rows[",[7968,34407,5354],{"class":8892},[7968,34409,27327],{"class":7984},[7968,34411,34412,34414,34416,34418,34421,34424,34427,34430,34432,34435],{"class":5110,"line":8349},[7968,34413,34275],{"class":7973},[7968,34415,6685],{"class":7984},[7968,34417,31727],{"class":7973},[7968,34419,34420],{"class":7984},"row) ",[7968,34422,34423],{"class":7973},"throw",[7968,34425,34426],{"class":7984}," errors.",[7968,34428,34429],{"class":7980},"notFound",[7968,34431,6100],{"class":7984},[7968,34433,34434],{"class":7993},"'company'",[7968,34436,9563],{"class":7984},[7968,34438,34439,34441,34444,34446,34449],{"class":5110,"line":8454},[7968,34440,34275],{"class":7973},[7968,34442,34443],{"class":7984}," (row.approvalState ",[7968,34445,33225],{"class":7973},[7968,34447,34448],{"class":7993}," 'approved'",[7968,34450,21462],{"class":7984},[7968,34452,34453,34456,34458,34461,34463,34466],{"class":5110,"line":8472},[7968,34454,34455],{"class":7973},"      throw",[7968,34457,34426],{"class":7984},[7968,34459,34460],{"class":7980},"forbidden",[7968,34462,6100],{"class":7984},[7968,34464,34465],{"class":8398},"\u002F* pending \u002F rejected 별 메시지 *\u002F",[7968,34467,9563],{"class":7984},[7968,34469,34470],{"class":5110,"line":8494},[7968,34471,34472],{"class":7984},"    }\n",[7968,34474,34475,34478,34480],{"class":5110,"line":8515},[7968,34476,34477],{"class":7973},"    await",[7968,34479,34305],{"class":7980},[7968,34481,9268],{"class":7984},[7968,34483,34484],{"class":5110,"line":8525},[7968,34485,21497],{"class":7984},[7968,34487,34488],{"class":5110,"line":8531},[7968,34489,8987],{"class":7984},[81,34491,34492,34498,34504,34513],{},[84,34493,34494,34495,34497],{},"기본 ",[32,34496,34109],{}," — GET·HEAD·OPTIONS는 통과 (조회 허용)",[84,34499,34500,34503],{},[32,34501,34502],{},"'all'"," 옵션 — 조회까지 차단 (필요 시)",[84,34505,34506,34508,34509,34512],{},[32,34507,21781],{}," 다음 체인 — ",[32,34510,34511],{},"authCtx","로 companyId 획득",[84,34514,34515],{},"매 요청 1회 SELECT (Hyperdrive 캐시 효과 기대)",[76,34517,34519],{"id":34518},"_82-18-라우트-일괄-적용","8.2 18 라우트 일괄 적용",[18,34521,34522],{},"대상:",[101,34524,34525,34536],{},[104,34526,34527],{},[107,34528,34529,34531,34534],{},[110,34530,22556],{},[110,34532,34533],{},"변수명",[110,34535,7718],{},[126,34537,34538,34561,34574,34587],{},[107,34539,34540,34543,34558],{},[131,34541,34542],{},"send · contacts · contact-groups · optout-entries · sender-phones · rcs-brands · email-domains · push-certs · kakao-sender-profiles · kakao-profile-groups · optout-080-numbers · templates · template-categories · landing-pages · flow-definitions · export-jobs · payment-methods · company-settings",[131,34544,34545,34548,34549,4536,34552,4536,34555,4343],{},[32,34546,34547],{},"app"," 또는 도메인별(",[32,34550,34551],{},"contacts",[32,34553,34554],{},"groups",[32,34556,34557],{},"phones",[131,34559,34560],{},"18종",[107,34562,34563,34569,34571],{},[131,34564,34565,34568],{},[21,34566,34567],{},"예외",": inquiries",[131,34570,322],{},[131,34572,34573],{},"승인 관련 문의 가능해야 함",[107,34575,34576,34582,34584],{},[131,34577,34578,34581],{},[21,34579,34580],{},"이미 §7",": me",[131,34583,322],{},[131,34585,34586],{},"인라인 차단",[107,34588,34589,34595,34597],{},[131,34590,34591,34594],{},[21,34592,34593],{},"읽기 전용",": dispatch-history · credit-ledger",[131,34596,322],{},[131,34598,34599],{},"GET만 정의, 차단 무영향",[18,34601,34602],{},"자동화 — 변수명을 grep으로 식별 후 perl로 import + use 2 군데 일괄 갱신:",[5251,34604,34606],{"className":10197,"code":34605,"language":10199,"meta":4208,"style":4208},"for f in \"${TARGETS[@]}\"; do\n  varname=$(grep -oE \"[a-zA-Z]+\\.use\\('\\\\*', requireAuth\\(\\)\\)\" \"src\u002Froutes\u002F$f.ts\" | head -1 | cut -d. -f1)\n  perl -i -pe \"...\"  # import 라인 + use 라인 갱신\ndone\n",[32,34607,34608,34639,34691,34708],{"__ignoreMap":4208},[7968,34609,34610,34613,34616,34619,34622,34625,34628,34631,34634,34636],{"class":5110,"line":7970},[7968,34611,34612],{"class":7973},"for",[7968,34614,34615],{"class":7984}," f ",[7968,34617,34618],{"class":7973},"in",[7968,34620,34621],{"class":7993}," \"${",[7968,34623,34624],{"class":7984},"TARGETS",[7968,34626,34627],{"class":7993},"[",[7968,34629,34630],{"class":7973},"@",[7968,34632,34633],{"class":7993},"]}\"",[7968,34635,9154],{"class":7984},[7968,34637,34638],{"class":7973},"do\n",[7968,34640,34641,34644,34646,34648,34650,34653,34656,34659,34662,34665,34668,34671,34673,34676,34679,34681,34683,34686,34689],{"class":5110,"line":4212},[7968,34642,34643],{"class":7984},"  varname",[7968,34645,8254],{"class":7973},[7968,34647,21168],{"class":7984},[7968,34649,21908],{"class":7980},[7968,34651,34652],{"class":8892}," -oE",[7968,34654,34655],{"class":7993}," \"[a-zA-Z]+\\.use\\('",[7968,34657,34658],{"class":8892},"\\\\",[7968,34660,34661],{"class":7993},"*', requireAuth\\(\\)\\)\"",[7968,34663,34664],{"class":7993}," \"src\u002Froutes\u002F",[7968,34666,34667],{"class":7984},"$f",[7968,34669,34670],{"class":7993},".ts\"",[7968,34672,21177],{"class":7973},[7968,34674,34675],{"class":7980}," head",[7968,34677,34678],{"class":8892}," -1",[7968,34680,21177],{"class":7973},[7968,34682,21180],{"class":7980},[7968,34684,34685],{"class":8892}," -d.",[7968,34687,34688],{"class":8892}," -f1",[7968,34690,9563],{"class":7984},[7968,34692,34693,34696,34699,34702,34705],{"class":5110,"line":4209},[7968,34694,34695],{"class":7980},"  perl",[7968,34697,34698],{"class":8892}," -i",[7968,34700,34701],{"class":8892}," -pe",[7968,34703,34704],{"class":7993}," \"...\"",[7968,34706,34707],{"class":8398},"  # import 라인 + use 라인 갱신\n",[7968,34709,34710],{"class":5110,"line":4219},[7968,34711,34712],{"class":7973},"done\n",[18,34714,34715,34716,34719],{},"각 파일에 ",[32,34717,34718],{},"requireApproved"," 2번 등장(import + use) 확인 — 18 파일 × 2 = 36 매치.",[76,34721,34723],{"id":34722},"_83-라이브-e2e-production","8.3 라이브 e2e (Production)",[101,34725,34726,34736],{},[104,34727,34728],{},[107,34729,34730,34732,34734],{},[110,34731,3846],{},[110,34733,29790],{},[110,34735,22362],{},[126,34737,34738,34751,34763,34774,34785,34797],{},[107,34739,34740,34742,34748],{},[131,34741,4636],{},[131,34743,34744,34745],{},"미승인 사업자(corp) ",[21,34746,34747],{},"GET \u002Fcontacts",[131,34749,34750],{},"✅ 200 — 조회 허용",[107,34752,34753,34755,34760],{},[131,34754,4649],{},[131,34756,34757,34758],{},"미승인 사업자 ",[21,34759,22013],{},[131,34761,34762],{},"✅ 403 \"사업자등록증 심사 승인 후 이용할 수 있습니다.\"",[107,34764,34765,34767,34771],{},[131,34766,4662],{},[131,34768,34757,34769],{},[21,34770,22032],{},[131,34772,34773],{},"✅ 403 동일 메시지",[107,34775,34776,34778,34782],{},[131,34777,4678],{},[131,34779,34757,34780],{},[21,34781,23428],{},[131,34783,34784],{},"✅ 403 — 발송 차단",[107,34786,34787,34789,34794],{},[131,34788,4691],{},[131,34790,34791,34792],{},"개인(approved) ",[21,34793,22013],{},[131,34795,34796],{},"✅ 201 정상 생성",[107,34798,34799,34801,34806],{},[131,34800,4708],{},[131,34802,34757,34803],{},[21,34804,34805],{},"POST \u002Finquiries",[131,34807,34808],{},"✅ 차단 안 됨(400은 body validation) — 예외 정상",[18,34810,34811],{},"검증 데이터 cleanup 완료.",[76,34813,34815],{"id":34814},"_84-산출물","8.4 산출물",[81,34817,34818,34826],{},[84,34819,34820,34823,34824],{},[32,34821,34822],{},"malgn-noti-api: ?"," — 19 파일 변경(1 신규 + 18 라우트). Workers 배포 #16 Version ",[32,34825,34116],{},[84,34827,34828],{},"WBS 5-3C-17은 이미 ✅, 추가 갱신은 없음(같은 정책의 확장 적용)",[76,34830,34832],{"id":34831},"_85-알려진-한계-다음-작업","8.5 알려진 한계 \u002F 다음 작업",[81,34834,34835,34840,34850,34859],{},[84,34836,34837,34839],{},[21,34838,34045],{}," (3번) — 발송·이력·주소록 등 페이지에 접근 시 안내 배너 또는 기능 락 UI. 아직 페이지가 모두 목업 데이터 기반이라 백엔드 차단이 화면에 즉시 반영 안 됨 — 추후 페이지별 백엔드 연동 시 일관 처리 또는 별도 글로벌 안내 띠.",[84,34841,34842,34845,34846,34849],{},[21,34843,34844],{},"GNB 글로벌 안내 띠"," (4번) — ",[32,34847,34848],{},"auth.tenant.approvalState","를 GNB·셸 컴포넌트에서 구독해 모든 페이지 상단에 노출.",[84,34851,34852,34855,34856,34858],{},[21,34853,34854],{},"요청당 DB SELECT 1회"," — Hyperdrive 캐시로 빠르지만, 트래픽 증가 시 JWT claim에 ",[32,34857,33685],{},"를 넣어 단축 가능. 단 승인 후 사용자가 재로그인하기 전엔 갱신 안 됨 → 단기적으로 미적용 권장.",[84,34860,34861,34866],{},[21,34862,34863,34865],{},[32,34864,4073],{}," 외 예외"," — 추후 운영자단에서 첨부 파일 업로드(R2)·결제(PG 콜백) 등 필요 시 케이스별 검토.",[73,34868],{},[11,34870,34872,34873,34875],{"id":34871},"_9-사용자단-승인-게이트-ui-일관화-글로벌-띠-라우트-가드-home-안내-배포-65","§9. 사용자단 승인 게이트 UI 일관화 — 글로벌 띠 + 라우트 가드 + ",[32,34874,11278],{}," 안내 (배포 #65)",[76,34877,27637],{"id":34878},"한-줄-8",[18,34880,34881,34882,51,34885,34888,34889,51,34892,34895,34896,34898,34899,34904,34905,34908,34909,5606],{},"§7·§8에서 백엔드(DB + 18 라우트 차단)로 정책 인프라화 완료. 사용자단도 일관 — ",[21,34883,34884],{},"글로벌 띠",[32,34886,34887],{},"AppApprovalBanner","(layout 최상단·GNB 위)로 모든 페이지에 승인 상태 알림 + ",[21,34890,34891],{},"글로벌 라우트 가드",[32,34893,34894],{},"middleware\u002Fapproval.global.ts","로 차단 페이지 접근 시 ",[32,34897,3991],{},"로 자동 리다이렉트 + ",[21,34900,34901,34903],{},[32,34902,11278],{}," 페이지","는 미승인 시 KPI\u002F채널\u002F통계 카드 대신 전체 화면 큰 안내(",[32,34906,34907],{},"approval-hero",")로 교체. Pages 배포 #65 (alias ",[32,34910,34911],{},"2eec9e0b.malgn-noti.pages.dev",[76,34913,34915,34916],{"id":34914},"_91-글로벌-띠-appapprovalbannervue","9.1 글로벌 띠 — ",[32,34917,34918],{},"AppApprovalBanner.vue",[18,34920,34921,34130],{},[26,34922,34924],{"href":34923},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppApprovalBanner.vue","app\u002Fcomponents\u002FAppApprovalBanner.vue",[81,34926,34927,34937,34944,34951,34957],{},[84,34928,34929,34931,34932,4361,34934,34936],{},[32,34930,34848],{},"를 구독 → ",[32,34933,27043],{},[32,34935,33601],{},"일 때만 노출",[84,34938,34939,34940,34943],{},"pending: 노란색 띠(",[32,34941,34942],{},"#fff8e6"," + warning border) + 시계 아이콘",[84,34945,34946,34947,34950],{},"rejected: 빨간색 띠(",[32,34948,34949],{},"#fef2f2"," + danger border) + X 아이콘 + 반려 사유 인용",[84,34952,34953,34954,34956],{},"우측 버튼 — pending이면 \"회원 정보\", rejected면 \"다시 제출하기\" → ",[32,34955,3991],{},"로 이동",[84,34958,34959],{},"반응형(720px 미만 wrap)",[18,34961,34962,34965,34966,34968],{},[26,34963,24750],{"href":34964},"..\u002F..\u002Fapp\u002Flayouts\u002Fdefault.vue","에 마운트 — ",[32,34967,11316],{}," 위, layout 최상단:",[5251,34970,34972],{"className":8232,"code":34971,"language":8234,"meta":4208,"style":4208},"\u003CAppApprovalBanner \u002F>\n\u003CAppGnb \u002F>\n\u003Cmain>\u003Cslot \u002F>\u003C\u002Fmain>\n\u003CAppFooter \u002F>\n",[32,34973,34974,34982,34990,35003],{"__ignoreMap":4208},[7968,34975,34976,34978,34980],{"class":5110,"line":7970},[7968,34977,8241],{"class":7984},[7968,34979,34887],{"class":8244},[7968,34981,10017],{"class":7984},[7968,34983,34984,34986,34988],{"class":5110,"line":4212},[7968,34985,8241],{"class":7984},[7968,34987,11316],{"class":8244},[7968,34989,10017],{"class":7984},[7968,34991,34992,34994,34996,34999,35001],{"class":5110,"line":4209},[7968,34993,8241],{"class":7984},[7968,34995,12995],{"class":8244},[7968,34997,34998],{"class":7984},">\u003Cslot \u002F>\u003C\u002F",[7968,35000,12995],{"class":8244},[7968,35002,8260],{"class":7984},[7968,35004,35005,35007,35009],{"class":5110,"line":4219},[7968,35006,8241],{"class":7984},[7968,35008,13094],{"class":8244},[7968,35010,10017],{"class":7984},[18,35012,35013],{},"→ 모든 인증 페이지에서 자동 노출.",[76,35015,35017,35018],{"id":35016},"_92-글로벌-라우트-가드-middlewareapprovalglobalts","9.2 글로벌 라우트 가드 — ",[32,35019,34894],{},[18,35021,35022,34130],{},[26,35023,35025],{"href":35024},"..\u002F..\u002Fapp\u002Fmiddleware\u002Fapproval.global.ts","app\u002Fmiddleware\u002Fapproval.global.ts",[5251,35027,35029],{"className":7962,"code":35028,"language":7964,"meta":4208,"style":4208},"const ALLOWED_PREFIXES = ['\u002Faccount', '\u002Fhome', '\u002Fhelp', '\u002Fguide', '\u002Fwbs', '\u002Finquiry']\n\nexport default defineNuxtRouteMiddleware((to) => {\n  if (to.meta.auth === false) return\n  const state = useAuthStore().tenant?.approvalState\n  if (!state || state === 'approved') return  \u002F\u002F 미hydrate면 통과\n  if (ALLOWED_PREFIXES.some(p => to.path === p || to.path.startsWith(`${p}\u002F`))) return\n  return navigateTo('\u002Faccount\u002Fsettings')\n})\n",[32,35030,35031,35072,35076,35097,35113,35127,35155,35206,35220],{"__ignoreMap":4208},[7968,35032,35033,35035,35038,35040,35042,35045,35047,35050,35052,35055,35057,35060,35062,35065,35067,35070],{"class":5110,"line":7970},[7968,35034,9204],{"class":7973},[7968,35036,35037],{"class":8892}," ALLOWED_PREFIXES",[7968,35039,9210],{"class":7973},[7968,35041,27321],{"class":7984},[7968,35043,35044],{"class":7993},"'\u002Faccount'",[7968,35046,4473],{"class":7984},[7968,35048,35049],{"class":7993},"'\u002Fhome'",[7968,35051,4473],{"class":7984},[7968,35053,35054],{"class":7993},"'\u002Fhelp'",[7968,35056,4473],{"class":7984},[7968,35058,35059],{"class":7993},"'\u002Fguide'",[7968,35061,4473],{"class":7984},[7968,35063,35064],{"class":7993},"'\u002Fwbs'",[7968,35066,4473],{"class":7984},[7968,35068,35069],{"class":7993},"'\u002Finquiry'",[7968,35071,27327],{"class":7984},[7968,35073,35074],{"class":5110,"line":4212},[7968,35075,8288],{"emptyLinePlaceholder":4259},[7968,35077,35078,35080,35082,35085,35088,35091,35093,35095],{"class":5110,"line":4209},[7968,35079,7974],{"class":7973},[7968,35081,7977],{"class":7973},[7968,35083,35084],{"class":7980}," defineNuxtRouteMiddleware",[7968,35086,35087],{"class":7984},"((",[7968,35089,35090],{"class":8939},"to",[7968,35092,21438],{"class":7984},[7968,35094,9426],{"class":7973},[7968,35096,8887],{"class":7984},[7968,35098,35099,35101,35104,35106,35109,35111],{"class":5110,"line":4219},[7968,35100,21447],{"class":7973},[7968,35102,35103],{"class":7984}," (to.meta.auth ",[7968,35105,31645],{"class":7973},[7968,35107,35108],{"class":8892}," false",[7968,35110,21438],{"class":7984},[7968,35112,31709],{"class":7973},[7968,35114,35115,35117,35120,35122,35124],{"class":5110,"line":8291},[7968,35116,34235],{"class":7973},[7968,35118,35119],{"class":8892}," state",[7968,35121,9210],{"class":7973},[7968,35123,9403],{"class":7980},[7968,35125,35126],{"class":7984},"().tenant?.approvalState\n",[7968,35128,35129,35131,35133,35135,35138,35141,35144,35146,35148,35150,35152],{"class":5110,"line":8301},[7968,35130,21447],{"class":7973},[7968,35132,6685],{"class":7984},[7968,35134,31727],{"class":7973},[7968,35136,35137],{"class":7984},"state ",[7968,35139,35140],{"class":7973},"||",[7968,35142,35143],{"class":7984}," state ",[7968,35145,31645],{"class":7973},[7968,35147,34448],{"class":7993},[7968,35149,21438],{"class":7984},[7968,35151,34302],{"class":7973},[7968,35153,35154],{"class":8398},"  \u002F\u002F 미hydrate면 통과\n",[7968,35156,35157,35159,35161,35164,35166,35169,35171,35173,35175,35178,35180,35183,35185,35188,35191,35193,35196,35198,35201,35204],{"class":5110,"line":8310},[7968,35158,21447],{"class":7973},[7968,35160,6685],{"class":7984},[7968,35162,35163],{"class":8892},"ALLOWED_PREFIXES",[7968,35165,4703],{"class":7984},[7968,35167,35168],{"class":7980},"some",[7968,35170,6100],{"class":7984},[7968,35172,18],{"class":8939},[7968,35174,28485],{"class":7973},[7968,35176,35177],{"class":7984}," to.path ",[7968,35179,31645],{"class":7973},[7968,35181,35182],{"class":7984}," p ",[7968,35184,35140],{"class":7973},[7968,35186,35187],{"class":7984}," to.path.",[7968,35189,35190],{"class":7980},"startsWith",[7968,35192,6100],{"class":7984},[7968,35194,35195],{"class":7993},"`${",[7968,35197,18],{"class":7984},[7968,35199,35200],{"class":7993},"}\u002F`",[7968,35202,35203],{"class":7984},"))) ",[7968,35205,31709],{"class":7973},[7968,35207,35208,35210,35213,35215,35218],{"class":5110,"line":8321},[7968,35209,34253],{"class":7973},[7968,35211,35212],{"class":7980}," navigateTo",[7968,35214,6100],{"class":7984},[7968,35216,35217],{"class":7993},"'\u002Faccount\u002Fsettings'",[7968,35219,9563],{"class":7984},[7968,35221,35222],{"class":5110,"line":8332},[7968,35223,8007],{"class":7984},[18,35225,35226],{},"허용 경로:",[81,35228,35229,35234,35239,35248],{},[84,35230,35231,35233],{},[32,35232,5699],{}," — 회원 정보·승인 안내·재제출",[84,35235,35236,35238],{},[32,35237,11278],{}," — 큰 안내 카드(다음 절)",[84,35240,35241,4536,35243,4536,35245,35247],{},[32,35242,5744],{},[32,35244,4457],{},[32,35246,26863],{}," — 정적 문서",[84,35249,35250,4536,35252,35254],{},[32,35251,18473],{},[32,35253,16882],{}," — 1:1 문의 (백엔드도 §8에서 예외)",[18,35256,35257,35258,8864],{},"차단 경로(자동 리다이렉트 → ",[32,35259,3991],{},[81,35261,35262],{},[84,35263,35264,4420,35266,4420,35268,4420,35271,4420,35274,4420,35277,4420,35280],{},[32,35265,34052],{},[32,35267,22063],{},[32,35269,35270],{},"\u002Fcontacts\u002F*",[32,35272,35273],{},"\u002Fsender\u002F*",[32,35275,35276],{},"\u002Fmanage\u002F*",[32,35278,35279],{},"\u002Fcampaign*",[32,35281,35282],{},"\u002Fcharge*",[18,35284,35285,35286,35289,35290,35292],{},"SSR 안전: store 미hydrate(",[32,35287,35288],{},"state === undefined",")면 통과. 클라이언트 부트스트랩이 ",[32,35291,28976],{},"로 hydrate한 다음 재진입 시 작동.",[76,35294,35296,35297,35299],{"id":35295},"_93-home-페이지-미승인-분기","9.3 ",[32,35298,11278],{}," 페이지 미승인 분기",[18,35301,35302,89,35305,35308],{},[26,35303,13277],{"href":35304},"..\u002F..\u002Fapp\u002Fpages\u002Fhome.vue",[32,35306,35307],{},"v-if=\"isLocked\"","로 두 화면 분기:",[18,35310,35311,35312,8864],{},"미승인 화면(",[32,35313,34907],{},[81,35315,35316,35319,35322,35325,35328],{},[84,35317,35318],{},"중앙 정렬 큰 카드 (max-width 720px)",[84,35320,35321],{},"72px 시계\u002FX 아이콘 + 24px 제목",[84,35323,35324],{},"본문(pending: \"심사 중, 보통 영업일 1~2일\" \u002F rejected: 사유 인용)",[84,35326,35327],{},"CTA 2개: \"회원 정보 확인하기\u002F다시 제출하기\" + \"1:1 문의 작성\"",[84,35329,35330],{},"하단 현재 상태 메타",[18,35332,35333],{},"승인 상태(기본):",[81,35335,35336],{},[84,35337,35338],{},"기존 KPI·채널·통계 카드 그대로 (변경 없음)",[76,35340,35342],{"id":35341},"_94-흐름-정리-가입-후-미승인-사용자가-경험하는-ux","9.4 흐름 정리 — 가입 후 미승인 사용자가 경험하는 UX",[6674,35344,35345,35354,35361,35367,35378,35385],{},[84,35346,35347,35350,35351,35353],{},[21,35348,35349],{},"회원가입 완료"," → 자동 로그인 → ",[32,35352,11278],{},"으로 이동",[84,35355,35356,35360],{},[21,35357,35358],{},[32,35359,11278],{},": 큰 안내 카드 \"사업자등록증 심사 중입니다 … 보통 영업일 1~2일\" + CTA \"회원 정보 확인하기\"",[84,35362,35363,35366],{},[21,35364,35365],{},"상단 글로벌 띠",": 모든 페이지에 항상 노출 (회원 정보·문의 등 허용 페이지에서도)",[84,35368,35369,47,35372,35374,35375,35377],{},[21,35370,35371],{},"차단 페이지 시도",[32,35373,12271],{}," 등 클릭 시 미들웨어가 즉시 ",[32,35376,3991],{},"로 리다이렉트",[84,35379,35380,35384],{},[21,35381,35382],{},[32,35383,3991],{},": 가입 정보 상단 배너(§7) + 모든 입력 disabled",[84,35386,35387,35390],{},[21,35388,35389],{},"승인 완료 후",": store 갱신 시점부터 띠 사라짐 + 모든 페이지 정상 접근 가능",[76,35392,35394],{"id":35393},"_95-산출물","9.5 산출물",[81,35396,35397,35410],{},[84,35398,33415,35399,35401,35402,35404,35405,35401,35407,35409],{},[32,35400,34924],{}," 신규 + ",[32,35403,24750],{}," 마운트 + ",[32,35406,35025],{},[32,35408,13277],{}," 미승인 분기",[84,35411,35412,35413],{},"Pages 배포 #65 alias ",[32,35414,34911],{},[76,35416,35418],{"id":35417},"_96-알려진-한계-다음-작업","9.6 알려진 한계 \u002F 다음 작업",[81,35420,35421,35431,35437,35450,35456],{},[84,35422,35423,35426,35427,35430],{},[21,35424,35425],{},"GNB 메뉴 항목 disabled"," — 현재 미들웨어가 리다이렉트로 처리하지만, 시각적으로 GNB 메뉴는 그대로 활성 표시. 메뉴 항목별 ",[32,35428,35429],{},"disabled"," 클래스 + 호버 시 사유 툴팁이 더 친절. 후속.",[84,35432,35433,35436],{},[21,35434,35435],{},"승인 완료 후 자동 새로고침"," — 현재는 사용자가 직접 새로고침해야 새 상태 반영. WebSocket 또는 폴링으로 자동 갱신 검토.",[84,35438,35439,35444,35445,4536,35447,35449],{},[21,35440,35441,35443],{},[32,35442,16882],{},"도 허용"," — 1:1 문의 작성은 미승인 시에도 가능해야 함. 현재 ",[32,35446,18473],{},[32,35448,16882],{}," 두 경로 모두 허용 처리 — 라우트 구조 확정 후 정리.",[84,35451,35452,35455],{},[21,35453,35454],{},"모바일 띠 줄바꿈"," — 720px 미만에서 텍스트 wrap. 실제 모바일에서 가독성 점검 필요.",[84,35457,35458,35463],{},[21,35459,35460,35462],{},[32,35461,19458],{}," 분기"," — pending 상태에서 계약서 화면이 어떻게 보일지 정책 결정 필요.",[73,35465],{},[11,35467,35469,35470,35472],{"id":35468},"_10-미승인-accountcontract-리다이렉트-정책-배포-66","§10. 미승인 → ",[32,35471,19458],{}," 리다이렉트 정책 (배포 #66)",[76,35474,27637],{"id":35475},"한-줄-9",[18,35477,35478,35479,35481,35482,5439,35488,35490,35491,6219,35493,35495,35496,35499,35500,5606],{},"§9의 \"미승인 사용자 진입점은 ",[32,35480,11278],{},"\"을 변경 — ",[21,35483,35484,35485,35487],{},"사용자 정책 결정으로 미승인 사용자는 가입 직후·로그인 시·차단 페이지 접근 시 모두 ",[32,35486,19458],{}," (계약 관리 — 사업자등록증 제출\u002F재제출 화면)로 이동",[32,35489,11278],{},"도 차단 페이지에 포함, 미들웨어 리다이렉트 대상도 ",[32,35492,3991],{},[32,35494,19458],{}," 변경. 가입 마법사 Step 5 버튼은 유형 분기(\"계약 관리로 이동\" \u002F \"대시보드로 이동\"). AppApprovalBanner의 CTA도 \"회원 정보\" → \"계약 관리\". ",[32,35497,35498],{},"\u002Fhome.vue","의 §9에서 추가했던 미승인 분기 코드는 진입 자체가 불가능해진 결과 불필요 → 제거. Pages 배포 #66 (alias ",[32,35501,35502],{},"5256d66d.malgn-noti.pages.dev",[76,35504,35506],{"id":35505},"_101-변경-사항","10.1 변경 사항",[101,35508,35509,35517],{},[104,35510,35511],{},[107,35512,35513,35515],{},[110,35514,28918],{},[110,35516,7525],{},[126,35518,35519,35536,35552,35572],{},[107,35520,35521,35525],{},[131,35522,35523],{},[26,35524,34894],{"href":35024},[131,35526,35527,13735,35529,35531,35532,6219,35534],{},[32,35528,35163],{},[32,35530,11278],{}," 제거. 리다이렉트 대상 ",[32,35533,3991],{},[32,35535,19458],{},[107,35537,35538,35543],{},[131,35539,35540],{},[26,35541,35542],{"href":34923},"components\u002FAppApprovalBanner.vue",[131,35544,35545,6219,35548,35551],{},[32,35546,35547],{},"goToSettings()",[32,35549,35550],{},"goToContract()",". CTA 텍스트 pending \"회원 정보\" → \"계약 관리\" \u002F rejected \"다시 제출하기\"(유지)",[107,35553,35554,35558],{},[131,35555,35556],{},[26,35557,14220],{"href":18106},[131,35559,35560,89,35562,35565,35566,35568,35569,35571],{},[32,35561,29092],{},[32,35563,35564],{},"isBusiness","이면 ",[32,35567,19458],{},", 개인이면 ",[32,35570,11278],{},". 버튼 라벨도 분기 (\"계약 관리로 이동\" \u002F \"대시보드로 이동\")",[107,35573,35574,35578],{},[131,35575,35576],{},[26,35577,4441],{"href":35304},[131,35579,35580,35581,4339,35583,35585],{},"§9에서 추가한 미승인 분기(",[32,35582,35307],{},[32,35584,34907],{}," 카드 + 스타일) 모두 제거 — 미승인 사용자는 미들웨어가 차단해 진입 자체가 안 됨. 코드 단순화",[76,35587,35589],{"id":35588},"_102-새-흐름-미승인-사용자가-경험하는-ux-정리","10.2 새 흐름 — 미승인 사용자가 경험하는 UX (정리)",[6674,35591,35592,35603,35611,35618,35631,35640],{},[84,35593,35594,35596,35597,35599,35600,35602],{},[21,35595,35349],{}," → Step 5 → 클릭 시 자동으로 ",[32,35598,19458],{}," (사업자) \u002F ",[32,35601,11278],{}," (개인)",[84,35604,35605,35608,35609,35377],{},[21,35606,35607],{},"로그인"," → fetchMe → store에 approvalState='pending' → \u002Fhome 진입 시도 → 미들웨어가 ",[32,35610,19458],{},[84,35612,35613,35615,35616],{},[21,35614,35365],{},": 모든 페이지에 항상 노출 (\"사업자등록증 심사 중 …\") + CTA \"계약 관리\" → ",[32,35617,19458],{},[84,35619,35620,6685,35623,4473,35625,35627,35628,35630],{},[21,35621,35622],{},"다른 페이지 시도",[32,35624,12271],{},[32,35626,16262],{}," 등): 미들웨어가 즉시 ",[32,35629,19458],{},"로 자동 리다이렉트",[84,35632,35633,35637,35638,4343],{},[21,35634,35635],{},[32,35636,19458],{},": 사용자가 사업자등록증 제출\u002F재제출 가능 (",[32,35639,19343],{},[84,35641,35642,35644,35645,35647],{},[21,35643,35389],{},": store 갱신 시점부터 모든 페이지 정상 접근 + ",[32,35646,11278],{},"도 진입 가능",[76,35649,35651],{"id":35650},"_103-허용-페이지-변경-후","10.3 허용 페이지 (변경 후)",[101,35653,35654,35663],{},[104,35655,35656],{},[107,35657,35658,35661],{},[110,35659,35660],{},"경로",[110,35662,4629],{},[126,35664,35665,35678,35691,35702],{},[107,35666,35667,35671],{},[131,35668,35669],{},[32,35670,5699],{},[131,35672,35673,35674,35677],{},"회원 정보·",[21,35675,35676],{},"계약 관리(메인 진입점)","·문의 등",[107,35679,35680,35688],{},[131,35681,35682,4536,35684,4536,35686],{},[32,35683,5744],{},[32,35685,4457],{},[32,35687,26863],{},[131,35689,35690],{},"정적 문서",[107,35692,35693,35699],{},[131,35694,35695,4536,35697],{},[32,35696,18473],{},[32,35698,16882],{},[131,35700,35701],{},"1:1 문의",[107,35703,35704,35708],{},[131,35705,35706],{},[32,35707,29010],{},[131,35709,35710],{},"로그인·가입·재설정 등",[18,35712,35713,35714,8864],{},"차단 (자동 리다이렉트 → ",[32,35715,19458],{},[81,35717,35718,35723],{},[84,35719,35720,35722],{},[32,35721,11278],{}," (NEW — §9에서는 허용이었음)",[84,35724,35725,4536,35727,4536,35729,4536,35731,4536,35733,4536,35735,4536,35737,35739],{},[32,35726,34052],{},[32,35728,22063],{},[32,35730,35270],{},[32,35732,35273],{},[32,35734,35276],{},[32,35736,35279],{},[32,35738,35282],{}," 등 모든 변경 페이지",[76,35741,35743],{"id":35742},"_104-산출물","10.4 산출물",[81,35745,35746,35749],{},[84,35747,35748],{},"사용자단: 4 파일 수정 — middleware\u002Fapproval.global.ts · components\u002FAppApprovalBanner.vue · pages\u002Fsignup.vue · pages\u002Fhome.vue(미승인 분기 + 스타일 ~80줄 제거)",[84,35750,35751,35752],{},"Pages 배포 #66 alias ",[32,35753,35502],{},[76,35755,35757],{"id":35756},"_105-알려진-한계-다음-작업","10.5 알려진 한계 \u002F 다음 작업",[81,35759,35760,35768,35780,35786],{},[84,35761,35762,35767],{},[21,35763,35764,35766],{},[32,35765,19343],{},"의 미승인 UX 최적화"," — 현재 컴포넌트는 가입 후 일반 흐름(승인된 사용자의 계약 갱신 등) 기준으로 디자인됨. 미승인(처음 제출) \u002F 반려(재제출) 상태에 따라 화면 헤더·CTA를 더 명확히 분기할 여지. 후속.",[84,35769,35770,89,35773,35775,35776,35779],{},[21,35771,35772],{},"사업자등록증 업로드 실 API",[32,35774,19343],{},"의 업로드 모달은 현재 UI만. R2 업로드 + ",[32,35777,35778],{},"TB_CONTRACT_FILE"," 적재 라우트 필요.",[84,35781,35782,35785],{},[21,35783,35784],{},"계약 재제출 후 상태 전이"," — 운영자가 다시 'pending'으로 돌리는 흐름 + 사용자에게 알림 필요.",[84,35787,35788,35794],{},[21,35789,35790,35791,35793],{},"개인 가입자의 ",[32,35792,19458],{}," 비노출"," — 메뉴에서 숨김 처리 검토(현재 모두 노출).",[73,35796],{},[11,35798,35800,35801,35803],{"id":35799},"_11-accountcontract-실-api-r2-첨부-인프라-배포-17-69","§11. ",[32,35802,19458],{}," 실 API + R2 첨부 인프라 (배포 #17 \u002F #69)",[76,35805,27637],{"id":35806},"한-줄-10",[18,35808,35809,35810,35812,35813,35816,35817,35820,35821,51,35823,35826,35827,5069,35829,4536,35832,35834,35835,5069,35838,35841,35842,51,35845,35847,35848,35850,35851,4473,35854,7086,35857,51,35860,35863,35864,4536,35867,4536,35870,35873,35874,35876,35877,35880,35881,35884,35885,35888,35889,35892,35893,35896,35897,35900],{},"§10에서 정책 정합화한 ",[32,35811,19458],{},"의 실 백엔드 연동. ",[21,35814,35815],{},"(a)"," R2 bucket ",[32,35818,35819],{},"malgn-noti-files"," 생성 + ",[32,35822,11253],{},[32,35824,35825],{},"FILES"," 바인딩 + ",[32,35828,28258],{},[32,35830,35831],{},"TB_CONTRACT",[32,35833,35778],{}," 정의(라이브 DDL과 일치) + Hono ",[32,35836,35837],{},"AuthBindings",[32,35839,35840],{},"FILES?: R2Bucket"," 옵셔널 추가. ",[21,35843,35844],{},"(b)",[32,35846,22968],{}," 확장 — 사업자(corp\u002Fsole) 가입 시 NICE 소비 직후 ",[32,35849,35831],{}," 'initial' 자동 1건 생성(",[32,35852,35853],{},"title='최초 이용계약 온라인체결'",[32,35855,35856],{},"version='신규'",[21,35858,35859],{},"(c)",[32,35861,35862],{},"\u002Fcontracts"," 라우트 신설 5 엔드포인트 — list \u002F sign \u002F files list \u002F files upload(multipart) \u002F files download(stream) \u002F files delete. PDF·10MB 제한, name 접두사(",[32,35865,35866],{},"사업자등록증_…",[32,35868,35869],{},"대부업등록증_…",[32,35871,35872],{},"지급이행보증보험증권_…",")로 종류 구분(",[32,35875,35778],{},"에 kind 컬럼 없음). 회사 단위 권한 — companyId 매칭 안 되면 404. renew 체결 시 같은 회사의 다른 done 계약은 자동 expired. ",[21,35878,35879],{},"(d)"," OpenAPI 5 paths + 2 schemas 추가. ",[21,35882,35883],{},"(e)"," 프런트 ",[32,35886,35887],{},"AppContractPanel.vue"," 전면 교체 — 목업 contracts\u002FbizFiles\u002FloanFiles\u002FinsuranceFiles 제거, ",[32,35890,35891],{},"await Promise.all([loadContracts(), loadFiles()])"," SSR, FormData multipart POST, 미리보기는 인증 fetch → blob → object URL(iframe은 Authorization 헤더 못 실음). Workers 배포 #17(Version ",[32,35894,35895],{},"7213946f-42c4-4772-bfbe-e8d271167a01","), Pages 배포 alias ",[32,35898,35899],{},"9808fe42.malgn-noti.pages.dev",". 라이브 e2e 4 시나리오 통과(corp signup auto-contract \u002F 파일 업로드 R2 \u002F 파일 목록 \u002F 체결 → done+expires).",[76,35902,35904,35905,35908,35909,35911],{"id":35903},"_111-결정-tb_contract_filekind-컬럼-없음-name-접두사-사용","11.1 결정 — ",[32,35906,35907],{},"TB_CONTRACT_FILE.kind"," 컬럼 없음 → ",[32,35910,14992],{}," 접두사 사용",[18,35913,35914,35915,35917,35918,35921,35922,35928,35929,35932],{},"라이브 ",[32,35916,35778],{}," 스키마는 ",[32,35919,35920],{},"id \u002F contract_id \u002F name \u002F size_bytes \u002F r2_key \u002F uploaded_at"," 6 컬럼. 파일 종류(사업자등록증·대부업등록증·보험증권)를 구분할 컬럼이 없음. 새 DDL을 발행하기보다는 ",[21,35923,35924,35925,35927],{},"업로드 시 ",[32,35926,14992],{},"에 한국어 라벨 접두사를 붙여 저장",", 프런트에서 ",[32,35930,35931],{},"startsWith('사업자등록증_')"," 등으로 분류. 라이브 DDL 추가 없이 정책 흡수.",[101,35934,35935,35945],{},[104,35936,35937],{},[107,35938,35939,35942],{},[110,35940,35941],{},"kind 폼 필드",[110,35943,35944],{},"name 접두사",[126,35946,35947,35959,35971],{},[107,35948,35949,35954],{},[131,35950,35951],{},[32,35952,35953],{},"biz",[131,35955,35956],{},[32,35957,35958],{},"사업자등록증_\u003C원본파일명>",[107,35960,35961,35966],{},[131,35962,35963],{},[32,35964,35965],{},"loan",[131,35967,35968],{},[32,35969,35970],{},"대부업등록증_\u003C원본파일명>",[107,35972,35973,35978],{},[131,35974,35975],{},[32,35976,35977],{},"insurance",[131,35979,35980],{},[32,35981,35982],{},"지급이행보증보험증권_\u003C원본파일명>",[18,35984,35985,35986,4536,35988,4536,35990,35993],{},"R2 customMetadata에는 ",[32,35987,17261],{},[32,35989,22750],{},[32,35991,35992],{},"contractId"," 모두 기록 — 라이브 운영 중 별도 보고서\u002F감사에 활용 가능.",[76,35995,35997],{"id":35996},"_112-r2-bucket-바인딩","11.2 R2 bucket + 바인딩",[81,35999,36000,36006,36013,36026],{},[84,36001,36002,36005],{},[32,36003,36004],{},"npx wrangler@4 r2 bucket create malgn-noti-files --remote"," → 신규 버킷",[84,36007,36008,5069,36010,11190],{},[32,36009,11253],{},[32,36011,36012],{},"[[r2_buckets]] binding = \"FILES\" \u002F bucket_name = \"malgn-noti-files\"",[84,36014,36015,11686,36017,5069,36019,36021,36022,36025],{},[32,36016,21778],{},[32,36018,35837],{},[32,36020,35840],{}," 옵셔널 추가 — 기존 라우트가 모두 ",[32,36023,36024],{},"Hono\u003CAuthEnv>()"," 패턴이라 한 곳에서 형 변경하면 자동 전파",[84,36027,36028,36029,36032],{},"사용처에서 ",[32,36030,36031],{},"if (!c.env.FILES) throw errors.internal(...)"," 가드 — 로컬 dev에서 바인딩 누락 시 503 응답으로 빠르게 실패",[76,36034,36036],{"id":36035},"_113-schemats-라이브-ddl과-정합","11.3 schema.ts — 라이브 DDL과 정합",[18,36038,36039,36041,36042,4536,36044,36046],{},[26,36040,21654],{"href":29606}," — 라이브에 이미 존재하던 ",[32,36043,35831],{},[32,36045,35778],{}," 정의 추가:",[81,36048,36049,36091],{},[84,36050,36051,89,36053,36055,36056,36058,36059,7505,36061,7505,36063,7505,36066,36069,36070,7505,36072,36058,36075,7505,36077,7505,36080,7505,36082,7505,36084,36087,36088],{},[32,36052,18554],{},[32,36054,27203],{}," PK auto \u002F ",[32,36057,20071],{}," FK→",[32,36060,28064],{},[32,36062,7082],{},[32,36064,36065],{},"version",[32,36067,36068],{},"contract_state"," ('initial'\u002F'renew'\u002F'done'\u002F'expired', 기본 'initial') \u002F ",[32,36071,27220],{},[32,36073,36074],{},"signer_user_id",[32,36076,20302],{},[32,36078,36079],{},"signed_at",[32,36081,25690],{},[32,36083,32027],{},[32,36085,36086],{},"updated_at",". 인덱스 ",[32,36089,36090],{},"(company_id, contract_state)",[84,36092,36093,89,36096,36055,36098,36058,36101,7505,36103,36105,36106,7505,36109,7505,36111,36087,36114],{},[32,36094,36095],{},"contractFile",[32,36097,27203],{},[32,36099,36100],{},"contract_id",[32,36102,35831],{},[32,36104,14992],{}," (한국어 접두사 포함) \u002F ",[32,36107,36108],{},"size_bytes",[32,36110,26257],{},[32,36112,36113],{},"uploaded_at",[32,36115,36116],{},"(contract_id)",[76,36118,36120],{"id":36119},"_114-signup-자동-initial-계약-생성","11.4 signup 자동 'initial' 계약 생성",[18,36122,36123,36125],{},[26,36124,22961],{"href":29639}," — NICE 세션 consume 직후 분기:",[5251,36127,36129],{"className":7962,"code":36128,"language":7964,"meta":4208,"style":4208},"if (body.companyType === 'corp' || body.companyType === 'sole') {\n  await db.insert(contract).values({\n    companyId,\n    title: '최초 이용계약 온라인체결',\n    version: '신규',\n    contractState: 'initial',\n    status: 1,\n  })\n}\n",[32,36130,36131,36156,36174,36179,36190,36200,36210,36219,36224],{"__ignoreMap":4208},[7968,36132,36133,36135,36138,36140,36143,36146,36149,36151,36154],{"class":5110,"line":7970},[7968,36134,31692],{"class":7973},[7968,36136,36137],{"class":7984}," (body.companyType ",[7968,36139,31645],{"class":7973},[7968,36141,36142],{"class":7993}," 'corp'",[7968,36144,36145],{"class":7973}," ||",[7968,36147,36148],{"class":7984}," body.companyType ",[7968,36150,31645],{"class":7973},[7968,36152,36153],{"class":7993}," 'sole'",[7968,36155,21462],{"class":7984},[7968,36157,36158,36161,36163,36166,36169,36172],{"class":5110,"line":4212},[7968,36159,36160],{"class":7973},"  await",[7968,36162,34357],{"class":7984},[7968,36164,36165],{"class":7980},"insert",[7968,36167,36168],{"class":7984},"(contract).",[7968,36170,36171],{"class":7980},"values",[7968,36173,7985],{"class":7984},[7968,36175,36176],{"class":5110,"line":4209},[7968,36177,36178],{"class":7984},"    companyId,\n",[7968,36180,36181,36184,36187],{"class":5110,"line":4219},[7968,36182,36183],{"class":7984},"    title: ",[7968,36185,36186],{"class":7993},"'최초 이용계약 온라인체결'",[7968,36188,36189],{"class":7984},",\n",[7968,36191,36192,36195,36198],{"class":5110,"line":8291},[7968,36193,36194],{"class":7984},"    version: ",[7968,36196,36197],{"class":7993},"'신규'",[7968,36199,36189],{"class":7984},[7968,36201,36202,36205,36208],{"class":5110,"line":8301},[7968,36203,36204],{"class":7984},"    contractState: ",[7968,36206,36207],{"class":7993},"'initial'",[7968,36209,36189],{"class":7984},[7968,36211,36212,36215,36217],{"class":5110,"line":8310},[7968,36213,36214],{"class":7984},"    status: ",[7968,36216,4636],{"class":8892},[7968,36218,36189],{"class":7984},[7968,36220,36221],{"class":5110,"line":8321},[7968,36222,36223],{"class":7984},"  })\n",[7968,36225,36226],{"class":5110,"line":8332},[7968,36227,8987],{"class":7984},[18,36229,36230,36232,36233,36235],{},[32,36231,33576],{},"은 자동 생성 안 함 — ",[32,36234,19458],{},"에 진입할 일이 없음(§10 정책).",[76,36237,36239,36240,36242],{"id":36238},"_115-contracts-라우트-5-엔드포인트","11.5 ",[32,36241,35862],{}," 라우트 — 5 엔드포인트",[18,36244,36245,36249],{},[26,36246,36248],{"href":36247},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Froutes\u002Fcontracts.ts","src\u002Froutes\u002Fcontracts.ts"," 신규(244 줄):",[101,36251,36252,36260],{},[104,36253,36254],{},[107,36255,36256,36258],{},[110,36257,22556],{},[110,36259,32194],{},[126,36261,36262,36272,36295,36308,36318,36335],{},[107,36263,36264,36269],{},[131,36265,36266],{},[32,36267,36268],{},"GET \u002Fcontracts",[131,36270,36271],{},"본 회사 계약 목록 (status=1 한정, id 오름차순)",[107,36273,36274,36279],{},[131,36275,36276],{},[32,36277,36278],{},"POST \u002Fcontracts\u002F:id\u002Fsign",[131,36280,36281,36282,4339,36285,4339,36288,4339,36291,36294],{},"'initial' 또는 'renew' → ",[32,36283,36284],{},"'done'",[32,36286,36287],{},"signer_user_id=ctx.userId",[32,36289,36290],{},"signed_at=now",[32,36292,36293],{},"expires_at=+2y",". renew였다면 같은 회사의 다른 done 계약 모두 'expired'로 일괄 전이",[107,36296,36297,36302],{},[131,36298,36299],{},[32,36300,36301],{},"GET \u002Fcontracts\u002Ffiles",[131,36303,36304,36305,36307],{},"본 회사 파일 목록 (",[32,36306,18554],{}," JOIN으로 회사 단위 좁힘)",[107,36309,36310,36315],{},[131,36311,36312],{},[32,36313,36314],{},"POST \u002Fcontracts\u002Ffiles",[131,36316,36317],{},"multipart (contractId \u002F kind \u002F file). PDF·10MB 검증 + 회사 소유 검증 → R2 put + DB insert",[107,36319,36320,36325],{},[131,36321,36322],{},[32,36323,36324],{},"GET \u002Fcontracts\u002Ffiles\u002F:id\u002Fdownload",[131,36326,36327,36328,36331,36332,4343],{},"R2 stream → ",[32,36329,36330],{},"application\u002Fpdf"," 응답 (",[32,36333,36334],{},"Content-Disposition: inline; filename*=UTF-8''…",[107,36336,36337,36342],{},[131,36338,36339],{},[32,36340,36341],{},"DELETE \u002Fcontracts\u002Ffiles\u002F:id",[131,36343,36344],{},"R2 delete(실패 swallow) + DB delete",[18,36346,36347,36348,36351,36352,27750,36354,36357],{},"라우트는 ",[32,36349,36350],{},"app.use('*', requireAuth())","만 — 승인 게이트(",[32,36353,34718],{},[21,36355,36356],{},"적용 안 함"," (미승인 사용자가 사업자등록증을 업로드해야 하기 때문).",[18,36359,36360,36361,36364],{},"R2 key 패턴: ",[32,36362,36363],{},"contracts\u002F\u003CcompanyId>\u002F\u003CcontractId>\u002F\u003Cunix>_\u003CsafeName>"," — 회사·계약별로 prefix 분리 + 안전한 ASCII 파일명으로 escape.",[76,36366,36368],{"id":36367},"_116-openapi","11.6 OpenAPI",[81,36370,36371,36392,36401],{},[84,36372,36373,36374,36376,36377,36380,36381,36384,36385,36376,36388,36391],{},"5 paths 추가 (",[32,36375,35862],{}," GET, ",[32,36378,36379],{},"\u002Fcontracts\u002F{id}\u002Fsign"," POST, ",[32,36382,36383],{},"\u002Fcontracts\u002Ffiles"," GET·POST, ",[32,36386,36387],{},"\u002Fcontracts\u002Ffiles\u002F{id}\u002Fdownload",[32,36389,36390],{},"\u002Fcontracts\u002Ffiles\u002F{id}"," DELETE)",[84,36393,36394,36395,4473,36398,4343],{},"2 schemas 추가 (",[32,36396,36397],{},"Contract",[32,36399,36400],{},"ContractFile",[84,36402,36403,36404,36406,36407,36410],{},"목록은 ",[32,36405,22890],{}," 헬퍼 재사용(실제로는 cursor 없음, 응답 형식은 ",[32,36408,36409],{},"{data:[...]}"," 동일)",[76,36412,36414,36415,36417],{"id":36413},"_117-프런트-appcontractpanelvue-실-api-연동","11.7 프런트 — ",[32,36416,35887],{}," 실 API 연동",[18,36419,36420,36421,4536,36424,4536,36427,4536,36430,36433],{},"목업 데이터(",[32,36422,36423],{},"contracts[3건]",[32,36425,36426],{},"bizFiles[2건]",[32,36428,36429],{},"nowStamp()",[32,36431,36432],{},"expiryStamp()",") 모두 제거. 핵심 변경:",[81,36435,36436,36444,36478,36488,36501,36510,36522,36536],{},[84,36437,36438,47,36441,36443],{},[21,36439,36440],{},"로딩",[32,36442,35891],{}," — top-level await (Nuxt SSR 호환)",[84,36445,36446,36448,36449,36452,36453,4536,36456,36459,36460,36463,36464,36467,36468,36470,36471,36474,36475,36477],{},[21,36447,27032],{},": 백엔드 ",[32,36450,36451],{},"contractState","를 STATE_META 테이블로 ",[32,36454,36455],{},"statusLabel",[32,36457,36458],{},"icon"," 매핑 + ",[32,36461,36462],{},"metas"," 자동 구성(",[32,36465,36466],{},"initial","은 가입 안내·요청일, ",[32,36469,27035],{},"은 체결·만료, ",[32,36472,36473],{},"renew","는 요청일, ",[32,36476,19371],{},"는 만료)",[84,36479,36480,36483,36484,36487],{},[21,36481,36482],{},"파일 분류",": 목록은 단일 호출 → ",[32,36485,36486],{},"classify(name)"," 으로 biz\u002Floan\u002Finsurance 그룹 분배 → 표시명은 접두사 제거",[84,36489,36490,36493,36494,4536,36497,36500],{},[21,36491,36492],{},"첨부 활성화",": 파일이 이미 있으면 ",[32,36495,36496],{},"loanApplicable",[32,36498,36499],{},"insuranceApplicable"," 자동 true",[84,36502,36503,47,36506,36509],{},[21,36504,36505],{},"활성 계약 결정",[32,36507,36508],{},"activeContractId = initial || renew || 가장 오래된"," — 새 첨부는 이쪽으로 묶음",[84,36511,36512,47,36515,36518,36519,36521],{},[21,36513,36514],{},"업로드",[32,36516,36517],{},"FormData"," POST ",[32,36520,36383],{},". 실패 시 토스트 + input.value 초기화",[84,36523,36524,47,36527,6219,36529,36532,36533,36535],{},[21,36525,36526],{},"체결",[32,36528,36278],{},[32,36530,36531],{},"loadContracts()"," 재호출 → 토스트(",[32,36534,36473],{},"였다면 \"기존 계약 만료 처리\" 메시지)",[84,36537,36538,36540,36541,6219,36544,6219,36547,5069,36549,36552,36553],{},[21,36539,7244],{},": iframe은 Authorization 헤더를 못 싣기 때문에 ",[32,36542,36543],{},"api\u003CBlob>('\u002Fcontracts\u002Ffiles\u002F:id\u002Fdownload', { responseType: 'blob' })",[32,36545,36546],{},"URL.createObjectURL",[32,36548,19414],{},[32,36550,36551],{},"file.url"," 전달. modal 닫힐 때 ",[32,36554,36555],{},"revokeObjectURL",[76,36557,36559],{"id":36558},"_118-라이브-e2e-검증-4-시나리오","11.8 라이브 e2e 검증 (4 시나리오)",[101,36561,36562,36572],{},[104,36563,36564],{},[107,36565,36566,36568,36570],{},[110,36567,3846],{},[110,36569,27964],{},[110,36571,22362],{},[126,36573,36574,36590,36605,36616],{},[107,36575,36576,36578,36581],{},[131,36577,4636],{},[131,36579,36580],{},"corp signup (mock NICE)",[131,36582,36583,36584,36586,36587,36589],{},"✅ 자동 ",[32,36585,35831],{}," 1건 'initial' 생성 (",[32,36588,35862],{}," 응답 정상)",[107,36591,36592,36594,36599],{},[131,36593,4649],{},[131,36595,36596,36598],{},[32,36597,36314],{}," (105B PDF, kind=biz)",[131,36600,36601,36602],{},"✅ 201 + ",[32,36603,36604],{},"name='사업자등록증_test.pdf'",[107,36606,36607,36609,36613],{},[131,36608,4662],{},[131,36610,36611],{},[32,36612,36301],{},[131,36614,36615],{},"✅ 1건 목록",[107,36617,36618,36620,36624],{},[131,36619,4678],{},[131,36621,36622],{},[32,36623,36278],{},[131,36625,36626,36627,6219,36630,6219,36632,4473,36635,4361,36638,36640],{},"✅ ",[32,36628,36629],{},"data.ok:true",[32,36631,36268],{},[32,36633,36634],{},"contractState='done'",[32,36636,36637],{},"signedAt",[32,36639,29809],{},"(+2y) 정확",[18,36642,36643],{},"E2E 데이터 cleanup 완료 (TB_CONTRACT_FILE 0 \u002F TB_CONTRACT 0 \u002F TB_USER e2e_% 0 \u002F TB_COMPANY E2E% 0 \u002F R2 객체 1건 삭제).",[76,36645,36647],{"id":36646},"_119-산출물","11.9 산출물",[81,36649,36650,36676,36684],{},[84,36651,32568,36652,36654,36655,36657,36658,4536,36660,4536,36662,4536,36664,4536,36666,4536,36668,36670,36671,36673,36674,14421],{},[32,36653,50],{}," — 신규 ",[32,36656,36248],{},"(244) · 수정 ",[32,36659,11253],{},[32,36661,21778],{},[32,36663,21654],{},[32,36665,22961],{},[32,36667,22139],{},[32,36669,11243],{},". Workers 배포 #17 Version ",[32,36672,35895],{},". R2 bucket ",[32,36675,35819],{},[84,36677,33415,36678,36681,36682,4703],{},[32,36679,36680],{},"app\u002Fcomponents\u002FAppContractPanel.vue"," 전면 교체. Pages 배포 alias ",[32,36683,35899],{},[84,36685,36686,36687,36689],{},"WBS: 5-3C-* 신규 항목 \"계약 관리 (",[32,36688,19458],{},") 실 API 연동\" — 사업자등록증 업로드 + 계약 체결 ✅",[76,36691,36693],{"id":36692},"_1110-알려진-한계-다음-작업","11.10 알려진 한계 \u002F 다음 작업",[81,36695,36696,36709,36718,36723,36729],{},[84,36697,36698,36703,36704,4536,36706,36708],{},[21,36699,36700,36702],{},[32,36701,19343],{}," 헤더의 안내문구","가 §10에서 지적된 \"미승인 \u002F 반려에 따른 분기\"는 아직 미반영. 모달 UX(",[32,36705,19408],{},[32,36707,19398],{},")도 현 상태 유지.",[84,36710,36711,36714,36715,36717],{},[21,36712,36713],{},"운영자단 사업자 승인 화면 미구현"," (P0 §7.7) — 현재 라이브 DB UPDATE만으로 승인\u002F반려. 운영자단 라우트(",[32,36716,25421],{},") 신설 필요.",[84,36719,36720,36722],{},[21,36721,34074],{}," (승인\u002F반려\u002F계약 만료) — NHN 자격증명 등록 후 trigger.",[84,36724,36725,36728],{},[21,36726,36727],{},"사업자등록증 OCR 자동 검증"," — 향후 운영자 부담 경감 검토. 현재는 운영자 수동 심사.",[84,36730,36731,36734,36735,36737],{},[21,36732,36733],{},"계약 갱신 트리거"," — 만료 1개월 전 자동 'renew' 계약 row 생성 cron 필요(아직 없음). signed_at + 2y가 가까워지면 ",[32,36736,29809],{},"만으로 판단해 화면에서 경고는 가능.",[73,36739],{},[11,36741,36743],{"id":36742},"_12-사업자등록증-첨부-시-회사-승인-상태-reviewing-자동-전이-배포-1819-pages-alias-d9a82bfa","§12. 사업자등록증 첨부 시 회사 승인 상태 'reviewing' 자동 전이 (배포 #18·#19 \u002F Pages alias d9a82bfa)",[76,36745,27637],{"id":36746},"한-줄-11",[18,36748,36749,36750,36752,36753,36758,36759,36762,36763,36766,36767,36769,36770,13735,36772,36775,36776,26162,36778,35565,36780,36782,36783,36786,36787,51,36789,22747,36791,36793,36794,36796,36797,36799,36800,36803,36804,36807,36808,4536,36810,36812,36813,36815,36816,51,36818,36821,36822,36824,36825,36827,36828,35896,36831,36834],{},"§11의 후속 — 사용자가 \"사업자등록증을 첨부하면 심사 중으로 변경해 달라\"고 요청. ",[32,36751,20267],{}," enum에 ",[21,36754,36755],{},[32,36756,36757],{},"reviewing"," 추가(",[32,36760,36761],{},"pending → reviewing → approved | rejected"," 4단계). DDL 변경은 없음(",[32,36764,36765],{},"VARCHAR(20)","이라 자동 흡수). ",[21,36768,35815],{}," 백엔드 ",[32,36771,36314],{},[32,36773,36774],{},"kind=biz"," 업로드 후 회사 상태가 ",[32,36777,27043],{},[32,36779,33601],{},[32,36781,36757],{},"으로 UPDATE. ",[32,36784,36785],{},"rejected_reason","은 그대로 둠(운영자가 결정). ",[21,36788,35844],{},[32,36790,34718],{},[32,36792,33985],{}," PATCH 차단 메시지에 ",[32,36795,36757],{}," 분기 추가(\"사업자등록증 심사가 진행 중입니다. 승인 완료 후 …\"). ",[21,36798,35859],{}," 사용자단 ",[32,36801,36802],{},"AuthCompany.approvalState"," 타입 확장(",[32,36805,36806],{},"'reviewing'"," 추가) + ",[32,36809,34887],{},[32,36811,18572],{}," 안내문구 3분기 + ",[32,36814,19343],{}," 패널 상단에 승인 상태 카드(pending=warning · reviewing=info · rejected=danger) 신규. ",[21,36817,35879],{},[32,36819,36820],{},"pickFile"," 성공 후 ",[32,36823,36774],{},"면 ",[32,36826,29025],{}," 호출 → store 즉시 갱신 → 글로벌 띠·페이지 배너가 즉시 \"심사 중\"으로 전환(새로고침 불필요). Workers 배포 #19(Version ",[32,36829,36830],{},"f6877b91-4b2c-429e-951f-9185bcf69c4a",[32,36832,36833],{},"d9a82bfa.malgn-noti.pages.dev",". 라이브 e2e 통과(corp 가입 직후 pending → biz 업로드 → reviewing 자동 전이 + PATCH \u002Fme 시도 시 새 메시지 노출).",[76,36836,36838,36839,36841],{"id":36837},"_121-정책-approval_state-4단계로-확장","12.1 정책 — ",[32,36840,20267],{}," 4단계로 확장",[101,36843,36844,36858],{},[104,36845,36846],{},[107,36847,36848,36850,36852,36855],{},[110,36849,270],{},[110,36851,4629],{},[110,36853,36854],{},"진입 트리거",[110,36856,36857],{},"사용자 액션",[126,36859,36860,36875,36890,36904],{},[107,36861,36862,36866,36869,36872],{},[131,36863,36864],{},[32,36865,27043],{},[131,36867,36868],{},"사업자등록증 미제출",[131,36870,36871],{},"corp\u002Fsole signup",[131,36873,36874],{},"계약 관리에서 사업자등록증 업로드",[107,36876,36877,36881,36884,36887],{},[131,36878,36879],{},[32,36880,36757],{},[131,36882,36883],{},"운영자 심사 대기",[131,36885,36886],{},"biz 첨부 완료 시 자동(pending\u002Frejected에서만)",[131,36888,36889],{},"대기 — 결과 안내 메일\u002FSMS",[107,36891,36892,36896,36898,36901],{},[131,36893,36894],{},[32,36895,33595],{},[131,36897,16090],{},[131,36899,36900],{},"운영자(BackOffice)",[131,36902,36903],{},"모든 기능 이용",[107,36905,36906,36910,36913,36916],{},[131,36907,36908],{},[32,36909,33601],{},[131,36911,36912],{},"반려",[131,36914,36915],{},"운영자(BackOffice) — 사유 입력",[131,36917,36918],{},"사업자등록증 재첨부 → reviewing으로 자동 전이",[81,36920,36921,36929,36941],{},[84,36922,36923,36925,36926,36928],{},[32,36924,33576],{},"은 처음부터 ",[32,36927,33595],{}," — 영향 없음",[84,36930,36931,36933,36934,36937,36938,36940],{},[32,36932,34718],{}," 미들웨어는 여전히 ",[32,36935,36936],{},"state !== 'approved'"," 차단 — ",[32,36939,36757],{},"도 차단 대상(메시지만 분기)",[84,36942,36943,36944,36947,36948,36951],{},"DDL 변경 없음 (",[32,36945,36946],{},"approval_state VARCHAR(20)","). 추후 ",[32,36949,36950],{},"idx_company_approval","은 4 상태 모두 같은 컬럼이라 그대로 효율적",[76,36953,36955],{"id":36954},"_122-백엔드-상태-전이-메시지","12.2 백엔드 — 상태 전이 + 메시지",[237,36957,36959,36960,36962],{"id":36958},"post-contractsfiles-kindbiz만-발동","POST \u002Fcontracts\u002Ffiles (",[32,36961,36774],{},"만 발동)",[5251,36964,36966],{"className":7962,"code":36965,"language":7964,"meta":4208,"style":4208},"if (kind === 'biz') {\n  const cs = await db.select({ state: company.approvalState })...\n  if (cs[0]?.state === 'pending' || cs[0]?.state === 'rejected') {\n    await db.update(company).set({ approvalState: 'reviewing' })\n      .where(eq(company.id, companyId))\n  }\n}\n",[32,36967,36968,36982,37003,37035,37056,37070,37074],{"__ignoreMap":4208},[7968,36969,36970,36972,36975,36977,36980],{"class":5110,"line":7970},[7968,36971,31692],{"class":7973},[7968,36973,36974],{"class":7984}," (kind ",[7968,36976,31645],{"class":7973},[7968,36978,36979],{"class":7993}," 'biz'",[7968,36981,21462],{"class":7984},[7968,36983,36984,36986,36989,36991,36993,36995,36997,37000],{"class":5110,"line":4212},[7968,36985,34235],{"class":7973},[7968,36987,36988],{"class":8892}," cs",[7968,36990,9210],{"class":7973},[7968,36992,9280],{"class":7973},[7968,36994,34357],{"class":7984},[7968,36996,34360],{"class":7980},[7968,36998,36999],{"class":7984},"({ state: company.approvalState })",[7968,37001,37002],{"class":7973},"...\n",[7968,37004,37005,37007,37010,37012,37015,37017,37019,37021,37024,37026,37028,37030,37033],{"class":5110,"line":4209},[7968,37006,21447],{"class":7973},[7968,37008,37009],{"class":7984}," (cs[",[7968,37011,5354],{"class":8892},[7968,37013,37014],{"class":7984},"]?.state ",[7968,37016,31645],{"class":7973},[7968,37018,27184],{"class":7993},[7968,37020,36145],{"class":7973},[7968,37022,37023],{"class":7984}," cs[",[7968,37025,5354],{"class":8892},[7968,37027,37014],{"class":7984},[7968,37029,31645],{"class":7973},[7968,37031,37032],{"class":7993}," 'rejected'",[7968,37034,21462],{"class":7984},[7968,37036,37037,37039,37041,37044,37046,37049,37052,37054],{"class":5110,"line":4219},[7968,37038,34477],{"class":7973},[7968,37040,34357],{"class":7984},[7968,37042,37043],{"class":7980},"update",[7968,37045,34373],{"class":7984},[7968,37047,37048],{"class":7980},"set",[7968,37050,37051],{"class":7984},"({ approvalState: ",[7968,37053,36806],{"class":7993},[7968,37055,8274],{"class":7984},[7968,37057,37058,37061,37063,37065,37067],{"class":5110,"line":8291},[7968,37059,37060],{"class":7984},"      .",[7968,37062,34376],{"class":7980},[7968,37064,6100],{"class":7984},[7968,37066,34381],{"class":7980},[7968,37068,37069],{"class":7984},"(company.id, companyId))\n",[7968,37071,37072],{"class":5110,"line":8301},[7968,37073,21497],{"class":7984},[7968,37075,37076],{"class":5110,"line":8310},[7968,37077,8987],{"class":7984},[81,37079,37080,37086,37093,37101],{},[84,37081,37082,37083],{},"첫 첨부 시: ",[32,37084,37085],{},"pending → reviewing",[84,37087,37088,37089,37092],{},"반려 후 재첨부 시: ",[32,37090,37091],{},"rejected → reviewing"," (사유는 그대로 둠 → 운영자가 새 심사에서 덮어쓰거나 NULL로 설정)",[84,37094,21130,37095,37097,37098,37100],{},[32,37096,36757],{},"이거나 ",[32,37099,33595],{},"면 변동 없음(idempotent)",[84,37102,37103,7505,37106,37109],{},[32,37104,37105],{},"kind=loan",[32,37107,37108],{},"kind=insurance","는 트리거 안 함 — 보조 서류",[237,37111,37113],{"id":37112},"메시지-분기-2-곳","메시지 분기 — 2 곳",[18,37115,37116,9148],{},[26,37117,34124],{"href":34129},[5251,37119,37121],{"className":7962,"code":37120,"language":7964,"meta":4208,"style":4208},"state === 'rejected' ? '심사가 반려되어 이용할 수 없습니다. 사유: …'\n: state === 'reviewing' ? '사업자등록증 심사가 진행 중입니다. 승인 완료 후 이용할 수 있습니다.'\n: '사업자등록증을 등록한 후 이용할 수 있습니다.'\n",[32,37122,37123,37136,37152],{"__ignoreMap":4208},[7968,37124,37125,37127,37129,37131,37133],{"class":5110,"line":7970},[7968,37126,35137],{"class":7984},[7968,37128,31645],{"class":7973},[7968,37130,37032],{"class":7993},[7968,37132,31651],{"class":7973},[7968,37134,37135],{"class":7993}," '심사가 반려되어 이용할 수 없습니다. 사유: …'\n",[7968,37137,37138,37140,37142,37144,37147,37149],{"class":5110,"line":4212},[7968,37139,9148],{"class":7973},[7968,37141,35143],{"class":7984},[7968,37143,31645],{"class":7973},[7968,37145,37146],{"class":7993}," 'reviewing'",[7968,37148,31651],{"class":7973},[7968,37150,37151],{"class":7993}," '사업자등록증 심사가 진행 중입니다. 승인 완료 후 이용할 수 있습니다.'\n",[7968,37153,37154,37156],{"class":5110,"line":4209},[7968,37155,9148],{"class":7973},[7968,37157,37158],{"class":7993}," '사업자등록증을 등록한 후 이용할 수 있습니다.'\n",[18,37160,37161,89,37163,4536,37165,37167],{},[26,37162,32748],{"href":32747},[32,37164,32697],{},[32,37166,32700],{}," 두 핸들러 모두 동일 분기 추가(\"정보를 수정할 수 있습니다\" 변형).",[76,37169,37171],{"id":37170},"_123-사용자단-타입배너카드store-갱신","12.3 사용자단 — 타입·배너·카드·store 갱신",[237,37173,37175],{"id":37174},"타입-확장","타입 확장",[18,37177,37178,89,37180,13419,37182,37184],{},[26,37179,28955],{"href":29322},[32,37181,36802],{},[32,37183,36806],{}," 추가. 타입 좁힘이 풀려 모든 화면의 컴파일 에러가 한 번에 해소됨.",[237,37186,37188],{"id":37187},"appapprovalbanner-글로벌-띠","AppApprovalBanner — 글로벌 띠",[18,37190,37191,89,37193,14840,37195,37197],{},[26,37192,34924],{"href":34923},[32,37194,9018],{},[32,37196,36936],{},"로 단순화(이전엔 pending\u002Frejected만 명시). 본문 3분기:",[81,37199,37200,37203,37206],{},[84,37201,37202],{},"pending: \"사업자등록증을 등록해 주세요\"",[84,37204,37205],{},"reviewing: \"사업자등록증 심사 중입니다 — 영업일 1~2일 내 안내\"",[84,37207,37208],{},"rejected: \"사업자등록증 심사 반려 — 사유: …\"",[18,37210,37211,37212,37215,37216,37219],{},"CTA 라벨: rejected=\"다시 제출하기\" \u002F reviewing=\"진행 상태 보기\" \u002F pending=\"사업자등록증 등록\". 클래스는 ",[32,37213,37214],{},":class=\"state\"","로 단순화(",[32,37217,37218],{},"approval-banner.reviewing","은 별도 톤 없이 pending과 같은 warning 톤 유지).",[237,37221,37223],{"id":37222},"appmemberinfopanel-페이지-배너","AppMemberInfoPanel — 페이지 배너",[18,37225,37226,37228,37229,4703],{},[26,37227,33421],{"href":32722}," — 같은 패턴(3분기 strong + p). 클래스는 ",[32,37230,37231],{},":class=\"approvalState\"",[237,37233,37235],{"id":37234},"appcontractpanel-패널-상단-상태-카드","AppContractPanel — 패널 상단 상태 카드",[18,37237,37238,89,37241,37244,37245,37247],{},[26,37239,36680],{"href":37240},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppContractPanel.vue",[32,37242,37243],{},"\u003Cdiv class=\"state-card\" :class=\"approvalState\">"," 신규(",[32,37246,33817],{}," 화면에서만 노출):",[81,37249,37250,37253,37256],{},[84,37251,37252],{},"pending: warning 톤 + 시계 아이콘 + \"사업자등록증을 등록해 주세요\" + \"PDF, 최대 10MB\" 안내",[84,37254,37255],{},"reviewing: info 톤 + 로딩 아이콘 + \"사업자등록증 심사 중입니다\" + 영업일 안내",[84,37257,37258],{},"rejected: danger 톤 + X 아이콘 + 반려 사유 + \"다시 첨부 시 재심사\" 안내",[18,37260,37261,37262,4536,37265,4536,37268,5606],{},"CSS 토큰만 사용(",[32,37263,37264],{},"--warning-line",[32,37266,37267],{},"--info-soft",[32,37269,37270],{},"--danger",[237,37272,37274],{"id":37273},"pickfile-성공-후-store-즉시-갱신","pickFile 성공 후 store 즉시 갱신",[5251,37276,37278],{"className":7962,"code":37277,"language":7964,"meta":4208,"style":4208},"await api('\u002Fcontracts\u002Ffiles', { method: 'POST', body: form })\nawait loadFiles()\nif (target === 'biz') await auth.fetchMe()\ntoast.add({ title: target === 'biz' && approvalState.value === 'reviewing'\n  ? '사업자등록증이 제출되었습니다. 심사가 진행됩니다.'\n  : '서류가 첨부되었습니다.', ... })\n",[32,37279,37280,37300,37309,37331,37356,37364],{"__ignoreMap":4208},[7968,37281,37282,37285,37287,37289,37292,37295,37297],{"class":5110,"line":7970},[7968,37283,37284],{"class":7973},"await",[7968,37286,9260],{"class":7980},[7968,37288,6100],{"class":7984},[7968,37290,37291],{"class":7993},"'\u002Fcontracts\u002Ffiles'",[7968,37293,37294],{"class":7984},", { method: ",[7968,37296,34156],{"class":7993},[7968,37298,37299],{"class":7984},", body: form })\n",[7968,37301,37302,37304,37307],{"class":5110,"line":4212},[7968,37303,37284],{"class":7973},[7968,37305,37306],{"class":7980}," loadFiles",[7968,37308,9268],{"class":7984},[7968,37310,37311,37313,37316,37318,37320,37322,37324,37327,37329],{"class":5110,"line":4209},[7968,37312,31692],{"class":7973},[7968,37314,37315],{"class":7984}," (target ",[7968,37317,31645],{"class":7973},[7968,37319,36979],{"class":7993},[7968,37321,21438],{"class":7984},[7968,37323,37284],{"class":7973},[7968,37325,37326],{"class":7984}," auth.",[7968,37328,28992],{"class":7980},[7968,37330,9268],{"class":7984},[7968,37332,37333,37336,37339,37342,37344,37346,37348,37351,37353],{"class":5110,"line":4219},[7968,37334,37335],{"class":7984},"toast.",[7968,37337,37338],{"class":7980},"add",[7968,37340,37341],{"class":7984},"({ title: target ",[7968,37343,31645],{"class":7973},[7968,37345,36979],{"class":7993},[7968,37347,34285],{"class":7973},[7968,37349,37350],{"class":7984}," approvalState.value ",[7968,37352,31645],{"class":7973},[7968,37354,37355],{"class":7993}," 'reviewing'\n",[7968,37357,37358,37361],{"class":5110,"line":8291},[7968,37359,37360],{"class":7973},"  ?",[7968,37362,37363],{"class":7993}," '사업자등록증이 제출되었습니다. 심사가 진행됩니다.'\n",[7968,37365,37366,37369,37372,37374,37376],{"class":5110,"line":8301},[7968,37367,37368],{"class":7973},"  :",[7968,37370,37371],{"class":7993}," '서류가 첨부되었습니다.'",[7968,37373,4473],{"class":7984},[7968,37375,9432],{"class":7973},[7968,37377,8274],{"class":7984},[18,37379,37380,37382,37383,37385,37386,37388,37389,37391],{},[32,37381,28976],{},"로 store가 갱신되면 — 글로벌 띠(",[32,37384,34887],{},")·페이지 배너(",[32,37387,18572],{}," 진입 시)·이 패널 상태 카드(",[32,37390,33685],{}," computed) 모두 같은 store를 구독하므로 즉시 \"심사 중\"으로 전환됨.",[76,37393,37395],{"id":37394},"_124-라이브-e2e-검증","12.4 라이브 e2e 검증",[101,37397,37398,37408],{},[104,37399,37400],{},[107,37401,37402,37404,37406],{},[110,37403,3846],{},[110,37405,27964],{},[110,37407,22362],{},[126,37409,37410,37422,37435,37447,37460],{},[107,37411,37412,37414,37416],{},[131,37413,4636],{},[131,37415,36580],{},[131,37417,36626,37418,6219,37420],{},[32,37419,3987],{},[32,37421,33672],{},[107,37423,37424,37426,37430],{},[131,37425,4649],{},[131,37427,37428],{},[32,37429,36268],{},[131,37431,36626,37432,37434],{},[32,37433,36207],{}," 1건 자동 생성됨",[107,37436,37437,37439,37444],{},[131,37438,4662],{},[131,37440,37441,37443],{},[32,37442,36314],{}," (kind=biz)",[131,37445,37446],{},"✅ 201",[107,37448,37449,37451,37455],{},[131,37450,4678],{},[131,37452,37453,33367],{},[32,37454,21820],{},[131,37456,36626,37457],{},[32,37458,37459],{},"approvalState='reviewing'",[107,37461,37462,37464,37468],{},[131,37463,4691],{},[131,37465,37466],{},[32,37467,33880],{},[131,37469,37470],{},"✅ 403 \"사업자등록증 심사가 진행 중입니다. 승인 완료 후 정보를 수정할 수 있습니다.\"",[18,37472,37473],{},"테스트 데이터 cleanup(R2 객체 1 + DB rows) 완료.",[76,37475,37477],{"id":37476},"_125-산출물","12.5 산출물",[81,37479,37480,37498],{},[84,37481,32568,37482,37485,37486,4536,37489,4536,37492,37495,37496],{},[32,37483,37484],{},"malgn-noti-api: 66dab21"," — 3 파일 수정(",[32,37487,37488],{},"routes\u002Fcontracts.ts",[32,37490,37491],{},"middleware\u002Fapproval.ts",[32,37493,37494],{},"routes\u002Fme.ts","). Workers 배포 Version ",[32,37497,36830],{},[84,37499,33415,37500,37503,37504,4536,37506,4536,37508,4536,37510,37512,37513],{},[32,37501,37502],{},"malgn-noti: 5d530d9"," — 4 파일 수정(",[32,37505,34918],{},[32,37507,32723],{},[32,37509,35887],{},[32,37511,11040],{},"). Pages 배포 alias ",[32,37514,36833],{},[76,37516,37518],{"id":37517},"_126-알려진-한계-후속-작업","12.6 알려진 한계 \u002F 후속 작업",[81,37520,37521,37533,37541,37556],{},[84,37522,37523,37526,37527,37529,37530,37532],{},[21,37524,37525],{},"운영자단 심사 화면 미구현"," (§7.7부터 누적) — ",[32,37528,36757],{},"까지 자동 진행되지만 승인\u002F반려는 여전히 DB 직접 UPDATE. 운영자단 BackOffice 화면(",[32,37531,25421],{},") 필요.",[84,37534,37535,89,37538,37540],{},[21,37536,37537],{},"알림 미발송",[32,37539,37085],{}," 전이 시 사용자에게 알림 메일\u002FSMS 없음. NHN 자격증명 발급 후 trigger.",[84,37542,37543,34030,37546,37548,37549,37551,37552,37555],{},[21,37544,37545],{},"반려 후 재첨부 시 사유 처리",[32,37547,36785],{},"을 그대로 둔 채 ",[32,37550,36757],{},"으로 전이. 운영자가 새 심사에서 결정하도록 위임. 정책상 더 명확히 하려면 재첨부 시 ",[32,37553,37554],{},"rejected_reason=NULL","로 정리도 가능 — 향후 결정.",[84,37557,37558,37561],{},[21,37559,37560],{},"사업자등록증 외 보조 서류 정책"," — 대부업등록증·보험증권은 첨부해도 상태 전이 없음. 운영자가 별도로 확인. 후속 정책 검토 시 변경 가능.",[73,37563],{},[11,37565,37567],{"id":37566},"_13-accountcontract-첫-진입-회복-lazy-auto-create-reviewing-자동-회복-배포-18-pages-6a7a7d2ca657b2","§13. \u002Faccount\u002Fcontract 첫 진입 회복 — lazy auto-create + reviewing 자동 회복 (배포 #18 \u002F Pages 6a7a7d2·ca657b2)",[76,37569,27637],{"id":37570},"한-줄-12",[18,37572,37573,37574,37577,37578,37580,37581,37583,37584,37587,37588,37590,37591,37594,37595,37598,37599,37602,37603,37606,37607,37609,37610,37612,37613,37615,37616,37618,37619,37622,37623,37626,37627,37630,37631,37633],{},"§11\u002F§12 배포 직후 사용자(",[32,37575,37576],{},"bubin@malgnsoft.com",", 회사 16)에게서 두 가지 잔존 문제 보고: ",[21,37579,35815],{}," \"파일 선택해도 아무 액션이 없다\" — §11 배포 이전에 가입한 사업자라서 signup auto-create가 안 일어났고, ",[32,37582,35831],{}," 0건 → ",[32,37585,37586],{},"activeContractId=undefined"," → 업로드 분기 무력화. ",[21,37589,35844],{}," \"사업자등록증을 업로드한 상태인데 화면이 '등록해 주세요'(pending)로 나온다\" — §12 배포(17:18 UTC) ",[21,37592,37593],{},"이전인 17:10에 첨부","한 상태라 reviewing 전이가 안 일어나고 ",[32,37596,37597],{},"approval_state=pending"," 그대로. 두 케이스 모두 ",[21,37600,37601],{},"타이밍 race","로 §11\u002F§12 신규 코드가 기존 데이터에는 미적용. 백엔드 두 군데에 ",[21,37604,37605],{},"lazy backfill"," 추가 — ",[32,37608,36268],{},"는 회사 corp\u002Fsole + 계약 0건이면 ",[32,37611,36207],{}," 자동 INSERT, ",[32,37614,36301],{},"는 회사 pending + biz 파일 1건 이상이면 ",[32,37617,36757],{},"으로 자동 UPDATE. 사용자가 새로고침 한 번이면 자동 복구. 회사 16은 즉시 DB UPDATE로 backfill 완료. Workers 배포 #18(",[32,37620,37621],{},"35e2ec85...",") 및 추가 패치 (",[32,37624,37625],{},"456b73c2...","). 프런트 SSR 안전성도 함께 보강 — ",[32,37628,37629],{},"await Promise.all","을 try\u002Fcatch로 감싸고 ",[32,37632,8663],{}," 재시도.",[76,37635,37637],{"id":37636},"_131-두-가지-문제와-원인","13.1 두 가지 문제와 원인",[101,37639,37640,37654],{},[104,37641,37642],{},[107,37643,37644,37646,37649,37652],{},[110,37645,3846],{},[110,37647,37648],{},"증상",[110,37650,37651],{},"회사 16 DB 상태",[110,37653,13542],{},[126,37655,37656,37670],{},[107,37657,37658,37660,37663,37667],{},[131,37659,35815],{},[131,37661,37662],{},"\"파일 선택 후 아무 액션 없음\"",[131,37664,37665,31064],{},[32,37666,35831],{},[131,37668,37669],{},"§11 signup auto-create가 적용된 시점 이전 가입",[107,37671,37672,37674,37677,37682],{},[131,37673,35844],{},[131,37675,37676],{},"\"업로드 후에도 'pending'으로 노출\"",[131,37678,37679,37680],{},"biz 파일 1건 + ",[32,37681,33549],{},[131,37683,37684],{},"첨부 시점이 §12 배포(17:18) 이전(17:10)",[18,37686,37687,37688,37691,37692,6219,37695,37697],{},"(a)는 ",[32,37689,37690],{},"activeContractId"," computed가 첫 계약을 선택하는데 contracts 빈 배열이면 ",[32,37693,37694],{},"undefined",[32,37696,36820],{},"이 \"활성 계약을 찾을 수 없습니다\" 토스트만 떨궈서 사용자에겐 \"아무 일도 안 일어난\" 것처럼 보임. 토스트는 떴지만 짧고 우측 상단이라 놓치기 쉬움.",[18,37699,37700,37701,37703,37704,37706,37707,37709],{},"(b)는 §12의 ",[32,37702,37085],{}," 전이가 ",[32,37705,36314],{}," 시점에만 발동하는 설계라서, 그 코드가 라이브에 올라가기 ",[21,37708,11524],{},"에 이미 첨부한 사용자는 이벤트가 사라진 셈.",[76,37711,37713],{"id":37712},"_132-백엔드-두-군데-lazy-backfill","13.2 백엔드 두 군데 lazy backfill",[237,37715,37717],{"id":37716},"get-contracts-커밋-6a7a7d2","GET \u002Fcontracts (커밋 6a7a7d2)",[5251,37719,37721],{"className":7962,"code":37720,"language":7964,"meta":4208,"style":4208},"let rows = await select()\nif (rows.length === 0) {\n  const cs = await db.select({ companyType: company.companyType })\n    .from(company).where(eq(company.id, companyId)).limit(1)\n  const ct = cs[0]?.companyType\n  if (ct === 'corp' || ct === 'sole') {\n    await db.insert(contract).values({\n      companyId,\n      title: '최초 이용계약 온라인체결',\n      version: '신규',\n      contractState: 'initial',\n      status: 1,\n    })\n    rows = await select()\n  }\n}\n",[32,37722,37723,37740,37755,37772,37797,37813,37835,37849,37854,37863,37872,37881,37890,37895,37908,37912],{"__ignoreMap":4208},[7968,37724,37725,37728,37731,37733,37735,37738],{"class":5110,"line":7970},[7968,37726,37727],{"class":7973},"let",[7968,37729,37730],{"class":7984}," rows ",[7968,37732,8254],{"class":7973},[7968,37734,9280],{"class":7973},[7968,37736,37737],{"class":7980}," select",[7968,37739,9268],{"class":7984},[7968,37741,37742,37744,37747,37749,37751,37753],{"class":5110,"line":4212},[7968,37743,31692],{"class":7973},[7968,37745,37746],{"class":7984}," (rows.",[7968,37748,33268],{"class":8892},[7968,37750,33271],{"class":7973},[7968,37752,33274],{"class":8892},[7968,37754,21462],{"class":7984},[7968,37756,37757,37759,37761,37763,37765,37767,37769],{"class":5110,"line":4209},[7968,37758,34235],{"class":7973},[7968,37760,36988],{"class":8892},[7968,37762,9210],{"class":7973},[7968,37764,9280],{"class":7973},[7968,37766,34357],{"class":7984},[7968,37768,34360],{"class":7980},[7968,37770,37771],{"class":7984},"({ companyType: company.companyType })\n",[7968,37773,37774,37777,37779,37781,37783,37785,37787,37789,37791,37793,37795],{"class":5110,"line":4219},[7968,37775,37776],{"class":7984},"    .",[7968,37778,9490],{"class":7980},[7968,37780,34373],{"class":7984},[7968,37782,34376],{"class":7980},[7968,37784,6100],{"class":7984},[7968,37786,34381],{"class":7980},[7968,37788,34384],{"class":7984},[7968,37790,34387],{"class":7980},[7968,37792,6100],{"class":7984},[7968,37794,4636],{"class":8892},[7968,37796,9563],{"class":7984},[7968,37798,37799,37801,37804,37806,37808,37810],{"class":5110,"line":8291},[7968,37800,34235],{"class":7973},[7968,37802,37803],{"class":8892}," ct",[7968,37805,9210],{"class":7973},[7968,37807,37023],{"class":7984},[7968,37809,5354],{"class":8892},[7968,37811,37812],{"class":7984},"]?.companyType\n",[7968,37814,37815,37817,37820,37822,37824,37826,37829,37831,37833],{"class":5110,"line":8301},[7968,37816,21447],{"class":7973},[7968,37818,37819],{"class":7984}," (ct ",[7968,37821,31645],{"class":7973},[7968,37823,36142],{"class":7993},[7968,37825,36145],{"class":7973},[7968,37827,37828],{"class":7984}," ct ",[7968,37830,31645],{"class":7973},[7968,37832,36153],{"class":7993},[7968,37834,21462],{"class":7984},[7968,37836,37837,37839,37841,37843,37845,37847],{"class":5110,"line":8310},[7968,37838,34477],{"class":7973},[7968,37840,34357],{"class":7984},[7968,37842,36165],{"class":7980},[7968,37844,36168],{"class":7984},[7968,37846,36171],{"class":7980},[7968,37848,7985],{"class":7984},[7968,37850,37851],{"class":5110,"line":8321},[7968,37852,37853],{"class":7984},"      companyId,\n",[7968,37855,37856,37859,37861],{"class":5110,"line":8332},[7968,37857,37858],{"class":7984},"      title: ",[7968,37860,36186],{"class":7993},[7968,37862,36189],{"class":7984},[7968,37864,37865,37868,37870],{"class":5110,"line":8343},[7968,37866,37867],{"class":7984},"      version: ",[7968,37869,36197],{"class":7993},[7968,37871,36189],{"class":7984},[7968,37873,37874,37877,37879],{"class":5110,"line":8349},[7968,37875,37876],{"class":7984},"      contractState: ",[7968,37878,36207],{"class":7993},[7968,37880,36189],{"class":7984},[7968,37882,37883,37886,37888],{"class":5110,"line":8454},[7968,37884,37885],{"class":7984},"      status: ",[7968,37887,4636],{"class":8892},[7968,37889,36189],{"class":7984},[7968,37891,37892],{"class":5110,"line":8472},[7968,37893,37894],{"class":7984},"    })\n",[7968,37896,37897,37900,37902,37904,37906],{"class":5110,"line":8494},[7968,37898,37899],{"class":7984},"    rows ",[7968,37901,8254],{"class":7973},[7968,37903,9280],{"class":7973},[7968,37905,37737],{"class":7980},[7968,37907,9268],{"class":7984},[7968,37909,37910],{"class":5110,"line":8515},[7968,37911,21497],{"class":7984},[7968,37913,37914],{"class":5110,"line":8525},[7968,37915,8987],{"class":7984},[18,37917,37918,37920],{},[32,37919,33576],{},"은 trigger 안 함 — 승인 게이트 대상이 아니라서.",[237,37922,37924],{"id":37923},"get-contractsfiles-커밋-ca657b2","GET \u002Fcontracts\u002Ffiles (커밋 ca657b2)",[5251,37926,37928],{"className":7962,"code":37927,"language":7964,"meta":4208,"style":4208},"const hasBiz = rows.some(r => r.name.startsWith('사업자등록증_'))\nif (hasBiz) {\n  const cs = await db.select({ state: company.approvalState })\n    .from(company).where(eq(company.id, companyId)).limit(1)\n  if (cs[0]?.state === 'pending') {\n    await db.update(company)\n      .set({ approvalState: 'reviewing' })\n      .where(eq(company.id, companyId))\n  }\n}\n",[32,37929,37930,37964,37971,37988,38012,38028,38039,38051,38063,38067],{"__ignoreMap":4208},[7968,37931,37932,37934,37937,37939,37942,37944,37946,37949,37951,37954,37956,37958,37961],{"class":5110,"line":7970},[7968,37933,9204],{"class":7973},[7968,37935,37936],{"class":8892}," hasBiz",[7968,37938,9210],{"class":7973},[7968,37940,37941],{"class":7984}," rows.",[7968,37943,35168],{"class":7980},[7968,37945,6100],{"class":7984},[7968,37947,37948],{"class":8939},"r",[7968,37950,28485],{"class":7973},[7968,37952,37953],{"class":7984}," r.name.",[7968,37955,35190],{"class":7980},[7968,37957,6100],{"class":7984},[7968,37959,37960],{"class":7993},"'사업자등록증_'",[7968,37962,37963],{"class":7984},"))\n",[7968,37965,37966,37968],{"class":5110,"line":4212},[7968,37967,31692],{"class":7973},[7968,37969,37970],{"class":7984}," (hasBiz) {\n",[7968,37972,37973,37975,37977,37979,37981,37983,37985],{"class":5110,"line":4209},[7968,37974,34235],{"class":7973},[7968,37976,36988],{"class":8892},[7968,37978,9210],{"class":7973},[7968,37980,9280],{"class":7973},[7968,37982,34357],{"class":7984},[7968,37984,34360],{"class":7980},[7968,37986,37987],{"class":7984},"({ state: company.approvalState })\n",[7968,37989,37990,37992,37994,37996,37998,38000,38002,38004,38006,38008,38010],{"class":5110,"line":4219},[7968,37991,37776],{"class":7984},[7968,37993,9490],{"class":7980},[7968,37995,34373],{"class":7984},[7968,37997,34376],{"class":7980},[7968,37999,6100],{"class":7984},[7968,38001,34381],{"class":7980},[7968,38003,34384],{"class":7984},[7968,38005,34387],{"class":7980},[7968,38007,6100],{"class":7984},[7968,38009,4636],{"class":8892},[7968,38011,9563],{"class":7984},[7968,38013,38014,38016,38018,38020,38022,38024,38026],{"class":5110,"line":8291},[7968,38015,21447],{"class":7973},[7968,38017,37009],{"class":7984},[7968,38019,5354],{"class":8892},[7968,38021,37014],{"class":7984},[7968,38023,31645],{"class":7973},[7968,38025,27184],{"class":7993},[7968,38027,21462],{"class":7984},[7968,38029,38030,38032,38034,38036],{"class":5110,"line":8301},[7968,38031,34477],{"class":7973},[7968,38033,34357],{"class":7984},[7968,38035,37043],{"class":7980},[7968,38037,38038],{"class":7984},"(company)\n",[7968,38040,38041,38043,38045,38047,38049],{"class":5110,"line":8310},[7968,38042,37060],{"class":7984},[7968,38044,37048],{"class":7980},[7968,38046,37051],{"class":7984},[7968,38048,36806],{"class":7993},[7968,38050,8274],{"class":7984},[7968,38052,38053,38055,38057,38059,38061],{"class":5110,"line":8321},[7968,38054,37060],{"class":7984},[7968,38056,34376],{"class":7980},[7968,38058,6100],{"class":7984},[7968,38060,34381],{"class":7980},[7968,38062,37069],{"class":7984},[7968,38064,38065],{"class":5110,"line":8332},[7968,38066,21497],{"class":7984},[7968,38068,38069],{"class":5110,"line":8343},[7968,38070,8987],{"class":7984},[18,38072,38073,4361,38075,4361,38077,38079],{},[32,38074,36757],{},[32,38076,33595],{},[32,38078,33601],{},"인 회사는 손대지 않음 — pending만 보정.",[18,38081,38082,38083,38085,38086,38088],{},"다음 ",[32,38084,3987],{}," hydrate(또는 ",[32,38087,28976],{}," 호출)부터 글로벌 띠·페이지 배너 모두 정상.",[76,38090,38092],{"id":38091},"_133-즉시-backfill-회사-16","13.3 즉시 backfill (회사 16)",[5251,38094,38096],{"className":31016,"code":38095,"language":31018,"meta":4208,"style":4208},"INSERT INTO TB_CONTRACT (company_id, title, version, contract_state, status)\nVALUES (16, '최초 이용계약 온라인체결', '신규', 'initial', 1);\n\nUPDATE TB_COMPANY\nSET approval_state='reviewing'\nWHERE id=16;\n",[32,38097,38098,38103,38108,38112,38117,38122],{"__ignoreMap":4208},[7968,38099,38100],{"class":5110,"line":7970},[7968,38101,38102],{},"INSERT INTO TB_CONTRACT (company_id, title, version, contract_state, status)\n",[7968,38104,38105],{"class":5110,"line":4212},[7968,38106,38107],{},"VALUES (16, '최초 이용계약 온라인체결', '신규', 'initial', 1);\n",[7968,38109,38110],{"class":5110,"line":4209},[7968,38111,8288],{"emptyLinePlaceholder":4259},[7968,38113,38114],{"class":5110,"line":4219},[7968,38115,38116],{},"UPDATE TB_COMPANY\n",[7968,38118,38119],{"class":5110,"line":8291},[7968,38120,38121],{},"SET approval_state='reviewing'\n",[7968,38123,38124],{"class":5110,"line":8301},[7968,38125,38126],{},"WHERE id=16;\n",[18,38128,38129],{},"사용자에게 새로고침 안내. 같은 조건의 다른 회사가 있어도 이번 1회로 모두 보정(전체 1건만 해당).",[76,38131,38133],{"id":38132},"_134-프런트-ssr-안전성-보강-커밋-b7e8a21","13.4 프런트 SSR 안전성 보강 (커밋 b7e8a21)",[18,38135,38136,38138,38139,38141],{},[32,38137,35887],{},"의 top-level await가 SSR에서 401·네트워크 실패 시 페이지 전체가 죽었다. try\u002Fcatch로 감싸고 ",[32,38140,8663],{},"에서 한 번 더 시도하도록 변경 — 백엔드 lazy auto-create와 함께 새로고침 한 번으로 정상 복구된다.",[5251,38143,38145],{"className":7962,"code":38144,"language":7964,"meta":4208,"style":4208},"try { await Promise.all([loadContracts(), loadFiles()]) }\ncatch { \u002F* ignore — onMounted에서 재시도 *\u002F }\nonMounted(async () => {\n  if (contracts.value.length === 0 && bizFiles.value.length === 0) {\n    try { await Promise.all([loadContracts(), loadFiles()]) }\n    catch { \u002F* ignore *\u002F }\n  }\n})\n",[32,38146,38147,38176,38188,38203,38229,38254,38266,38270],{"__ignoreMap":4208},[7968,38148,38149,38152,38154,38156,38158,38160,38162,38164,38167,38170,38173],{"class":5110,"line":7970},[7968,38150,38151],{"class":7973},"try",[7968,38153,27200],{"class":7984},[7968,38155,37284],{"class":7973},[7968,38157,33292],{"class":8892},[7968,38159,4703],{"class":7984},[7968,38161,33297],{"class":7980},[7968,38163,34153],{"class":7984},[7968,38165,38166],{"class":7980},"loadContracts",[7968,38168,38169],{"class":7984},"(), ",[7968,38171,38172],{"class":7980},"loadFiles",[7968,38174,38175],{"class":7984},"()]) }\n",[7968,38177,38178,38181,38183,38186],{"class":5110,"line":4212},[7968,38179,38180],{"class":7973},"catch",[7968,38182,27200],{"class":7984},[7968,38184,38185],{"class":8398},"\u002F* ignore — onMounted에서 재시도 *\u002F",[7968,38187,9317],{"class":7984},[7968,38189,38190,38192,38194,38196,38199,38201],{"class":5110,"line":4209},[7968,38191,8663],{"class":7980},[7968,38193,6100],{"class":7984},[7968,38195,9453],{"class":7973},[7968,38197,38198],{"class":7984}," () ",[7968,38200,9426],{"class":7973},[7968,38202,8887],{"class":7984},[7968,38204,38205,38207,38210,38212,38214,38216,38218,38221,38223,38225,38227],{"class":5110,"line":4219},[7968,38206,21447],{"class":7973},[7968,38208,38209],{"class":7984}," (contracts.value.",[7968,38211,33268],{"class":8892},[7968,38213,33271],{"class":7973},[7968,38215,33274],{"class":8892},[7968,38217,34285],{"class":7973},[7968,38219,38220],{"class":7984}," bizFiles.value.",[7968,38222,33268],{"class":8892},[7968,38224,33271],{"class":7973},[7968,38226,33274],{"class":8892},[7968,38228,21462],{"class":7984},[7968,38230,38231,38234,38236,38238,38240,38242,38244,38246,38248,38250,38252],{"class":5110,"line":8291},[7968,38232,38233],{"class":7973},"    try",[7968,38235,27200],{"class":7984},[7968,38237,37284],{"class":7973},[7968,38239,33292],{"class":8892},[7968,38241,4703],{"class":7984},[7968,38243,33297],{"class":7980},[7968,38245,34153],{"class":7984},[7968,38247,38166],{"class":7980},[7968,38249,38169],{"class":7984},[7968,38251,38172],{"class":7980},[7968,38253,38175],{"class":7984},[7968,38255,38256,38259,38261,38264],{"class":5110,"line":8301},[7968,38257,38258],{"class":7973},"    catch",[7968,38260,27200],{"class":7984},[7968,38262,38263],{"class":8398},"\u002F* ignore *\u002F",[7968,38265,9317],{"class":7984},[7968,38267,38268],{"class":5110,"line":8310},[7968,38269,21497],{"class":7984},[7968,38271,38272],{"class":5110,"line":8321},[7968,38273,8007],{"class":7984},[76,38275,38277],{"id":38276},"_135-산출물","13.5 산출물",[81,38279,38280,38296,38304],{},[84,38281,32568,38282,38285,38286,38288,38289,38292,38293],{},[32,38283,38284],{},"malgn-noti-api: 6a7a7d2, ca657b2"," — 2 파일 수정(",[32,38287,37488],{},"). Workers 배포 #18 Version ",[32,38290,38291],{},"35e2ec85-3e89-4986-b120-d9cf5bbf877b",", 후속 ",[32,38294,38295],{},"456b73c2-c5de-4a99-aece-b4457c0bcd8d",[84,38297,33415,38298,89,38301,38303],{},[32,38299,38300],{},"malgn-noti: b7e8a21",[32,38302,35887],{}," SSR fallback",[84,38305,38306,38307,38309,38310,38313],{},"DB: 회사 16에 ",[32,38308,35831],{}," id=3 backfill + ",[32,38311,38312],{},"approval_state='reviewing'"," UPDATE",[76,38315,38317],{"id":38316},"_136-교훈","13.6 교훈",[18,38319,38320,38321,38324],{},"이번 같은 \"신규 코드 + 기존 데이터\" race는 회원·인증처럼 ",[21,38322,38323],{},"사용자 라이프사이클 이벤트 트리거","가 정책에 묶일 때 흔하다. 두 가지 패턴으로 방어:",[6674,38326,38327,38333],{},[84,38328,38329,38332],{},[21,38330,38331],{},"조회 시점 lazy backfill"," — 이번 §13에서 채택. GET 응답을 떠올릴 때 현재 코드가 보장해야 할 상태를 함께 확인·보정. 데이터마이그레이션 미수행 가능.",[84,38334,38335,38338],{},[21,38336,38337],{},"명시적 backfill 마이그레이션"," — DDL이나 SQL 스크립트로 일괄 보정. 정합성은 더 명확하지만 운영 절차 필요.",[18,38340,38341],{},"규칙은 신규 데이터가 적고 정책 trigger가 단순한 경우 1번이 비용 대비 효과 좋음. 향후 같은 패턴(사업자등록증 외 다른 본인확인·서류 흐름)에서도 1번을 default로 두는 것을 권장.",[73,38343],{},[11,38345,38347],{"id":38346},"_14-사업자등록증-파일-행에-심사-상태-배지-반려-시-삭제-pages-7675ce8f","§14. 사업자등록증 파일 행에 심사 상태 배지 + 반려 시 삭제 (Pages 7675ce8f)",[76,38349,27637],{"id":38350},"한-줄-13",[18,38352,38353,38354,38357,38358,38361,38362,38365,38366,38368,38369,38371],{},"§12까지의 안내는 패널 상단 카드와 글로벌 띠에 집중되어 있었는데, ",[21,38355,38356],{},"파일 행 자체","에서 상태가 한눈에 안 보였다. 사용자 요청으로 사업자등록증 행 우측에 ",[21,38359,38360],{},"회사 승인 상태 배지","(reviewing\u002Fapproved\u002Frejected) 표시 + ",[21,38363,38364],{},"반려 시에만 삭제 버튼"," 노출. 같은 묶음의 모든 biz 파일이 같은 회사 상태를 공유하므로 모든 행에 동일 배지. 삭제 후에도 회사는 ",[32,38367,33601],{}," 유지(운영자 결정 보존) → 새 파일 첨부 시 백엔드(§12)가 자동 ",[32,38370,36757],{},"으로 전이.",[76,38373,38375],{"id":38374},"_141-화면-변경","14.1 화면 변경",[81,38377,38378,38384,38409,38420],{},[84,38379,38380,38381],{},"파일 행 = ",[32,38382,38383],{},"[아이콘] [이름·메타] [심사 상태 배지] [확인] [(반려 시) 삭제]",[84,38385,38386,38387],{},"배지 색상 매핑:\n",[81,38388,38389,38394,38399,38404],{},[84,38390,38391,38393],{},[32,38392,36757],{}," → info 톤 + 로딩 아이콘 + \"심사 중\"",[84,38395,38396,38398],{},[32,38397,33595],{}," → success 톤 + 체크 + \"승인\"",[84,38400,38401,38403],{},[32,38402,33601],{}," → danger 톤 + X + \"반려\"",[84,38405,38406,38408],{},[32,38407,27043],{}," → 파일 자체가 없으므로 배지 미표시",[84,38410,38411,38412,38415,38416,38419],{},"삭제 버튼은 빨간 outline (",[32,38413,38414],{},".df-remove"," 자체 클래스 — 글로벌 ",[32,38417,38418],{},"btn-outline-danger","가 없어 인라인 정의)",[84,38421,38422,38424,38425,38428,38429,38432],{},[32,38423,36820],{},"이 안정적인 키를 쓰도록 ",[32,38426,38427],{},":key=\"f.id\"","(이전엔 ",[32,38430,38431],{},"name + at"," 조합)",[76,38434,38436],{"id":38435},"_142-삭제-후-상태-정책","14.2 삭제 후 상태 정책",[101,38438,38439,38449],{},[104,38440,38441],{},[107,38442,38443,38446],{},[110,38444,38445],{},"상황",[110,38447,38448],{},"변동",[126,38450,38451,38467,38478],{},[107,38452,38453,38458],{},[131,38454,38455,38457],{},[32,38456,33601],{}," 상태에서 biz 파일 삭제",[131,38459,38460,38461,6071,38463,38466],{},"회사 ",[32,38462,20267],{},[21,38464,38465],{},"그대로 rejected"," (사유도 유지)",[107,38468,38469,38472],{},[131,38470,38471],{},"그 뒤 새 파일 첨부",[131,38473,38474,38475,38477],{},"§12 코드가 ",[32,38476,37091],{}," 자동 전이",[107,38479,38480,38487],{},[131,38481,38482,4361,38484,38486],{},[32,38483,36757],{},[32,38485,33595],{}," 중에는",[131,38488,38489],{},"삭제 버튼이 안 보여 사용자 실수 방지",[76,38491,38493],{"id":38492},"_143-산출물","14.3 산출물",[81,38495,38496],{},[84,38497,33415,38498,89,38501,38503,38504],{},[32,38499,38500],{},"malgn-noti: 79e51af",[32,38502,35887],{}," 단일 파일 수정. Pages 배포 alias ",[32,38505,38506],{},"7675ce8f.malgn-noti.pages.dev",[76,38508,38510],{"id":38509},"_144-알려진-한계","14.4 알려진 한계",[81,38512,38513],{},[84,38514,38515,38517],{},[32,38516,36757],{}," 중 잘못 올린 파일을 사용자가 스스로 정정 못함. 정책상 \"심사 중에는 변경 불가\"가 안전하지만, UX적으로 답답할 수 있음. 운영자단 심사 화면이 생기면 같은 곳에서 처리 가능.",[73,38519],{},[11,38521,38523],{"id":38522},"_15-계약서-서명-다이얼로그-휴대폰-본인인증-sub-step-공인인증서-탭-제거-배포-workers-85de422a-pages-38d4e40e573a6200","§15. 계약서 서명 다이얼로그 — 휴대폰 본인인증 sub-step + 공인인증서 탭 제거 (배포 Workers 85de422a \u002F Pages 38d4e40e·573a6200)",[76,38525,27637],{"id":38526},"한-줄-14",[18,38528,38529,38531,38532,38535,38536,38539,38540,5069,38543,38546,38547,38549],{},[32,38530,19398],{},"의 STEP 3 \"전자서명\u002F공인인증\" 화면에 ",[21,38533,38534],{},"휴대폰 본인인증 sub-step","을 선행으로 추가. 인증 통과 전엔 서명·정보 테이블 자체가 노출되지 않음. 가입 시 NICE로 검증한 본인 휴대폰(",[32,38537,38538],{},"TB_USER.phone",")으로 SMS OTP를 발송 → 6자리 확인 → 통과 시 success 톤으로 전환되며 서명 캔버스 자동 셋업. 백엔드는 기존 ",[32,38541,38542],{},"POST \u002Fauth\u002Fphone-code\u002F{send,verify}",[32,38544,38545],{},"purpose='contract_sign'","을 새 enum 값으로 추가(기존 인프라 그대로 재활용). 후속 사용자 피드백 두 가지(\"등록 정보 없음으로 노출됨\" \u002F \"공인인증서 삭제\") 반영해 — (a) 다이얼로그 open 시 ",[32,38548,29025],{}," 강제 hydrate로 stale 휴대폰 회복, (b) 공인인증서 탭\u002F영역\u002F관련 CSS·상태 전부 제거해 단일 전자서명 흐름으로 단순화.",[76,38551,38553],{"id":38552},"_151-백엔드-contract_sign-purpose-추가-workers-85de422a","15.1 백엔드 — 'contract_sign' purpose 추가 (Workers 85de422a)",[18,38555,38556,38558,38559,4339,38561,4361,38564,38567,38568,38571,38572,38575],{},[32,38557,31508],{}," enum + ",[32,38560,29653],{},[32,38562,38563],{},"sendPhoneCodeB",[32,38565,38566],{},"verifyPhoneCodeB"," zod enum에 ",[32,38569,38570],{},"contract_sign"," 추가. 별도 라우트는 만들지 않고 기존 phone-code 인프라(SHA-256 해시·TTL 10분·5회 시도 제한·재발송 시 직전 코드 무효화·소비 후 재사용 차단) 그대로 재사용. OpenAPI 두 path의 schema enum 동기화. SMS 본문은 ",[32,38573,38574],{},"[맑은 메시징] 계약서 전자서명 인증코드: NNNNNN (10분 유효)"," 형태.",[18,38577,38578,38579,38582],{},"라이브 e2e: 발송 → mockCode 수신 → verify 200 → 소비 후 재시도 401(",[32,38580,38581],{},"인증코드가 만료되었거나 발급된 적이 없습니다",") 모두 정상.",[76,38584,38586],{"id":38585},"_152-다이얼로그-본인인증-sub-step-pages-38d4e40e","15.2 다이얼로그 본인인증 sub-step (Pages 38d4e40e)",[18,38588,38589,9148],{},[32,38590,38591],{},"AppContractSignDialog.vue",[81,38593,38594,38597,38604,38610,38617,38627,38632],{},[84,38595,38596],{},"STEP 3 상단에 본인인증 카드(info 톤 → 통과 후 success 톤 전환)",[84,38598,38599,38600,38603],{},"카드 구조: 헤더(strong + p) → 휴대폰 마스킹 표시(",[32,38601,38602],{},"010-****-1111",") + \"인증번호 받기\" → 발송 후 6자리 입력 + \"확인\"",[84,38605,38606,38609],{},[32,38607,38608],{},"phoneVerified === false"," 동안은 서명·정보 테이블·캔버스 모두 미노출",[84,38611,38612,38613,38616],{},"통과 시 ",[32,38614,38615],{},"setupCanvas()"," 직접 호출 (탭 watcher 제거)",[84,38618,38619,38622,38623,38626],{},[32,38620,38621],{},"canComplete"," computed에 ",[32,38624,38625],{},"phoneVerified.value"," 추가 → \"서명 완료\" 버튼 게이팅",[84,38628,38629,38631],{},[32,38630,29092],{},"에 본인인증 가드 추가(방어적)",[84,38633,38634,38637],{},[32,38635,38636],{},"reset()","에 인증 상태 초기화 추가 — 같은 다이얼로그를 닫고 다시 열면 처음부터",[18,38639,38640],{},"부가 정합화:",[81,38642,38643,38663],{},[84,38644,38645,38646,7505,38649,38652,38653,4361,38656,4361,38659,38662],{},"사업자명\u002F대표자 정보가 하드코딩(",[32,38647,38648],{},"(주)맑은소프트",[32,38650,38651],{},"하근호",")이었던 것을 ",[32,38654,38655],{},"auth.tenant.name",[32,38657,38658],{},"bizNo",[32,38660,38661],{},"ceoName","로 동적 바인딩",[84,38664,38665,38666,38669],{},"서명자명 기본값을 ",[32,38667,38668],{},"auth.user.name","으로 자동 채움",[76,38671,38673],{"id":38672},"_153-사용자-피드백-후속-pages-573a6200","15.3 사용자 피드백 후속 (Pages 573a6200)",[6674,38675,38676,38698],{},[84,38677,38678,38681,38682,38685,38686,38688,38689],{},[21,38679,38680],{},"\"등록 정보 없음으로 표시됨\""," — 회사 16 user는 phone=",[32,38683,38684],{},"010-1111-1111","이 있는데 다이얼로그가 stale state로 빈 값을 표시. 다이얼로그 open watcher에 ",[32,38687,29025],{}," 강제 호출 추가. 회원정보 수정 직후나 어떤 경로로든 다이얼로그 열릴 때마다 최신 데이터로 hydrate.\n",[81,38690,38691],{},[84,38692,38693,38694,38697],{},"미등록 케이스 안내문구도 강화 — ",[32,38695,38696],{},"회원 정보에 휴대폰 번호가 등록되어 있지 않습니다."," (danger 톤)",[84,38699,38700,89,38703,4361,38706,38709,38710,38713,38714,38717],{},[21,38701,38702],{},"\"공인인증서 삭제\"",[32,38704,38705],{},"signTab",[32,38707,38708],{},"certLoaded"," 상태, ",[32,38711,38712],{},"\u003Cbutton>공인인증서\u003C\u002Fbutton>"," 탭, ",[32,38715,38716],{},".cd-cert-*"," CSS 100여 줄 모두 제거. STEP 3은 본인인증 → 전자서명 단일 흐름.",[76,38719,38721],{"id":38720},"_154-라이브-e2e-production","15.4 라이브 e2e (Production)",[101,38723,38724,38734],{},[104,38725,38726],{},[107,38727,38728,38730,38732],{},[110,38729,3846],{},[110,38731,27964],{},[110,38733,22362],{},[126,38735,38736,38753,38767,38777],{},[107,38737,38738,38740,38748],{},[131,38739,4636],{},[131,38741,38742,6685,38744,38747],{},[32,38743,31450],{},[32,38745,38746],{},"purpose=contract_sign",", mock 모드)",[131,38749,38750,38751],{},"✅ 200 + ",[32,38752,29476],{},[107,38754,38755,38757,38762],{},[131,38756,4649],{},[131,38758,38759,38761],{},[32,38760,31453],{}," (올바른 코드)",[131,38763,36626,38764],{},[32,38765,38766],{},"{verified: true}",[107,38768,38769,38771,38774],{},[131,38770,4662],{},[131,38772,38773],{},"같은 코드 재시도",[131,38775,38776],{},"✅ 401 — 소비 후 재사용 차단 정상",[107,38778,38779,38781,38784],{},[131,38780,4678],{},[131,38782,38783],{},"잘못된 코드",[131,38785,38786,38787],{},"✅ 401 — ",[32,38788,38581],{},[18,38790,38791],{},"E2E 잔존 데이터 cleanup 완료.",[76,38793,38795],{"id":38794},"_155-산출물","15.5 산출물",[81,38797,38798,38811],{},[84,38799,32568,38800,38285,38803,4536,38806,37495,38808],{},[32,38801,38802],{},"malgn-noti-api: cd75d0c",[32,38804,38805],{},"routes\u002Fauth.ts",[32,38807,25376],{},[32,38809,38810],{},"85de422a-2ad7-4ce6-929c-8f2b29f03a6e",[84,38812,33415,38813,89,38816,38818,38819,6219,38822],{},[32,38814,38815],{},"malgn-noti: 40979f6, 0054bfc",[32,38817,38591],{}," 단일 파일(235 lines 추가 + 인증서 영역 107 lines 제거). Pages 배포 alias ",[32,38820,38821],{},"38d4e40e",[32,38823,38824],{},"573a6200",[76,38826,38828],{"id":38827},"_156-알려진-한계-후속","15.6 알려진 한계 \u002F 후속",[81,38830,38831,38840,38853],{},[84,38832,38833,38836,38837,38839],{},[21,38834,38835],{},"NICE 자격증명은 §16 참조"," — 현재 NHN_MOCK + NICE_MOCK 둘 다 켜진 mock 모드라서 사용자가 받는 SMS는 실제 발송 안 됨. 토스트에 ",[32,38838,29476],{},"가 노출되어 본인 검증.",[84,38841,38842,38845,38846,38848,38849,38852],{},[21,38843,38844],{},"서명 데이터 보존"," — 현재 캔버스 ink 데이터를 PNG로 저장하지 않음. ",[32,38847,36278],{}," 호출만 백엔드에 보내고 상태만 전이. 법적 효력을 강화하려면 캔버스 이미지를 R2 또는 ",[32,38850,38851],{},"TB_CONTRACT.signed_image_r2_key"," 컬럼에 저장 검토.",[84,38854,38855,38858],{},[21,38856,38857],{},"본인인증 결과 영속화"," — 인증 통과는 다이얼로그 메모리에만 존재. 같은 사용자가 같은 계약 서명을 다시 시작하면 재인증 필요. 정책상 적절 (전자서명법 등본인 동의 강도).",[73,38860],{},[11,38862,38864],{"id":38863},"_16-운영-노트-nice-nhn-notification-hub-자격증명-시도와-보류-라이브-운영-변경","§16. 운영 노트 — NICE \u002F NHN Notification Hub 자격증명 시도와 보류 (라이브 운영 변경)",[76,38866,27637],{"id":38867},"한-줄-15",[18,38869,38870,38871,38874,38875,38878,38879,38882,38883,38886,38887,38890,38891,38894],{},"오늘 두 외부 서비스의 production 자격증명 등록을 시도 — 둘 다 ",[21,38872,38873],{},"외부 측 제약","으로 mock 모드 유지 결정. ",[21,38876,38877],{},"NICE","는 자격증명 등록 성공했으나 NICE 콘솔의 ",[21,38880,38881],{},"IP 화이트리스트(에러 1007)"," 미해결로 즉시 mock 복귀. ",[21,38884,38885],{},"NHN Notification Hub","는 사용자가 준 자격이 ",[32,38888,38889],{},"AppKey","만이었는데 공식 문서 확인 결과 ",[21,38892,38893],{},"기존 채널별 SDK와 완전히 다른 인증 모델","(OAuth2 client_credentials → Bearer 토큰)이라 어댑터 재작성 + User Access Key + Secret Access Key 발급 필요. 둘 다 영업·콘솔 작업 대기.",[76,38896,38898],{"id":38897},"_161-nice-본인인증","16.1 NICE 본인인증",[237,38900,27681],{"id":27681},[18,38902,38903,38905],{},[32,38904,21365],{},"으로 3개 등록:",[81,38907,38908,38916,38925],{},[84,38909,38910,12760,38913],{},[32,38911,38912],{},"NICE_CLIENT_ID",[32,38914,38915],{},"NIed76e1a1-236a-4cfc-b3b3-4c3586b3dfcf",[84,38917,38918,12760,38921,38924],{},[32,38919,38920],{},"NICE_CLIENT_SECRET",[32,38922,38923],{},"NzY0..."," (전문 보안 사유 생략)",[84,38926,38927,12760,38930],{},[32,38928,38929],{},"NICE_RETURN_URL",[32,38931,38932],{},"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fauth\u002Fnice\u002Fcallback",[18,38934,38935,38938,38939,38941],{},[32,38936,38937],{},"NICE_MOCK"," 삭제 후 ",[32,38940,32203],{}," 호출 → 500 응답.",[237,38943,38944],{"id":38944},"진단",[18,38946,38947,38950],{},[32,38948,38949],{},"wrangler tail","로 캡처한 에러 로그:",[5251,38952,38955],{"className":38953,"code":38954,"language":5256},[5254],"(error) [onError] Error: NICE token failed: 1007 허용되지 않은 IP 접근\n",[32,38956,38954],{"__ignoreMap":4208},[18,38958,38959,38960,38963,38964,38967],{},"NICE 콘솔의 API 보안 정책에 ",[21,38961,38962],{},"호출 출발지 IP 화이트리스트","가 활성화된 상태. Cloudflare Workers는 outbound IP가 동적이라 단일 IP 등록 불가. ",[32,38965,38966],{},"doc\u002FNICE_AUTH.md §9","에서 사전 예측한 한계 그대로.",[237,38969,10663],{"id":10663},[81,38971,38972,38978],{},[84,38973,38974,38977],{},[32,38975,38976],{},"NICE_MOCK=1"," 다시 등록 → 가입 흐름 mock 모드 복귀(정상 동작 확인)",[84,38979,38980,7505,38982,7505,38984,38986,38987,38990,38991,38993],{},[32,38981,38912],{},[32,38983,38920],{},[32,38985,38929],{}," 3 secret은 ",[21,38988,38989],{},"유지"," — IP 정책 해결 시 ",[32,38992,32642],{}," 한 번이면 real 전환",[237,38995,38997],{"id":38996},"해결-옵션-사용자-결정-대기","해결 옵션 (사용자 결정 대기)",[101,38999,39000,39011],{},[104,39001,39002],{},[107,39003,39004,39007,39009],{},[110,39005,39006],{},"옵션",[110,39008,267],{},[110,39010,7718],{},[126,39012,39013,39024,39035],{},[107,39014,39015,39018,39021],{},[131,39016,39017],{},"A. NICE 콘솔에서 Cloudflare egress IP 등록",[131,39019,39020],{},"NICE 영업담당에게 IP 목록 송부 후 콘솔 반영",[131,39022,39023],{},"통상 거절될 가능성",[107,39025,39026,39029,39032],{},[131,39027,39028],{},"B. NICE 콘솔에서 IP 검사 OFF",[131,39030,39031],{},"콘솔 → API 설정 토글 해제",[131,39033,39034],{},"가장 단순, 보안 등급은 다소 낮아짐",[107,39036,39037,39040,39043],{},[131,39038,39039],{},"C. 고정 IP 프록시 EC2",[131,39041,39042],{},"AWS EC2 nano, Workers → EC2 → NICE",[131,39044,39045],{},"가장 안정, 월 비용 발생",[18,39047,39048,39049,39052],{},"사용자 의사로 IP 정책은 일단 ",[21,39050,39051],{},"보류",", 자격증명만 보관 상태.",[237,39054,39056],{"id":39055},"보안-메모","보안 메모",[18,39058,39059,39062,39063,39066],{},[32,39060,39061],{},"CLIENT_SECRET","이 채팅 평문에 노출됐다. IP 정책 해결 시점에 NICE 콘솔에서 한 번 회전 권장. 회전 후 ",[32,39064,39065],{},"wrangler secret put NICE_CLIENT_SECRET"," 재등록.",[76,39068,39070],{"id":39069},"_162-nhn-notification-hub","16.2 NHN Notification Hub",[237,39072,39074],{"id":39073},"사용자-제공","사용자 제공",[81,39076,39077,39083,39089],{},[84,39078,39079,39080],{},"AppKey: ",[32,39081,39082],{},"JhgDNGyD9dyYQqH5",[84,39084,39085,39086],{},"BaseURL: ",[32,39087,39088],{},"https:\u002F\u002Fnotification-hub.api.nhncloudservice.com",[84,39090,39091,39092],{},"Secret Key: ",[21,39093,39094],{},"제공되지 않음",[237,39096,39098],{"id":39097},"_1차-진단-기존-채널별-sdk-가정","1차 진단 — 기존 채널별 SDK 가정",[18,39100,39101,39102,39105,39106,39109,39110,39112],{},"현재 어댑터(",[32,39103,39104],{},"src\u002Fadapters\u002Fnhn\u002F{sms,email,push,kakao}.ts",")는 채널별 분리 API(",[32,39107,39108],{},"https:\u002F\u002Fapi-sms.cloud.toast.com"," 등)에 대해 작성됨. 인증은 ",[32,39111,25120],{}," 헤더. 사용자에게 SecretKey 요청.",[237,39114,39116],{"id":39115},"_2차-진단-공식-문서-확인","2차 진단 — 공식 문서 확인",[18,39118,39119,39124],{},[26,39120,39123],{"href":39121,"rel":39122},"https:\u002F\u002Fdocs.nhncloud.com\u002Fko\u002FNotification\u002FNotification%20Hub\u002Fko\u002Fapi-guide-v1x0\u002Fcommon-info\u002F",[30],"NHN Cloud Notification Hub 공통 정보"," 확인 결과:",[18,39126,39127],{},[21,39128,39129],{},"Notification Hub는 기존 NHN 채널별 API와 완전히 다른 신규 통합 서비스.",[101,39131,39132,39146],{},[104,39133,39134],{},[107,39135,39136,39138,39141],{},[110,39137,6889],{},[110,39139,39140],{},"기존 채널별 NHN",[110,39142,39143],{},[21,39144,39145],{},"Notification Hub",[126,39147,39148,39163,39177,39191],{},[107,39149,39150,39153,39158],{},[131,39151,39152],{},"인증 헤더",[131,39154,39155],{},[32,39156,39157],{},"X-Secret-Key: \u003C키>",[131,39159,39160],{},[32,39161,39162],{},"Authorization: Bearer \u003C토큰>",[107,39164,39165,39168,39171],{},[131,39166,39167],{},"자격 종류",[131,39169,39170],{},"AppKey + SecretKey (정적)",[131,39172,39173,39176],{},[21,39174,39175],{},"User Access Key ID + Secret Access Key"," (OAuth2)",[107,39178,39179,39182,39185],{},[131,39180,39181],{},"토큰 발급",[131,39183,39184],{},"불필요",[131,39186,39187,39190],{},[32,39188,39189],{},"POST https:\u002F\u002Foauth.api.nhncloudservice.com\u002Foauth2\u002Ftoken\u002Fcreate"," (Bearer, TTL 24h)",[107,39192,39193,39196,39199],{},[131,39194,39195],{},"AppKey 역할",[131,39197,39198],{},"경로 + 인증",[131,39200,39201,39202,39205],{},"JWT 토큰 발급 시 scope(",[32,39203,39204],{},"scope=appKey:\u003CAppKey>",")에만 사용",[18,39207,39208],{},"토큰 발급 cURL:",[5251,39210,39212],{"className":10197,"code":39211,"language":10199,"meta":4208,"style":4208},"curl -X POST 'https:\u002F\u002Foauth.api.nhncloudservice.com\u002Foauth2\u002Ftoken\u002Fcreate' \\\n  -H 'Content-Type: application\u002Fx-www-form-urlencoded' \\\n  -u 'UserAccessKeyID:SecretAccessKey' \\\n  -d 'grant_type=client_credentials' \\\n  -d 'scope=appKey:\u003CAppKey>'\n",[32,39213,39214,39227,39236,39246,39256],{"__ignoreMap":4208},[7968,39215,39216,39218,39220,39222,39225],{"class":5110,"line":7970},[7968,39217,21193],{"class":7980},[7968,39219,21196],{"class":8892},[7968,39221,21199],{"class":7993},[7968,39223,39224],{"class":7993}," 'https:\u002F\u002Foauth.api.nhncloudservice.com\u002Foauth2\u002Ftoken\u002Fcreate'",[7968,39226,10292],{"class":8892},[7968,39228,39229,39231,39234],{"class":5110,"line":4212},[7968,39230,21209],{"class":8892},[7968,39232,39233],{"class":7993}," 'Content-Type: application\u002Fx-www-form-urlencoded'",[7968,39235,10292],{"class":8892},[7968,39237,39238,39241,39244],{"class":5110,"line":4209},[7968,39239,39240],{"class":8892},"  -u",[7968,39242,39243],{"class":7993}," 'UserAccessKeyID:SecretAccessKey'",[7968,39245,10292],{"class":8892},[7968,39247,39248,39251,39254],{"class":5110,"line":4219},[7968,39249,39250],{"class":8892},"  -d",[7968,39252,39253],{"class":7993}," 'grant_type=client_credentials'",[7968,39255,10292],{"class":8892},[7968,39257,39258,39260],{"class":5110,"line":8291},[7968,39259,39250],{"class":8892},[7968,39261,39262],{"class":7993}," 'scope=appKey:\u003CAppKey>'\n",[237,39264,10663],{"id":39265},"결정-1",[18,39267,39268],{},"User Access Key ID + Secret Access Key를 받으면 다음을 한 번에 진행:",[6674,39270,39271,39277,39292,39296],{},[84,39272,39273,39276],{},[21,39274,39275],{},"NHN 어댑터 재작성"," — 채널별 SDK → Notification Hub 통합 API. 경로 구조와 페이로드 모두 변경. OAuth 토큰 발급 + KV 캐싱(24h) 헬퍼 추가.",[84,39278,39279,7505,39282,7505,39285,7505,39288,39291],{},[32,39280,39281],{},"wrangler secret put NHN_OAUTH_USER_KEY",[32,39283,39284],{},"NHN_OAUTH_SECRET_KEY",[32,39286,39287],{},"NHN_APP_KEY",[32,39289,39290],{},"NHN_BASE_URL"," 4개 등록.",[84,39293,39294,4703],{},[32,39295,29892],{},[84,39297,39298],{},"e2e — SMS 1건 + Email 1건 실 발송.",[18,39300,39301,39302,39304,39305,39307],{},"지금은 자격 미수령 + 어댑터 재작성 미진행 → 사용자단·관리자단의 모든 발송 호출은 ",[32,39303,25204],{}," 그대로 mock 모드 유지(가입 OTP 토스트의 ",[32,39306,29476],{},"로 검증 정상).",[237,39309,39311],{"id":39310},"코드-변경-없음","코드 변경 없음",[18,39313,39314,39315,39318,39319,39321],{},"이 §16은 운영 시도 + 외부 제약 확인 + 보류 결정의 기록. ",[21,39316,39317],{},"코드\u002F배포 변경은 없음","(자격 secret put\u002Fdelete + ",[32,39320,38937],{}," 재등록만). 어댑터 재작성은 키 수령 시점에 §17 이후로 별도.",[76,39323,39325],{"id":39324},"_163-산출물","16.3 산출물",[81,39327,39328,39331,39358],{},[84,39329,39330],{},"코드: 없음",[84,39332,39333,39334],{},"secret 변경(production Workers):\n",[81,39335,39336,39348],{},[84,39337,39338,7505,39341,7505,39344,39347],{},[32,39339,39340],{},"+NICE_CLIENT_ID",[32,39342,39343],{},"+NICE_CLIENT_SECRET",[32,39345,39346],{},"+NICE_RETURN_URL"," — 등록 후 유지",[84,39349,39350,39353,39354,39357],{},[32,39351,39352],{},"-NICE_MOCK"," 일시 삭제 → ",[32,39355,39356],{},"+NICE_MOCK=1"," 복원",[84,39359,39360,39361],{},"외부 미해결:\n",[81,39362,39363,39366],{},[84,39364,39365],{},"NICE: 1007 IP 화이트리스트 (사용자 콘솔 작업)",[84,39367,39368],{},"NHN: User Access Key 발급 (사용자 콘솔 작업)",[76,39370,39372],{"id":39371},"_164-다음-단계","16.4 다음 단계",[101,39374,39375,39386],{},[104,39376,39377],{},[107,39378,39379,39381,39384],{},[110,39380,6889],{},[110,39382,39383],{},"트리거",[110,39385,267],{},[126,39387,39388,39401,39412],{},[107,39389,39390,39393,39396],{},[131,39391,39392],{},"NICE real 전환",[131,39394,39395],{},"사용자가 IP 정책 해결 (옵션 B 권장)",[131,39397,39398,39400],{},[32,39399,32642],{}," + e2e 1건",[107,39402,39403,39406,39409],{},[131,39404,39405],{},"NHN real 전환",[131,39407,39408],{},"사용자가 User Access Key 발급",[131,39410,39411],{},"어댑터 재작성 + secret 등록 + e2e SMS·Email",[107,39413,39414,39417,39420],{},[131,39415,39416],{},"사용자 안내 자동화",[131,39418,39419],{},"위 두 trigger 발생 시",[131,39421,39422],{},"메일 발송 + SMS 통지 인프라가 정확히 위 두 secret에 의존하므로 동시에 enable",[8560,39424,39425],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":4208,"searchDepth":4209,"depth":4209,"links":39427},[39428,39429,39430,39431,39432,39433,39434,39435,39436,39437,39439,39441,39442,39443,39444,39445,39446,39447,39450,39451,39452,39453,39454,39455,39456,39457,39458,39459,39461,39462,39463,39464,39465,39466,39467,39468,39470,39472,39474,39475,39476,39478,39479,39480,39481,39482,39483,39484,39485,39486,39487,39488,39489,39490,39495,39500,39501,39502,39503,39504,39506,39507,39508,39509,39510,39511,39513,39515,39517,39518,39519,39520,39521,39522,39523,39524,39525,39526,39527,39529,39530,39531,39532,39534,39535,39537,39538,39539,39540,39541,39543,39548,39555,39556,39557,39558,39559,39560,39564,39565,39566,39567,39568,39569,39570,39571,39572,39573,39574,39575,39576,39577,39578,39579,39580,39581,39588,39595,39596],{"id":10635,"depth":4212,"text":10636},{"id":27636,"depth":4212,"text":27637},{"id":30195,"depth":4212,"text":30196},{"id":30202,"depth":4212,"text":30203},{"id":30286,"depth":4212,"text":30287},{"id":30312,"depth":4212,"text":30313},{"id":30421,"depth":4212,"text":30422},{"id":30443,"depth":4212,"text":30444},{"id":28262,"depth":4212,"text":27637},{"id":30531,"depth":4212,"text":39438},"2.1 백엔드 — POST \u002Fauth\u002Flogin-by-email",{"id":30619,"depth":4212,"text":39440},"2.2 프런트 — login\u002Findex.vue 개편",{"id":30698,"depth":4212,"text":30699},{"id":30780,"depth":4212,"text":30781},{"id":30820,"depth":4212,"text":30821},{"id":30848,"depth":4212,"text":30849},{"id":28712,"depth":4212,"text":27637},{"id":30925,"depth":4212,"text":30926},{"id":31006,"depth":4212,"text":31007,"children":39448},[39449],{"id":31038,"depth":4209,"text":31039},{"id":31093,"depth":4212,"text":31094},{"id":31163,"depth":4212,"text":31164},{"id":31234,"depth":4212,"text":31235},{"id":31302,"depth":4212,"text":31303},{"id":31340,"depth":4212,"text":31341},{"id":31400,"depth":4212,"text":31401},{"id":29444,"depth":4212,"text":27637},{"id":31444,"depth":4212,"text":31445},{"id":31543,"depth":4212,"text":31544},{"id":31604,"depth":4212,"text":39460},"4.3 useApi.ts 401 처리 분리 — \u002Fauth\u002F*는 호출자가 처리",{"id":31747,"depth":4212,"text":31748},{"id":31758,"depth":4212,"text":31759},{"id":31804,"depth":4212,"text":31805},{"id":31839,"depth":4212,"text":31840},{"id":31856,"depth":4212,"text":27637},{"id":29484,"depth":4212,"text":29485},{"id":31956,"depth":4212,"text":31957},{"id":32086,"depth":4212,"text":39469},"5.3 NICE 어댑터 — src\u002Fadapters\u002Fnice\u002Fauth.ts",{"id":32179,"depth":4212,"text":39471},"5.4 라우트 — src\u002Froutes\u002Fnice.ts",{"id":32253,"depth":4212,"text":39473},"5.5 \u002Fauth\u002Fsignup 확장",{"id":32320,"depth":4212,"text":32321},{"id":32398,"depth":4212,"text":32399},{"id":32509,"depth":4212,"text":39477},"5.8 정본 문서 — doc\u002FNICE_AUTH.md",{"id":32562,"depth":4212,"text":32563},{"id":32627,"depth":4212,"text":32628},{"id":32704,"depth":4212,"text":27637},{"id":32741,"depth":4212,"text":32742},{"id":32897,"depth":4212,"text":32898},{"id":32993,"depth":4212,"text":32994},{"id":33308,"depth":4212,"text":33309},{"id":33395,"depth":4212,"text":33396},{"id":33430,"depth":4212,"text":33431},{"id":33481,"depth":4212,"text":27637},{"id":33515,"depth":4212,"text":33516},{"id":33606,"depth":4212,"text":33607},{"id":33652,"depth":4212,"text":33653,"children":39491},[39492,39493,39494],{"id":33656,"depth":4209,"text":33657},{"id":33689,"depth":4209,"text":33690},{"id":33710,"depth":4209,"text":33711},{"id":33749,"depth":4212,"text":33750,"children":39496},[39497,39498,39499],{"id":33753,"depth":4209,"text":11040},{"id":33777,"depth":4209,"text":14163},{"id":33803,"depth":4209,"text":32723},{"id":33842,"depth":4212,"text":33843},{"id":33964,"depth":4212,"text":33965},{"id":34011,"depth":4212,"text":34012},{"id":34087,"depth":4212,"text":27637},{"id":34120,"depth":4212,"text":39505},"8.1 미들웨어 — src\u002Fmiddleware\u002Fapproval.ts",{"id":34518,"depth":4212,"text":34519},{"id":34722,"depth":4212,"text":34723},{"id":34814,"depth":4212,"text":34815},{"id":34831,"depth":4212,"text":34832},{"id":34878,"depth":4212,"text":27637},{"id":34914,"depth":4212,"text":39512},"9.1 글로벌 띠 — AppApprovalBanner.vue",{"id":35016,"depth":4212,"text":39514},"9.2 글로벌 라우트 가드 — middleware\u002Fapproval.global.ts",{"id":35295,"depth":4212,"text":39516},"9.3 \u002Fhome 페이지 미승인 분기",{"id":35341,"depth":4212,"text":35342},{"id":35393,"depth":4212,"text":35394},{"id":35417,"depth":4212,"text":35418},{"id":35475,"depth":4212,"text":27637},{"id":35505,"depth":4212,"text":35506},{"id":35588,"depth":4212,"text":35589},{"id":35650,"depth":4212,"text":35651},{"id":35742,"depth":4212,"text":35743},{"id":35756,"depth":4212,"text":35757},{"id":35806,"depth":4212,"text":27637},{"id":35903,"depth":4212,"text":39528},"11.1 결정 — TB_CONTRACT_FILE.kind 컬럼 없음 → name 접두사 사용",{"id":35996,"depth":4212,"text":35997},{"id":36035,"depth":4212,"text":36036},{"id":36119,"depth":4212,"text":36120},{"id":36238,"depth":4212,"text":39533},"11.5 \u002Fcontracts 라우트 — 5 엔드포인트",{"id":36367,"depth":4212,"text":36368},{"id":36413,"depth":4212,"text":39536},"11.7 프런트 — AppContractPanel.vue 실 API 연동",{"id":36558,"depth":4212,"text":36559},{"id":36646,"depth":4212,"text":36647},{"id":36692,"depth":4212,"text":36693},{"id":36746,"depth":4212,"text":27637},{"id":36837,"depth":4212,"text":39542},"12.1 정책 — approval_state 4단계로 확장",{"id":36954,"depth":4212,"text":36955,"children":39544},[39545,39547],{"id":36958,"depth":4209,"text":39546},"POST \u002Fcontracts\u002Ffiles (kind=biz만 발동)",{"id":37112,"depth":4209,"text":37113},{"id":37170,"depth":4212,"text":37171,"children":39549},[39550,39551,39552,39553,39554],{"id":37174,"depth":4209,"text":37175},{"id":37187,"depth":4209,"text":37188},{"id":37222,"depth":4209,"text":37223},{"id":37234,"depth":4209,"text":37235},{"id":37273,"depth":4209,"text":37274},{"id":37394,"depth":4212,"text":37395},{"id":37476,"depth":4212,"text":37477},{"id":37517,"depth":4212,"text":37518},{"id":37570,"depth":4212,"text":27637},{"id":37636,"depth":4212,"text":37637},{"id":37712,"depth":4212,"text":37713,"children":39561},[39562,39563],{"id":37716,"depth":4209,"text":37717},{"id":37923,"depth":4209,"text":37924},{"id":38091,"depth":4212,"text":38092},{"id":38132,"depth":4212,"text":38133},{"id":38276,"depth":4212,"text":38277},{"id":38316,"depth":4212,"text":38317},{"id":38350,"depth":4212,"text":27637},{"id":38374,"depth":4212,"text":38375},{"id":38435,"depth":4212,"text":38436},{"id":38492,"depth":4212,"text":38493},{"id":38509,"depth":4212,"text":38510},{"id":38526,"depth":4212,"text":27637},{"id":38552,"depth":4212,"text":38553},{"id":38585,"depth":4212,"text":38586},{"id":38672,"depth":4212,"text":38673},{"id":38720,"depth":4212,"text":38721},{"id":38794,"depth":4212,"text":38795},{"id":38827,"depth":4212,"text":38828},{"id":38867,"depth":4212,"text":27637},{"id":38897,"depth":4212,"text":38898,"children":39582},[39583,39584,39585,39586,39587],{"id":27681,"depth":4209,"text":27681},{"id":38944,"depth":4209,"text":38944},{"id":10663,"depth":4209,"text":10663},{"id":38996,"depth":4209,"text":38997},{"id":39055,"depth":4209,"text":39056},{"id":39069,"depth":4212,"text":39070,"children":39589},[39590,39591,39592,39593,39594],{"id":39073,"depth":4209,"text":39074},{"id":39097,"depth":4209,"text":39098},{"id":39115,"depth":4209,"text":39116},{"id":39265,"depth":4209,"text":10663},{"id":39310,"depth":4209,"text":39311},{"id":39324,"depth":4212,"text":39325},{"id":39371,"depth":4212,"text":39372},{},"\u002Fhistory\u002Fhistory.20260602",{"title":30087,"description":4208},"history\u002Fhistory.20260602","qSA_cFf7cbjHG83YXzNS-tN2bwvJoG2cLVnlVx8NWMQ",{"id":39603,"title":39604,"body":39605,"description":4208,"extension":4257,"meta":43617,"navigation":4259,"path":43618,"seo":43619,"stem":43620,"__hash__":43621},"docs\u002Fhistory\u002Fhistory.20260604.md","2026-06-04 — Hyperdrive Cloudflare Tunnel 전환 + 관리자단 핸드오프 17 페이지 풀세트 + NICE IPv6 진단 + WBS R2 편집 + 이메일 변경 버그 fix + 광고 수신 일시 기록",{"type":8,"value":39606,"toc":43552},[39607,39610,39612,39816,39818,39822,39824,39875,39879,39894,39898,40060,40069,40073,40108,40117,40121,40145,40149,40157,40159,40163,40165,40189,40193,40254,40261,40265,40284,40288,40355,40357,40381,40385,40409,40411,40415,40417,40434,40438,40444,40779,40783,40843,40847,41030,41044,41048,41054,41083,41086,41090,41111,41118,41122,41128,41131,41135,41147,41151,41195,41197,41201,41203,41252,41256,41262,41318,41321,41324,41372,41375,41401,41408,41411,41466,41499,41503,41510,41616,41637,41657,41679,41682,41746,41748,41771,41775,41794,41798,41869,41871,41875,41879,41897,41901,41950,41954,41956,42012,42014,42036,42040,42047,42103,42107,42132,42136,42163,42167,42187,42191,42194,42280,42284,42304,42306,42308,42312,42314,42362,42366,42447,42451,42478,42485,42514,42518,42574,42576,42617,42621,42636,42638,42645,42647,42655,42659,42755,42759,42781,42785,42793,42795,42797,42801,42803,42832,42836,42855,42857,42918,42922,42931,42978,42986,42990,43017,43029,43175,43185,43189,43201,43205,43250,43254,43271,43273,43277,43279,43285,43289,43303,43314,43318,43359,43363,43413,43417,43432,43434,43464,43468,43491,43493,43497,43549],[11,39608,39604],{"id":39609},"_2026-06-04-hyperdrive-cloudflare-tunnel-전환-관리자단-핸드오프-17-페이지-풀세트-nice-ipv6-진단-wbs-r2-편집-이메일-변경-버그-fix-광고-수신-일시-기록",[76,39611,10636],{"id":10635},[18,39613,39614,39615,39618,39619,39622,39623,39626,39627,39630,39631,6251,39633,39635,39636,39638,39639,39642,39643,39646,39647,24216,39650,39653,39654,39657,39658,39661,39662,39665,39666,39669,39670,39673,39674,39677,39678,39681,39682,39685,39686,39689,39690,24216,39692,39695,39696,39699,39700,39703,39704,39706,39707,39710,39711,6100,39714,39717,39718,39721,39722,39724,39725,6219,39728,39731,39732,39735,39736,4339,39739,39742,39743,51,39745,89,39748,39751,39752,39755,39756,39759,39760,39762,39763,39766,39767,51,39769,39772,39773,39776,39777,39780,39781,39784,39785,39787,39788,39790,39791,39793,39794,39797,39798,39801,39802,39805,39806,39809,39810,39797,39813,4703],{},"오늘 9건 처리. ",[21,39616,39617],{},"(§6)"," NHN Notification Hub 어댑터 신규(OAuth2 client_credentials → Bearer) + SMS·Email 라우트 활성화. Email real 발송 검증 통과(messageId 발급). + 서비스 담당자 이메일 변경 라우트(",[32,39620,39621],{},"POST \u002Fme\u002Femail-change",", OTP + 비밀번호 검증, ",[21,39624,39625],{},"loginid 유지·email만 UPDATE",") 및 사용자단 다이얼로그 실 API 연동. ",[21,39628,39629],{},"(§7)"," WBS 현행화 — R2 정본(",[32,39632,58],{},[32,39634,42],{}," 동시 갱신. Step 5 진척 48% → 55%, 가중평균 약 47.5%. 5-2 신규 19\u002F20\u002F21(WBS R2 \u002F 이메일 변경 \u002F NHN OAuth 어댑터), 5-3-15(",[32,39637,26863],{}," 인라인 편집), 5-3C-7(완료 승급), 5-3C-20(이메일 변경 다이얼로그), 5-4-14\u002F15\u002F16(핸드오프 17 페이지 + dev 라벨 + 관리자 로고), 5-5-10\u002F11\u002F12(Hyperdrive Tunnel \u002F NHN Email real \u002F NHN SMS pending). ",[21,39640,39641],{},"(§8)"," 서비스 담당자 이메일 변경 — 두 가지 버그 fix. (a) ",[32,39644,39645],{},"\u002Fauth\u002Femail-code\u002Fverify"," 가 ",[32,39648,39649],{},"purpose='change_email'",[32,39651,39652],{},"consumed_at"," 을 마킹해 직후 ",[32,39655,39656],{},"\u002Fme\u002Femail-change"," 의 OTP 재검증이 항상 실패하던 이중 소비 버그 → verify-only 분기로 수정. (b) 비밀번호·OTP 오류가 401 로 떨어져 ",[32,39659,39660],{},"useApi.onResponseError"," 가 토큰 만료로 오인하고 자동 로그아웃을 트리거하던 문제 → 새 헬퍼 ",[32,39663,39664],{},"errors.unprocessable()","(422) 도입해 본인 재인증 실패를 별도 코드로 분리. (c) 다이얼로그 ",[32,39667,39668],{},"submit()"," 이 ",[32,39671,39672],{},"submitting=true"," 만 켜고 부모의 ",[32,39675,39676],{},"close"," emit 을 기다리던 데드락 → ",[32,39679,39680],{},"onConfirm: (p) => Promise\u003Cvoid>"," 콜백 prop 패턴으로 전환해 다이얼로그가 자기 상태를 직접 관리. ",[21,39683,39684],{},"(§9)"," 광고성 메일 수신 동의\u002F거부 일시 기록 — DDL 0006 (",[32,39687,39688],{},"TB_COMPANY.ad_receive_at DATETIME NULL",") Aurora 적용 + ",[32,39691,32700],{},[32,39693,39694],{},"adReceive"," 변경 시 ",[32,39697,39698],{},"adReceiveAt: new Date()"," 동시 기록. 사용자단 패널에 ",[32,39701,39702],{},"[🕒] YYYY.MM.DD HH:mm 광고성 메일 수신에 동의\u002F거부함"," 회색 칩 노출 + 토스트 description 에 처리 일시 표기. 정보통신망법 50조 동의 증빙용. ",[21,39705,30100],{}," NICE 자격증명 재확인 + 옵션 B(Cloudflare 대역 등록) 시도 → 여전히 1007. 진단용 ",[32,39708,39709],{},"\u002Fdiag\u002Fegress","로 Workers의 outbound가 ",[21,39712,39713],{},"IPv6",[32,39715,39716],{},"2a06:98c0:3600::103",", Cloudflare ",[32,39719,39720],{},"2a06:98c0::\u002F29",")임을 확인. NICE 콘솔에 IPv4 대역만 등록된 게 원인 — IPv6 대역(7개) 추가 등록 안내 + mock 복귀. ",[21,39723,30104],{}," Hyperdrive 바인딩 교체 — ",[32,39726,39727],{},"a2ba4efe...",[32,39729,39730],{},"439b109d...",". 신규 Hyperdrive는 ",[21,39733,39734],{},"Cloudflare Tunnel(Access)"," 기반(host ",[32,39737,39738],{},"malgn-dev-db.apiserver.kr",[32,39740,39741],{},"access_client_id","). Aurora SG egress IP 화이트리스트 운영 부담 해소 — CLAUDE.md §12 TODO \"SG 갱신 운영 절차\" 항목 달성. 관련 정본 3개(API CLAUDE.md §3·§12·§8, SCALABILITY.md §6 신규 절, MIGRATION.md §1) 동기화. ",[21,39744,30111],{},[21,39746,39747],{},"관리자단 핸드오프 풀세트",[32,39749,39750],{},"handoff_noti_admin"," (3,129줄 jsx)을 Vue 3 + Nuxt UI v3로 1:1 포팅. 셸(LNB 메뉴 트리 완전 재정비 + Topbar 동적 브레드크럼) · 공유 컴포넌트 14종(PageHeader\u002FSectionCard\u002FTabs\u002FSegmented\u002FFilterBar\u002FDateRange\u002FDataTable generic+slot\u002FPagination\u002FStatusBadge 자동 매핑\u002FChannelChip\u002FStatCard\u002FDrawer\u002FModal\u002FField\u002FEmptyState) · 차트 4종(Bar\u002FArea SVG path+gradient\u002FDonut stroke-dasharray\u002FProgress) · ",[21,39753,39754],{},"17 페이지","(대시보드·고객사·고객사 상세·계정·모니터링·발신번호·발신프로필·템플릿검수·결제·채널단가·충전쿠폰·1:1문의·FAQ·공지·통계·운영자·권한그룹·API). app.config.ts ",[32,39757,39758],{},"info: 'indigo'"," 매핑으로 핸드오프 indigo 강조색을 Nuxt UI semantic으로. 18 라우트 라이브 200. ",[21,39761,30122],{}," 사용자 보고 후속 — admin의 폰트 사이즈가 핸드오프와 다르다는 신고에서 ",[21,39764,39765],{},"두 원인 동시 발견",": (a) ",[32,39768,4771],{},[32,39770,39771],{},"html,body{font-size:13px}","가 모든 Tailwind 토큰을 18% 축소시킴(핸드오프는 base font-size 명시 없음 = 16px), (b) 직전 turn의 cwd가 사용자단이라 ",[21,39774,39775],{},"사용자단 dist를 admin 프로젝트로 배포","한 상태였음(chunk 600개 \u002F GNB·메모·계약 컴포넌트 등 사용자단 자산이 admin URL에 노출). 둘 다 정정: 13px 제거 + ",[32,39778,39779],{},"letter-spacing: -0.01em"," 추가, admin 디렉토리에서 clean rebuild + 재배포. Pages alias ",[32,39782,39783],{},"8852d5da.malgn-noti-admin.pages.dev"," (chunk 96개로 정상화). ",[21,39786,30135],{}," WBS 페이지 편집 기능 — DB 미사용 \u002F R2 단일 JSON 객체 정본(",[32,39789,58],{},"). API에 GET 공개 + PATCH 인증 2 라우트 + 142 task 시드. 사용자단은 임베디드 STAGES 제거 → API 비동기 로드 + 인라인 편집 모달(5 필드, 빈값=",[32,39792,30655],{},"=필드 제거). Workers Version ",[32,39795,39796],{},"28f3e6a8...",", Pages alias ",[32,39799,39800],{},"02bb58e6",". 후속(§5.9) — 목표일·완료일 ",[32,39803,39804],{},"YYYY.MM.DD"," 포맷 통일 + ",[32,39807,39808],{},"\u003Cinput type=\"date\">"," 캘린더 위젯 + API Zod regex 강제. Workers Version ",[32,39811,39812],{},"eb02206c...",[32,39814,39815],{},"98bd09e2",[73,39817],{},[11,39819,39821],{"id":39820},"_1-nice-ipv6-진단-옵션-b-시도-ipv4만-등록되어-여전히-1007","§1. NICE IPv6 진단 — 옵션 B 시도 → IPv4만 등록되어 여전히 1007",[76,39823,27637],{"id":27636},[18,39825,39826,39827,39830,39831,39834,39835,39838,39839,39844,39845,39847,39848,6100,39851,4420,39854,4420,39857,4420,39860,4420,39863,4420,39866,4420,39868,39871,39872,39874],{},"6\u002F2 §16의 1007 차단 후속. 사용자가 옵션 B(NICE 콘솔에 Cloudflare egress IP 대역 등록)를 진행했다고 알림 → 재시도 했지만 여전히 ",[32,39828,39829],{},"1007 허용되지 않은 IP 접근",". 진단을 위해 임시 endpoint ",[32,39832,39833],{},"GET \u002Fdiag\u002Fegress","를 추가해 외부 echo(",[32,39836,39837],{},"api.ipify.org",")로 실제 출발지 IP를 8회 캡처 → ",[21,39840,39841,39842],{},"모두 IPv6 ",[32,39843,39716],{}," (Cloudflare 공식 IPv6 ",[32,39846,39720],{}," 소속). NICE에 등록된 대역은 IPv4뿐이라 IPv6 출발지가 거부됨. 즉시 mock 복원 + 진단 endpoint 제거(\u002Fdiag\u002Fegress → 404) + Workers 재배포. 사용자에게 NICE 콘솔에 ",[21,39849,39850],{},"Cloudflare IPv6 대역 7개",[32,39852,39853],{},"2400:cb00::\u002F32",[32,39855,39856],{},"2606:4700::\u002F32",[32,39858,39859],{},"2803:f800::\u002F32",[32,39861,39862],{},"2405:b500::\u002F32",[32,39864,39865],{},"2405:8100::\u002F32",[32,39867,39720],{},[32,39869,39870],{},"2c0f:f248::\u002F32",") 추가 등록 또는 IP 검사 OFF(옵션 A) 안내 후 사용자가 IP 보류 결정. 자격증명 3개(CLIENT_ID\u002FSECRET\u002FRETURN_URL) 보관 유지 — 정책 해결 시 ",[32,39873,32642],{}," 한 번이면 real 전환.",[76,39876,39878],{"id":39877},"_11-자격증명-재확인","1.1 자격증명 재확인",[18,39880,39881,39882,39885,39886,39889,39890,39893],{},"Cloudflare는 secret 값을 ",[32,39883,39884],{},"secret list","로 노출하지 않음. 가장 확실한 방법은 사용자가 준 값으로 ",[21,39887,39888],{},"덮어쓰기"," — 일치하면 그대로, 다르면 정확한 값으로 교정. ",[32,39891,39892],{},"wrangler secret put NICE_CLIENT_ID\u002FSECRET","로 사용자 제공값 100% 일치 보장 처리.",[76,39895,39897],{"id":39896},"_12-1007-재현-진단-endpoint","1.2 1007 재현 → 진단 endpoint",[5251,39899,39901],{"className":7962,"code":39900,"language":7964,"meta":4208,"style":4208},"app.get('\u002Fdiag\u002Fegress', async (c) => {\n  const samples: string[] = []\n  for (let i = 0; i \u003C 8; i++) {\n    const r = await fetch('https:\u002F\u002Fapi.ipify.org?format=json', { cf: { cacheTtl: 0 } })\n    const j = await r.json()\n    samples.push(j.ip)\n  }\n  return c.json({ samples, unique: [...new Set(samples)] })\n})\n",[32,39902,39903,39929,39946,39978,40005,40023,40033,40037,40056],{"__ignoreMap":4208},[7968,39904,39905,39908,39910,39912,39915,39917,39919,39921,39923,39925,39927],{"class":5110,"line":7970},[7968,39906,39907],{"class":7984},"app.",[7968,39909,32783],{"class":7980},[7968,39911,6100],{"class":7984},[7968,39913,39914],{"class":7993},"'\u002Fdiag\u002Fegress'",[7968,39916,4473],{"class":7984},[7968,39918,9453],{"class":7973},[7968,39920,6685],{"class":7984},[7968,39922,21430],{"class":8939},[7968,39924,21438],{"class":7984},[7968,39926,9426],{"class":7973},[7968,39928,8887],{"class":7984},[7968,39930,39931,39933,39936,39938,39940,39942,39944],{"class":5110,"line":4212},[7968,39932,34235],{"class":7973},[7968,39934,39935],{"class":8892}," samples",[7968,39937,9148],{"class":7973},[7968,39939,9151],{"class":8892},[7968,39941,27316],{"class":7984},[7968,39943,8254],{"class":7973},[7968,39945,33215],{"class":7984},[7968,39947,39948,39951,39953,39955,39958,39960,39962,39965,39967,39970,39973,39976],{"class":5110,"line":4209},[7968,39949,39950],{"class":7973},"  for",[7968,39952,6685],{"class":7984},[7968,39954,37727],{"class":7973},[7968,39956,39957],{"class":7984}," i ",[7968,39959,8254],{"class":7973},[7968,39961,33274],{"class":8892},[7968,39963,39964],{"class":7984},"; i ",[7968,39966,8241],{"class":7973},[7968,39968,39969],{"class":8892}," 8",[7968,39971,39972],{"class":7984},"; i",[7968,39974,39975],{"class":7973},"++",[7968,39977,21462],{"class":7984},[7968,39979,39980,39982,39985,39987,39989,39992,39994,39997,40000,40002],{"class":5110,"line":4219},[7968,39981,34312],{"class":7973},[7968,39983,39984],{"class":8892}," r",[7968,39986,9210],{"class":7973},[7968,39988,9280],{"class":7973},[7968,39990,39991],{"class":7980}," fetch",[7968,39993,6100],{"class":7984},[7968,39995,39996],{"class":7993},"'https:\u002F\u002Fapi.ipify.org?format=json'",[7968,39998,39999],{"class":7984},", { cf: { cacheTtl: ",[7968,40001,5354],{"class":8892},[7968,40003,40004],{"class":7984}," } })\n",[7968,40006,40007,40009,40012,40014,40016,40019,40021],{"class":5110,"line":8291},[7968,40008,34312],{"class":7973},[7968,40010,40011],{"class":8892}," j",[7968,40013,9210],{"class":7973},[7968,40015,9280],{"class":7973},[7968,40017,40018],{"class":7984}," r.",[7968,40020,21244],{"class":7980},[7968,40022,9268],{"class":7984},[7968,40024,40025,40028,40030],{"class":5110,"line":8301},[7968,40026,40027],{"class":7984},"    samples.",[7968,40029,12973],{"class":7980},[7968,40031,40032],{"class":7984},"(j.ip)\n",[7968,40034,40035],{"class":5110,"line":8310},[7968,40036,21497],{"class":7984},[7968,40038,40039,40041,40043,40045,40048,40051,40053],{"class":5110,"line":8321},[7968,40040,34253],{"class":7973},[7968,40042,21470],{"class":7984},[7968,40044,21244],{"class":7980},[7968,40046,40047],{"class":7984},"({ samples, unique: [",[7968,40049,40050],{"class":7973},"...new",[7968,40052,34150],{"class":7980},[7968,40054,40055],{"class":7984},"(samples)] })\n",[7968,40057,40058],{"class":5110,"line":8332},[7968,40059,8007],{"class":7984},[18,40061,40062,40063,40065,40066,40068],{},"연속 2회 호출 결과 — 16번 모두 동일 IP: ",[32,40064,39716],{},". Cloudflare 공식 IPv6 대역 ",[32,40067,39720],{}," 소속.",[76,40070,40072],{"id":40071},"_13-결정-ipv6-대역-추가-등록-또는-ip-검사-off","1.3 결정 — IPv6 대역 추가 등록 또는 IP 검사 OFF",[101,40074,40075,40083],{},[104,40076,40077],{},[107,40078,40079,40081],{},[110,40080,39006],{},[110,40082,267],{},[126,40084,40085,40093,40101],{},[107,40086,40087,40090],{},[131,40088,40089],{},"A. NICE 콘솔에서 IP 인증 OFF",[131,40091,40092],{},"콘솔 → 보안설정 토글",[107,40094,40095,40098],{},[131,40096,40097],{},"B'. IPv6 대역 7개 추가 등록 (오늘 사용자가 했던 B는 IPv4만 등록되어 실패)",[131,40099,40100],{},"Cloudflare 공식 IPv6 목록 NICE 영업에 송부",[107,40102,40103,40105],{},[131,40104,39039],{},[131,40106,40107],{},"AWS EC2 nano + 어댑터 baseUrl 변경",[18,40109,40110,40111,40113,40114,40116],{},"사용자 의사로 일단 ",[21,40112,39051],{}," 결정. ",[32,40115,38976],{}," 복원해 가입 흐름은 mock 정상. 진단 endpoint는 보안상 즉시 제거 + 재배포로 라이브에서 404.",[76,40118,40120],{"id":40119},"_14-산출물","1.4 산출물",[81,40122,40123,40126,40135],{},[84,40124,40125],{},"코드: 없음(진단 endpoint는 일시 추가 후 제거)",[84,40127,40128,40129,40132,40133,11673],{},"secret: ",[32,40130,40131],{},"NICE_CLIENT_ID\u002FSECRET"," 덮어쓰기(값 변경 없음, 일치 보장만), ",[32,40134,38976],{},[84,40136,40137,40138,40141,40142],{},"Workers 배포 — 진단 endpoint 추가 시 Version ",[32,40139,40140],{},"e77f89e0-8fed-4b27-a56a-a212d916cba3",", 제거 후 Version ",[32,40143,40144],{},"0e3d3eb0-38ca-487f-8a28-355b0243a5a6",[76,40146,40148],{"id":40147},"_15-보안-메모","1.5 보안 메모",[18,40150,40151,40153,40154,40156],{},[32,40152,39061],{},"이 6\u002F2 채팅 평문 노출 + 오늘 한 번 더 노출. IP 정책 해결 시점에 NICE 콘솔에서 회전 권장. ",[32,40155,30308],{}," §9 후속 작업 21번에 이미 등록됨.",[73,40158],{},[11,40160,40162],{"id":40161},"_2-hyperdrive-교체-cloudflare-tunnelaccess-기반-workers-version-a457b7dc","§2. Hyperdrive 교체 — Cloudflare Tunnel(Access) 기반 (Workers Version a457b7dc)",[76,40164,27637],{"id":28262},[18,40166,40167,40169,40170,6219,40172,40175,40176,40178,40179,40182,40183,40185,40186,40188],{},[32,40168,11253],{},"의 HYPERDRIVE id를 ",[32,40171,20108],{},[32,40173,40174],{},"439b109dd219479e8b3e8d80eea9a240","으로 교체. 신규 Hyperdrive origin host가 ",[32,40177,39738],{}," (Cloudflare Tunnel 엔드포인트) + ",[32,40180,40181],{},"access_client_id: 50b64dc493c35f1a9d9916baf4e2d735.access"," (Cloudflare Access 서비스 토큰)이라, 기존 \"퍼블릭 엔드포인트 + SG inbound에 Hyperdrive egress IP 화이트리스트\" 구성이 \"Cloudflare Tunnel 기반\"으로 전환됨. 코드 변경 0 — Hyperdrive 바인딩 인터페이스 동일, mysql2 드라이버 그대로 동작. 라이브 검증 통과(",[32,40184,20624],{}," mysql_version 8.0.42 + ",[32,40187,32203],{}," DB 쓰기 정상). 관련 정본 3개 문서 동기화.",[76,40190,40192],{"id":40191},"_21-전환-효과","2.1 전환 효과",[101,40194,40195,40207],{},[104,40196,40197],{},[107,40198,40199,40201,40204],{},[110,40200,6889],{},[110,40202,40203],{},"이전 (~6\u002F1)",[110,40205,40206],{},"신규 (6\u002F4~)",[126,40208,40209,40219,40230,40240],{},[107,40210,40211,40213,40216],{},[131,40212,10770],{},[131,40214,40215],{},"퍼블릭 엔드포인트 + SG 화이트리스트",[131,40217,40218],{},"Cloudflare Tunnel 뒤",[107,40220,40221,40224,40227],{},[131,40222,40223],{},"출발지 인증",[131,40225,40226],{},"Hyperdrive egress IP가 SG에 등록되어야 통과",[131,40228,40229],{},"Tunnel access_client_id로 인증 (출발지 IP 무관)",[107,40231,40232,40235,40238],{},[131,40233,40234],{},"egress IP 갱신 추적",[131,40236,40237],{},"Cloudflare 공식 IP 목록 변경 시 SG 동기화 필요",[131,40239,39184],{},[107,40241,40242,40248,40251],{},[131,40243,20657,40244,40247],{},[32,40245,40246],{},"drizzle-kit migrate"," 직결",[131,40249,40250],{},"SG 제약으로 불가 (admin 라우트로 우회)",[131,40252,40253],{},"Tunnel 인증 없으면 통과 불가 (동일 운영 절차 유지)",[18,40255,40256,40257,40260],{},"CLAUDE.md §12 TODO 중 \"",[21,40258,40259],{},"SG 갱신 운영 절차",": Hyperdrive egress IP 목록이 바뀔 때 어떻게 감지\u002F반영할지\" 항목 자연 달성 — 더 이상 필요 없음.",[76,40262,40264],{"id":40263},"_22-라이브-검증","2.2 라이브 검증",[81,40266,40267,40274,40279],{},[84,40268,40269,6219,40271,23936],{},[32,40270,20624],{},[32,40272,40273],{},"{ok: true, mysql_version: \"8.0.42\"}",[84,40275,40276,40278],{},[32,40277,32203],{}," (DB 쓰기) → mockMode true + niceAuth row insert ✅",[84,40280,40281,40283],{},[32,40282,36268],{}," (인증 라우트) → 401 ✅",[76,40285,40287],{"id":40286},"_23-정본-문서-동기화","2.3 정본 문서 동기화",[101,40289,40290,40298],{},[104,40291,40292],{},[107,40293,40294,40296],{},[110,40295,28918],{},[110,40297,7525],{},[126,40299,40300,40310,40320,40332,40345],{},[107,40301,40302,40307],{},[131,40303,40304,40306],{},[32,40305,10821],{}," §3",[131,40308,40309],{},"Aurora 노출 방식을 Tunnel로 명시. 이전 SG 화이트리스트 절차 삭제. egress IP 동기화 부담 제거",[107,40311,40312,40317],{},[131,40313,40314,40316],{},[32,40315,10821],{}," §12 TODO",[131,40318,40319],{},"\"SG 갱신 운영 절차\" 항목 ✅ 완료 표시",[107,40321,40322,40327],{},[131,40323,40324,40326],{},[32,40325,10821],{}," §8",[131,40328,40329,40331],{},[32,40330,21619],{}," 안내 문구 \"Aurora SG 제약\" → \"Tunnel 뒤\"로",[107,40333,40334,40339],{},[131,40335,40336,40338],{},[32,40337,20362],{}," §6",[131,40340,40341,40344],{},[21,40342,40343],{},"신규 절"," \"Hyperdrive ↔ Aurora 연결 방식 — Cloudflare Tunnel(Access) 기반 (2026-06-02 이후)\" 추가. 전\u002F후 구성 표 + 전환 이유 + 운영 영향 + 라이브 검증 + 신규 Hyperdrive id 명시",[107,40346,40347,40352],{},[131,40348,40349,40351],{},[32,40350,21606],{}," §1",[131,40353,40354],{},"통로 다이어그램에 Tunnel 단계 추가, 신규 Hyperdrive id 반영",[76,40356,30781],{"id":30780},[81,40358,40359],{},[84,40360,32568,40361,40363,40364],{},[32,40362,50],{}," — 2 커밋\n",[81,40365,40366,40375],{},[84,40367,40368,40371,40372],{},[32,40369,40370],{},"3d779ad"," chore(wrangler): Hyperdrive id 교체. Workers 배포 Version ",[32,40373,40374],{},"a457b7dc-e951-4f2a-bc78-29b5496fa90f",[84,40376,40377,40380],{},[32,40378,40379],{},"334ee69"," doc(infra): Aurora 연결 방식 Cloudflare Tunnel 전환 반영 (코드 변경 없음 — 정본만)",[76,40382,40384],{"id":40383},"_25-알려진-한계-후속","2.5 알려진 한계 \u002F 후속",[81,40386,40387,40393,40403],{},[84,40388,40389,40392],{},[21,40390,40391],{},"Aurora SG inbound 단순화"," — Tunnel 전환으로 더 이상 Cloudflare egress IP 추적 불필요하므로 SG inbound는 Tunnel daemon 호스트만 허용하도록 단순화 가능. AWS 측 정리 후속.",[84,40394,40395,40402],{},[21,40396,40397,40398,40401],{},"Aurora ",[32,40399,40400],{},"PubliclyAccessible=false"," 전환 검토"," — Tunnel만 노출이 되면 퍼블릭 IP 제거 가능. 운영 정책 합의 후 적용.",[84,40404,40405,40408],{},[21,40406,40407],{},"Tunnel 가용성 모니터링"," — Tunnel daemon이 죽으면 전 Workers DB 호출이 실패. 인지·복구 절차(Cloudflare 대시보드 알람 + 백업 경로) 정의 후속.",[73,40410],{},[11,40412,40414],{"id":40413},"_3-관리자단-핸드오프-17-페이지-풀세트-셸-공유-컴포넌트-차트-구현-pages-alias-8852d5da","§3. 관리자단 핸드오프 — 17 페이지 풀세트 + 셸 + 공유 컴포넌트 + 차트 구현 (Pages alias 8852d5da)",[76,40416,27637],{"id":28712},[18,40418,40419,40420,40422,40423,4361,40426,40429,40430,40433],{},"신규 핸드오프 정본 ",[32,40421,39750],{},"(prototype 3,129줄 jsx + 스타일 가이드 + 8 스크린샷)을 받아 사용자 요청으로 17 페이지 풀세트 신규 작성. 기존 admin은 셸(",[32,40424,40425],{},"AppLnb",[32,40427,40428],{},"AppTopbar",")만 있는 부트스트랩 상태였고 LNB 메뉴 트리도 핸드오프와 완전히 달라서 (a) 셸 완전 재정비 + (b) 공유 컴포넌트 14종 + (c) 차트 4종 + (d) 17 페이지를 모두 신규로 작성. Vue 3 + Nuxt UI v3로 jsx → Vue 1:1 포팅 패턴. 라이브 18 라우트(17 + 동적 ",[32,40431,40432],{},"\u002Fcustomers\u002F:id",") 모두 200 검증.",[76,40435,40437],{"id":40436},"_31-핸드오프-정본-ia","3.1 핸드오프 정본 IA",[18,40439,40440,40441,9148],{},"10 섹션 \u002F 17 라우트 — ",[32,40442,40443],{},"handoff_noti_admin\u002Fprototype\u002Flnb.jsx",[101,40445,40446,40460],{},[104,40447,40448],{},[107,40449,40450,40452,40455,40457],{},[110,40451,3846],{},[110,40453,40454],{},"라우트 키",[110,40456,35660],{},[110,40458,40459],{},"화면",[126,40461,40462,40479,40497,40515,40533,40551,40569,40587,40603,40619,40637,40655,40672,40689,40707,40725,40743,40761],{},[107,40463,40464,40467,40472,40476],{},[131,40465,40466],{},"01",[131,40468,40469],{},[32,40470,40471],{},"dashboard",[131,40473,40474],{},[32,40475,4361],{},[131,40477,40478],{},"KPI4 + 발송추이(area) + 채널비중(donut) + 검수큐 + 실시간 발송",[107,40480,40481,40484,40489,40494],{},[131,40482,40483],{},"02",[131,40485,40486],{},[32,40487,40488],{},"customer",[131,40490,40491],{},[32,40492,40493],{},"\u002Fcustomers",[131,40495,40496],{},"필터바 + 선택 + 페이지네이션",[107,40498,40499,40502,40507,40512],{},[131,40500,40501],{},"02d",[131,40503,40504],{},[32,40505,40506],{},"customer\u002F:id",[131,40508,40509],{},[32,40510,40511],{},"\u002Fcustomers\u002F[id]",[131,40513,40514],{},"InfoCard(KPI5) + 탭6 + 계정 테이블 + 메모 타임라인 + 차트 + 권한변경 모달",[107,40516,40517,40520,40525,40530],{},[131,40518,40519],{},"03",[131,40521,40522],{},[32,40523,40524],{},"account",[131,40526,40527],{},[32,40528,40529],{},"\u002Faccounts",[131,40531,40532],{},"계정 목록",[107,40534,40535,40538,40543,40548],{},[131,40536,40537],{},"04",[131,40539,40540],{},[32,40541,40542],{},"monitoring",[131,40544,40545],{},[32,40546,40547],{},"\u002Fmonitoring",[131,40549,40550],{},"LIVE KPI4 + 처리량 라인 + 작업 큐(진행바·상태배지)",[107,40552,40553,40556,40561,40566],{},[131,40554,40555],{},"05",[131,40557,40558],{},[32,40559,40560],{},"sender-num",[131,40562,40563],{},[32,40564,40565],{},"\u002Fsenders\u002Fnumbers",[131,40567,40568],{},"검수 목록 → 검수 Drawer(증빙·메모·승인\u002F반려)",[107,40570,40571,40574,40579,40584],{},[131,40572,40573],{},"06",[131,40575,40576],{},[32,40577,40578],{},"sender-profile",[131,40580,40581],{},[32,40582,40583],{},"\u002Fsenders\u002Fprofiles",[131,40585,40586],{},"동일 패턴",[107,40588,40589,40592,40596,40600],{},[131,40590,40591],{},"07",[131,40593,40594],{},[32,40595,8296],{},[131,40597,40598],{},[32,40599,22059],{},[131,40601,40602],{},"KPI4 + 미리보기 Drawer + 자동검수 결과",[107,40604,40605,40608,40612,40616],{},[131,40606,40607],{},"08",[131,40609,40610],{},[32,40611,18562],{},[131,40613,40614],{},[32,40615,26680],{},[131,40617,40618],{},"KPI4 + 충전\u002F차감\u002F환불 내역",[107,40620,40621,40624,40629,40634],{},[131,40622,40623],{},"09",[131,40625,40626],{},[32,40627,40628],{},"price-channel",[131,40630,40631],{},[32,40632,40633],{},"\u002Fpricing\u002Fchannels",[131,40635,40636],{},"채널별 단가표",[107,40638,40639,40642,40647,40652],{},[131,40640,40641],{},"10",[131,40643,40644],{},[32,40645,40646],{},"price-charge",[131,40648,40649],{},[32,40650,40651],{},"\u002Fpricing\u002Fcoupons",[131,40653,40654],{},"Tier 보너스 + 쿠폰\u002F프로모션",[107,40656,40657,40660,40664,40669],{},[131,40658,40659],{},"11",[131,40661,40662],{},[32,40663,22524],{},[131,40665,40666],{},[32,40667,40668],{},"\u002Fsupport\u002Finquiries",[131,40670,40671],{},"답변 Drawer",[107,40673,40674,40676,40681,40686],{},[131,40675,25857],{},[131,40677,40678],{},[32,40679,40680],{},"faq",[131,40682,40683],{},[32,40684,40685],{},"\u002Fsupport\u002Ffaq",[131,40687,40688],{},"카테고리 사이드 + FAQ 목록",[107,40690,40691,40694,40699,40704],{},[131,40692,40693],{},"13",[131,40695,40696],{},[32,40697,40698],{},"notice",[131,40700,40701],{},[32,40702,40703],{},"\u002Fsupport\u002Fnotices",[131,40705,40706],{},"공지 목록(고정·분류·공개)",[107,40708,40709,40712,40717,40722],{},[131,40710,40711],{},"14",[131,40713,40714],{},[32,40715,40716],{},"report",[131,40718,40719],{},[32,40720,40721],{},"\u002Freports",[131,40723,40724],{},"KPI4 + 월별 추이 + 실패사유 donut + 채널 bar + Top 고객사",[107,40726,40727,40730,40735,40740],{},[131,40728,40729],{},"15",[131,40731,40732],{},[32,40733,40734],{},"sys-operator",[131,40736,40737],{},[32,40738,40739],{},"\u002Fsystem\u002Foperators",[131,40741,40742],{},"목록 + 운영자 추가 모달",[107,40744,40745,40748,40753,40758],{},[131,40746,40747],{},"16",[131,40749,40750],{},[32,40751,40752],{},"sys-role",[131,40754,40755],{},[32,40756,40757],{},"\u002Fsystem\u002Froles",[131,40759,40760],{},"권한 그룹 카드 + 권한 매트릭스",[107,40762,40763,40766,40771,40776],{},[131,40764,40765],{},"17",[131,40767,40768],{},[32,40769,40770],{},"sys-api",[131,40772,40773],{},[32,40774,40775],{},"\u002Fsystem\u002Fapi",[131,40777,40778],{},"KPI4 + API 키 + 웹훅",[76,40780,40782],{"id":40781},"_32-셸-재정비","3.2 셸 재정비",[81,40784,40785,40801,40815,40827],{},[84,40786,40787,40792,40793,40796,40797,40800],{},[21,40788,40789,40791],{},[32,40790,40425],{}," 완전 재작성"," — 기존 메뉴 트리(8 그룹\u002F예약발송\u002FFCM\u002FAPNs 등)를 폐기하고 핸드오프 정본 9 그룹 \u002F 17 라우트로 교체. ",[32,40794,40795],{},"NuxtLink"," 기반 라우트 경로 매핑 + ",[32,40798,40799],{},"isActive()"," (path prefix 매칭) + 메뉴 검색 입력 + AI 발송 도우미 배너 + 사용자 칩.",[84,40802,40803,89,40807,40810,40811,40814],{},[21,40804,40805],{},[32,40806,40428],{},[32,40808,40809],{},"useState('breadcrumb')"," 기반 동적 브레드크럼으로 변경. 페이지에서 ",[32,40812,40813],{},"useBreadcrumb(['회원\u002F고객사', '고객사'])"," 호출로 즉시 갱신.",[84,40816,40817,40823,40824,4703],{},[21,40818,40819,40822],{},[32,40820,40821],{},"useBreadcrumb"," composable"," 신규 — ",[32,40825,40826],{},"composables\u002FuseBreadcrumb.ts",[84,40828,40829,89,40833,40835,40836,40839,40840,40842],{},[21,40830,40831],{},[32,40832,7959],{},[32,40834,39758],{}," 매핑 추가. 핸드오프의 indigo 강조색(",[32,40837,40838],{},"검수자"," 권한, ",[32,40841,7131],{}," 채널 등)을 Nuxt UI v3 semantic color로 사용 가능하게.",[76,40844,40846],{"id":40845},"_33-공유-컴포넌트-14종","3.3 공유 컴포넌트 14종",[101,40848,40849,40859],{},[104,40850,40851],{},[107,40852,40853,40856],{},[110,40854,40855],{},"컴포넌트",[110,40857,40858],{},"핵심",[126,40860,40861,40871,40881,40900,40909,40922,40932,40949,40959,40972,40982,40992,41002,41011,41021],{},[107,40862,40863,40868],{},[131,40864,40865],{},[32,40866,40867],{},"AppPageHeader",[131,40869,40870],{},"caption · title · badges slot · actions slot",[107,40872,40873,40878],{},[131,40874,40875],{},[32,40876,40877],{},"AppSectionCard",[131,40879,40880],{},"title\u002Fsubtitle\u002Factions\u002FnoBody\u002FbodyClass",[107,40882,40883,40888],{},[131,40884,40885],{},[32,40886,40887],{},"AppTabs",[131,40889,40890,4339,40893,40896,40897,4343],{},[32,40891,40892],{},"tabs[]",[32,40894,40895],{},"v-model"," (string|",[32,40898,40899],{},"{value,label}",[107,40901,40902,40906],{},[131,40903,40904],{},[32,40905,4515],{},[131,40907,40908],{},"pill 단일선택 (같은 패턴)",[107,40910,40911,40915],{},[131,40912,40913],{},[32,40914,9701],{},[131,40916,40917,40918,40921],{},"2열 그리드 + 조회\u002F초기화. 필드는 ",[32,40919,40920],{},"#field-N"," slot으로",[107,40923,40924,40929],{},[131,40925,40926],{},[32,40927,40928],{},"AppDateRange",[131,40930,40931],{},"from\u002Fto 표시 (placeholder)",[107,40933,40934,40939],{},[131,40935,40936],{},[32,40937,40938],{},"AppDataTable\u003CT>",[131,40940,40941,40948],{},[21,40942,40943,40944,40947],{},"generic + ",[32,40945,40946],{},"#cell-{key}"," scoped slot"," 패턴. selectable + 선택 set + row-click + min-width + 빈 상태",[107,40950,40951,40956],{},[131,40952,40953],{},[32,40954,40955],{},"AppPagination",[131,40957,40958],{},"page navigation + page size select",[107,40960,40961,40966],{},[131,40962,40963],{},[32,40964,40965],{},"AppStatusBadge",[131,40967,40968,40971],{},[21,40969,40970],{},"값→톤 자동 매핑"," (활성\u002F완료=success · 대기\u002F검수중=warning · 중지\u002F반려=error · 임시저장=neutral). 핸드오프 README §7 매핑 그대로",[107,40973,40974,40979],{},[131,40975,40976],{},[32,40977,40978],{},"AppChannelChip",[131,40980,40981],{},"PUSH=indigo · RCS=primary · SMS=emerald · 알림톡=amber · 이메일=sky",[107,40983,40984,40989],{},[131,40985,40986],{},[32,40987,40988],{},"AppStatCard",[131,40990,40991],{},"icon + label + value + sub + delta(deltaUp 분기) + accent 7종",[107,40993,40994,40999],{},[131,40995,40996],{},[32,40997,40998],{},"AppDrawer",[131,41000,41001],{},"Teleport + body scroll lock + footer slot",[107,41003,41004,41008],{},[131,41005,41006],{},[32,41007,4476],{},[131,41009,41010],{},"중앙 + Teleport + scroll lock",[107,41012,41013,41018],{},[131,41014,41015],{},[32,41016,41017],{},"AppField",[131,41019,41020],{},"label + required + hint",[107,41022,41023,41027],{},[131,41024,41025],{},[32,41026,12868],{},[131,41028,41029],{},"icon + title + desc + action slot",[18,41031,41032,41035,41036,41039,41040,41043],{},[32,41033,41034],{},"AppDataTable","의 generic + scoped slot 패턴이 핸드오프 prototype의 ",[32,41037,41038],{},"columns: [{render: r => JSX}]"," 패턴을 Vue로 가장 자연스럽게 옮긴 결과. 페이지에서 ",[32,41041,41042],{},"\u003Ctemplate #cell-status=\"{ row }\">\u003CAppStatusBadge :value=\"row.status\" \u002F>\u003C\u002Ftemplate>"," 식으로 컬럼별 렌더 정의.",[76,41045,41047],{"id":41046},"_34-차트-컴포넌트-4종","3.4 차트 컴포넌트 4종",[18,41049,13166,41050,41053],{},[32,41051,41052],{},"charts.jsx","(111줄, SVG 기반)와 동등:",[81,41055,41056,41062,41068,41077],{},[84,41057,41058,41061],{},[32,41059,41060],{},"AppBarChart"," — 그룹\u002F단일 막대 (div + height) + highlight + 점선 가이드",[84,41063,41064,41067],{},[32,41065,41066],{},"AppAreaChart"," — SVG path(라인 + 영역 + 그라데이션) + 도트 + 라벨 축",[84,41069,41070,89,41073,41076],{},[32,41071,41072],{},"AppDonut",[32,41074,41075],{},"stroke-dasharray","로 segment + center 라벨\u002Fsub + 우측 범례",[84,41078,41079,41082],{},[32,41080,41081],{},"AppProgressBar"," — 가로 진행바 + show-pct 옵션",[18,41084,41085],{},"외부 차트 라이브러리 없이 동작(prototype 그대로). 후속에서 ApexCharts\u002FChart.js로 교체 가능.",[76,41087,41089],{"id":41088},"_35-17-페이지","3.5 17 페이지",[18,41091,41092,41093,4339,41096,41099,41100,4361,41102,4361,41104,4361,41106,4361,41108,41110],{},"각 페이지에서 ",[32,41094,41095],{},"useHead({title})",[32,41097,41098],{},"useBreadcrumb([...])"," 호출 + 더미 데이터 ref + ",[32,41101,40867],{},[32,41103,9701],{},[32,41105,40877],{},[32,41107,41034],{},[32,41109,40955],{}," 조합 + Drawer\u002FModal(필요 시).",[18,41112,41113,41114,41117],{},"가장 큰 페이지: ",[32,41115,41116],{},"customers\u002F[id]"," (~370 라인) — InfoCard(좌측 identity + KPI5) + 탭 + 필터 + 계정 테이블(선택·역할 컬러·채널 칩) + 차트 + 최근 활동 + 메모 패널(우측 sticky aside) + 권한변경 모달(3열 라디오 그리드).",[76,41119,41121],{"id":41120},"_36-라이브-검증","3.6 라이브 검증",[5251,41123,41126],{"className":41124,"code":41125,"language":5256},[5254],"\u002F                       200    \u002Fsupport\u002Finquiries     200\n\u002Fcustomers              200    \u002Fsupport\u002Ffaq           200\n\u002Fcustomers\u002FC2241        200    \u002Fsupport\u002Fnotices       200\n\u002Faccounts               200    \u002Freports               200\n\u002Fmonitoring             200    \u002Fsystem\u002Foperators      200\n\u002Fsenders\u002Fnumbers        200    \u002Fsystem\u002Froles          200\n\u002Fsenders\u002Fprofiles       200    \u002Fsystem\u002Fapi            200\n\u002Ftemplates              200\n\u002Fbilling                200\n\u002Fpricing\u002Fchannels       200\n\u002Fpricing\u002Fcoupons        200\n",[32,41127,41125],{"__ignoreMap":4208},[18,41129,41130],{},"18 라우트 모두 정상.",[76,41132,41134],{"id":41133},"_37-산출물","3.7 산출물",[81,41136,41137],{},[84,41138,41139,41142,41143,41146],{},[32,41140,41141],{},"malgn-noti-admin: 0227cae"," — 21 컴포넌트 + 1 composable + 17 페이지(폴더 8개) + app.config.ts + AppLnb\u002FAppTopbar 갱신. Pages 초기 배포 alias ",[32,41144,41145],{},"82178863.malgn-noti-admin.pages.dev"," (이 배포는 §4의 사용자단 잘못 배포 시점에 덮어써짐 — §4 참조)",[76,41148,41150],{"id":41149},"_38-알려진-한계-후속","3.8 알려진 한계 \u002F 후속",[81,41152,41153,41171,41177,41183,41189],{},[84,41154,41155,41158,41159,11686,41161,41163,41164,4536,41167,41170],{},[21,41156,41157],{},"실 API 연동"," — 현재 모든 더미 데이터. ",[32,41160,50],{},[32,41162,21053],{}," 라우트 (대부분 미구현)를 신설 후 교체. ",[32,41165,41166],{},"\u002Fadmin\u002Fcompanies",[32,41168,41169],{},"\u002Fadmin\u002Fcompanies\u002F:id\u002F{approve,reject}","가 P0(승인 게이트의 짝).",[84,41172,41173,41176],{},[21,41174,41175],{},"운영자 인증·RBAC 미들웨어"," — admin CLAUDE.md §4 보안 원칙(2FA, Cloudflare Access, RBAC). 현재는 공개 라이브.",[84,41178,41179,41182],{},[21,41180,41181],{},"반응형 보강"," — 핸드오프는 1600px 데스크톱 기준. 1280px 미만 메모 패널 숨김, 1024px 미만 LNB drawer화 필요.",[84,41184,41185,41188],{},[21,41186,41187],{},"차트 라이브러리 도입"," — 현재 SVG 자체 구현(prototype 그대로). 데이터 양이 늘면 ApexCharts\u002FChart.js로 교체 검토.",[84,41190,41191,41194],{},[21,41192,41193],{},"고객사 상세 메모 composer 동작"," — 현재는 placeholder 입력만 있고 등록 동작 없음. 운영자단 메모 API와 함께 후속.",[73,41196],{},[11,41198,41200],{"id":41199},"_4-폰트-사이즈-정합화-base-16px-복원-사용자단을-admin에-잘못-배포한-사고-정정-pages-alias-8852d5da","§4. 폰트 사이즈 정합화 — base 16px 복원 + 사용자단을 admin에 잘못 배포한 사고 정정 (Pages alias 8852d5da)",[76,41202,27637],{"id":29444},[18,41204,41205,41206,41209,41210,89,41213,41216,41217,41220,41221,41224,41225,41228,41229,4339,41231,41234,41235,41238,41239,4361,41241,4361,41243,41245,41246,41248,41249,41251],{},"사용자 보고 — \"관리자단의 폰트 사이즈가 핸드오프 디자인과 다르다.\" 진단 결과 ",[21,41207,41208],{},"두 원인이 겹쳐"," 있었음. ",[21,41211,41212],{},"(a) main.css의 base font-size 13px",[32,41214,41215],{},"html, body { font-size: 13px }","로 명시되어 있어 Tailwind 토큰(",[32,41218,41219],{},"text-xs=0.75rem"," 등은 16px base 가정)이 모두 약 18% 작게 표시됨. 핸드오프 prototype\u002Findex.html과 스타일 가이드는 body에 font-size 명시 없음(= 16px Tailwind 기본). ",[21,41222,41223],{},"(b) 사용자단을 admin에 잘못 배포"," — 직전 turn의 cwd가 ",[32,41226,41227],{},"\u002FUsers\u002Fdotype\u002FProjects\u002Fmalgn-noti","(사용자단)였고, 거기서 ",[32,41230,11195],{},[32,41232,41233],{},"wrangler pages deploy dist --project-name=malgn-noti-admin","을 실행한 결과 ",[21,41236,41237],{},"사용자단의 dist를 admin 프로젝트로 배포","한 상태였음. admin URL을 열어도 사용자단의 LNB·계약·메모 등 컴포넌트와 CSS 토큰(",[32,41240,4962],{},[32,41242,4842],{},[32,41244,13415],{},")이 떴고, 그래서 폰트 토큰도 사용자단의 것이 적용되어 핸드오프와 일치하지 않게 보임. 둘 다 정정 — (a) main.css 13px 제거 + ",[32,41247,39779],{}," 추가, (b) admin 디렉토리에서 clean rebuild + 재배포(",[32,41250,39783],{},"). chunk 600개(사용자단) → 96개(admin 정상)로 정상화.",[76,41253,41255],{"id":41254},"_41-a-maincss-13px-16px-정합화","4.1 (a) main.css 13px → 16px 정합화",[18,41257,41258,41259,8864],{},"핸드오프 정본 인용 (",[32,41260,41261],{},"prototype\u002Findex.html",[5251,41263,41267],{"className":41264,"code":41265,"language":41266,"meta":4208,"style":4208},"language-css shiki shiki-themes github-light github-dark","html, body { font-family: \"DM Sans\",\"Pretendard Variable\",Pretendard,system-ui,sans-serif; letter-spacing: -0.01em; }\n","css",[32,41268,41269],{"__ignoreMap":4208},[7968,41270,41271,41273,41275,41277,41279,41282,41284,41287,41289,41292,41295,41298,41300,41303,41305,41308,41310,41313,41315],{"class":5110,"line":7970},[7968,41272,13555],{"class":8244},[7968,41274,4473],{"class":7984},[7968,41276,14975],{"class":8244},[7968,41278,27200],{"class":7984},[7968,41280,41281],{"class":8892},"font-family",[7968,41283,47],{"class":7984},[7968,41285,41286],{"class":7993},"\"DM Sans\"",[7968,41288,7001],{"class":7984},[7968,41290,41291],{"class":7993},"\"Pretendard Variable\"",[7968,41293,41294],{"class":7984},",Pretendard,",[7968,41296,41297],{"class":8892},"system-ui",[7968,41299,7001],{"class":7984},[7968,41301,41302],{"class":8892},"sans-serif",[7968,41304,9154],{"class":7984},[7968,41306,41307],{"class":8892},"letter-spacing",[7968,41309,47],{"class":7984},[7968,41311,41312],{"class":8892},"-0.01",[7968,41314,6818],{"class":7973},[7968,41316,41317],{"class":7984},"; }\n",[18,41319,41320],{},"font-size 명시 없음 → 브라우저\u002FTailwind 기본 16px base.",[18,41322,41323],{},"우리 main.css는:",[5251,41325,41327],{"className":41264,"code":41326,"language":41266,"meta":4208,"style":4208},"html, body {\n  font-size: 13px;\n  line-height: 1.55;\n  ...\n}\n",[32,41328,41329,41339,41352,41364,41368],{"__ignoreMap":4208},[7968,41330,41331,41333,41335,41337],{"class":5110,"line":7970},[7968,41332,13555],{"class":8244},[7968,41334,4473],{"class":7984},[7968,41336,14975],{"class":8244},[7968,41338,8887],{"class":7984},[7968,41340,41341,41344,41346,41348,41350],{"class":5110,"line":4212},[7968,41342,41343],{"class":8892},"  font-size",[7968,41345,47],{"class":7984},[7968,41347,40693],{"class":8892},[7968,41349,8954],{"class":7973},[7968,41351,8901],{"class":7984},[7968,41353,41354,41357,41359,41362],{"class":5110,"line":4209},[7968,41355,41356],{"class":8892},"  line-height",[7968,41358,47],{"class":7984},[7968,41360,41361],{"class":8892},"1.55",[7968,41363,8901],{"class":7984},[7968,41365,41366],{"class":5110,"line":4219},[7968,41367,32921],{"class":7984},[7968,41369,41370],{"class":5110,"line":8291},[7968,41371,8987],{"class":7984},[18,41373,41374],{},"13px base에서 Tailwind 토큰은:",[81,41376,41377,41383,41389,41395],{},[84,41378,41379,41382],{},[32,41380,41381],{},"text-xs"," (0.75rem) → 9.75px (핸드오프 12px ❌)",[84,41384,41385,41388],{},[32,41386,41387],{},"text-sm"," (0.875rem) → 11.375px (14px ❌)",[84,41390,41391,41394],{},[32,41392,41393],{},"text-base"," (1rem) → 13px (16px ❌)",[84,41396,41397,41400],{},[32,41398,41399],{},"text-2xl"," (1.5rem) → 19.5px (24px ❌)",[18,41402,41403,41404,41407],{},"→ 모든 사이즈가 약 ",[21,41405,41406],{},"18% 축소",". 페이지 제목·KPI 대표값·필터 라벨 등 전 화면에 영향.",[18,41409,41410],{},"수정:",[5251,41412,41414],{"className":41264,"code":41413,"language":41266,"meta":4208,"style":4208},"html, body {\n  font-family: var(--font-sans);\n  letter-spacing: -0.01em;  \u002F* 핸드오프 정본 — 전역 자간 -1% *\u002F\n  ...\n}\n",[32,41415,41416,41426,41441,41458,41462],{"__ignoreMap":4208},[7968,41417,41418,41420,41422,41424],{"class":5110,"line":7970},[7968,41419,13555],{"class":8244},[7968,41421,4473],{"class":7984},[7968,41423,14975],{"class":8244},[7968,41425,8887],{"class":7984},[7968,41427,41428,41431,41433,41435,41437,41439],{"class":5110,"line":4212},[7968,41429,41430],{"class":8892},"  font-family",[7968,41432,47],{"class":7984},[7968,41434,8934],{"class":8892},[7968,41436,6100],{"class":7984},[7968,41438,7822],{"class":8939},[7968,41440,8942],{"class":7984},[7968,41442,41443,41446,41448,41450,41452,41455],{"class":5110,"line":4209},[7968,41444,41445],{"class":8892},"  letter-spacing",[7968,41447,47],{"class":7984},[7968,41449,41312],{"class":8892},[7968,41451,6818],{"class":7973},[7968,41453,41454],{"class":7984},";  ",[7968,41456,41457],{"class":8398},"\u002F* 핸드오프 정본 — 전역 자간 -1% *\u002F\n",[7968,41459,41460],{"class":5110,"line":4219},[7968,41461,32921],{"class":7984},[7968,41463,41464],{"class":5110,"line":8291},[7968,41465,8987],{"class":7984},[18,41467,41468,4339,41471,41474,41475,41478,41479,41481,41482,41485,41486,41488,41489,41491,41492,41495,41496,41498],{},[32,41469,41470],{},"font-size: 13px",[32,41472,41473],{},"line-height: 1.55"," 제거. Tailwind 기본 16px base 동작. 핸드오프 README §8 Typography 토큰(",[32,41476,41477],{},"text-[11px]","=11 · ",[32,41480,41381],{},"=12 · ",[32,41483,41484],{},"text-[13px]","=13 · ",[32,41487,41387],{},"=14 · ",[32,41490,41393],{},"=16 · ",[32,41493,41494],{},"text-lg","=18 · ",[32,41497,41399],{},"=24)이 정본 그대로 매칭.",[76,41500,41502],{"id":41501},"_42-b-사용자단을-admin에-잘못-배포한-사고","4.2 (b) 사용자단을 admin에 잘못 배포한 사고",[18,41504,41505,41506,41509],{},"증상 — 빌드된 admin ",[32,41507,41508],{},"entry.css","에 사용자단 토큰이 보임:",[5251,41511,41513],{"className":41264,"code":41512,"language":41266,"meta":4208,"style":4208},"body, html { background: var(--paper); color: var(--ink-700); font-family: var(--font-sans); font-size: var(--font-base); ...; font-feature-settings: \"cv11\",\"ss01\",\"ss03\"; letter-spacing: 0; line-height: 1.55; ... }\n",[32,41514,41515],{"__ignoreMap":4208},[7968,41516,41517,41519,41521,41523,41525,41528,41530,41532,41534,41536,41539,41542,41544,41546,41548,41550,41552,41554,41556,41558,41560,41562,41564,41567,41569,41571,41573,41575,41578,41581,41583,41586,41588,41591,41593,41596,41598,41600,41602,41604,41606,41609,41611,41613],{"class":5110,"line":7970},[7968,41518,14975],{"class":8244},[7968,41520,4473],{"class":7984},[7968,41522,13555],{"class":8244},[7968,41524,27200],{"class":7984},[7968,41526,41527],{"class":8892},"background",[7968,41529,47],{"class":7984},[7968,41531,8934],{"class":8892},[7968,41533,6100],{"class":7984},[7968,41535,4962],{"class":8939},[7968,41537,41538],{"class":7984},"); ",[7968,41540,41541],{"class":8892},"color",[7968,41543,47],{"class":7984},[7968,41545,8934],{"class":8892},[7968,41547,6100],{"class":7984},[7968,41549,4842],{"class":8939},[7968,41551,41538],{"class":7984},[7968,41553,41281],{"class":8892},[7968,41555,47],{"class":7984},[7968,41557,8934],{"class":8892},[7968,41559,6100],{"class":7984},[7968,41561,7822],{"class":8939},[7968,41563,41538],{"class":7984},[7968,41565,41566],{"class":8892},"font-size",[7968,41568,47],{"class":7984},[7968,41570,8934],{"class":8892},[7968,41572,6100],{"class":7984},[7968,41574,13415],{"class":8939},[7968,41576,41577],{"class":7984},"); ...; ",[7968,41579,41580],{"class":8892},"font-feature-settings",[7968,41582,47],{"class":7984},[7968,41584,41585],{"class":7993},"\"cv11\"",[7968,41587,7001],{"class":7984},[7968,41589,41590],{"class":7993},"\"ss01\"",[7968,41592,7001],{"class":7984},[7968,41594,41595],{"class":7993},"\"ss03\"",[7968,41597,9154],{"class":7984},[7968,41599,41307],{"class":8892},[7968,41601,47],{"class":7984},[7968,41603,5354],{"class":8892},[7968,41605,9154],{"class":7984},[7968,41607,41608],{"class":8892},"line-height",[7968,41610,47],{"class":7984},[7968,41612,41361],{"class":8892},[7968,41614,41615],{"class":7984},"; ... }\n",[18,41617,41618,4361,41620,4361,41622,4361,41624,4361,41627,4361,41630,41633,41634,41636],{},[32,41619,4962],{},[32,41621,4842],{},[32,41623,13415],{},[32,41625,41626],{},"cv11",[32,41628,41629],{},"ss01",[32,41631,41632],{},"ss03","은 모두 사용자단(malgn-noti)의 ",[32,41635,4352],{}," 토큰. admin에는 정의 없음.",[18,41638,41639,41640,5069,41643,4536,41646,4536,41649,41652,41653,41656],{},"원인 — ",[32,41641,41642],{},"dist\u002F_worker.js\u002Fchunks\u002Fbuild\u002F",[32,41644,41645],{},"AppGnb-styles.*.mjs",[32,41647,41648],{},"AppContractPanel-styles.*.mjs",[32,41650,41651],{},"AppCardAddDialog-styles.*.mjs"," 등 사용자단 컴포넌트 chunk가 들어있음. chunk 총수도 ",[21,41654,41655],{},"600개","(admin 단독이면 ~96개).",[18,41658,41659,41660,13739,41663,41665,41666,13746,41669,41671,41672,5439,41675,41678],{},"추적 — ",[32,41661,41662],{},"pwd",[32,41664,41227],{},"(사용자단). 거기서 빌드한 ",[32,41667,41668],{},"dist",[32,41670,41233],{},"으로 ",[21,41673,41674],{},"다른 프로젝트로 deploy",[32,41676,41677],{},"wrangler","는 dist의 출처를 검증하지 않음. project-name만 일치하면 그대로 푸시.",[18,41680,41681],{},"정정:",[5251,41683,41685],{"className":10197,"code":41684,"language":10199,"meta":4208,"style":4208},"cd \u002FUsers\u002Fdotype\u002FProjects\u002Fmalgn-noti-admin\nrm -rf .nuxt .output dist\npnpm build\nnpx wrangler@4 pages deploy dist --project-name=malgn-noti-admin --branch=main --commit-dirty=true --commit-message \"admin clean rebuild\"\n",[32,41686,41687,41695,41712,41718],{"__ignoreMap":4208},[7968,41688,41689,41692],{"class":5110,"line":7970},[7968,41690,41691],{"class":8892},"cd",[7968,41693,41694],{"class":7993}," \u002FUsers\u002Fdotype\u002FProjects\u002Fmalgn-noti-admin\n",[7968,41696,41697,41700,41703,41706,41709],{"class":5110,"line":4212},[7968,41698,41699],{"class":7980},"rm",[7968,41701,41702],{"class":8892}," -rf",[7968,41704,41705],{"class":7993}," .nuxt",[7968,41707,41708],{"class":7993}," .output",[7968,41710,41711],{"class":7993}," dist\n",[7968,41713,41714,41716],{"class":5110,"line":4209},[7968,41715,10206],{"class":7980},[7968,41717,10272],{"class":7993},[7968,41719,41720,41722,41725,41727,41729,41731,41734,41737,41740,41743],{"class":5110,"line":4219},[7968,41721,10277],{"class":7980},[7968,41723,41724],{"class":7993}," wrangler@4",[7968,41726,10283],{"class":7993},[7968,41728,10286],{"class":7993},[7968,41730,10289],{"class":7993},[7968,41732,41733],{"class":8892}," --project-name=malgn-noti-admin",[7968,41735,41736],{"class":8892}," --branch=main",[7968,41738,41739],{"class":8892}," --commit-dirty=true",[7968,41741,41742],{"class":8892}," --commit-message",[7968,41744,41745],{"class":7993}," \"admin clean rebuild\"\n",[18,41747,23142],{},[81,41749,41750,41756,41765],{},[84,41751,41752,41753],{},"chunk 수 600 → ",[21,41754,41755],{},"96",[84,41757,41758,41760,41761,41764],{},[32,41759,41508],{}," body 룰: ",[32,41762,41763],{},"body,html{color:#0f172a;font-family:var(--font-sans);letter-spacing:-.01em;...}"," — font-size 없음 + letter-spacing -0.01em 정확",[84,41766,41767,41768],{},"18 라우트 200, 제목 ",[32,41769,41770],{},"대시보드 · 맑은 메시징 Admin",[76,41772,41774],{"id":41773},"_43-산출물","4.3 산출물",[81,41776,41777,41784],{},[84,41778,41779,41780,41783],{},"사용자단: 없음(사용자단은 6\u002F2 alias ",[32,41781,41782],{},"3ee66d7c"," 그대로 라이브 유지 — 이번 잘못 배포는 admin 프로젝트로만 갔으므로 사용자단 영향 없음)",[84,41785,41786,41787,41790,41791,41793],{},"관리자단: ",[32,41788,41789],{},"malgn-noti-admin: 1b63200"," fix(font) main.css 정합화. Pages 배포 alias ",[32,41792,39783],{}," (clean rebuild)",[76,41795,41797],{"id":41796},"_44-교훈-운영-절차-보강-검토","4.4 교훈 \u002F 운영 절차 보강 검토",[6674,41799,41800,41845,41863],{},[84,41801,41802,41805,41806,41808,41809],{},[21,41803,41804],{},"deploy 명령 보호"," — 멀티 레포 환경에서 cwd가 잘못된 상태로 ",[32,41807,13896],{},"가 다른 프로젝트로 가는 사고는 재발 가능. 방어책:\n",[81,41810,41811,41826,41835],{},[84,41812,41813,89,41816,11686,41818,41821,41822,41825],{},[21,41814,41815],{},"prebuild 가드",[32,41817,10928],{},[32,41819,41820],{},"build"," 스크립트에 ",[32,41823,41824],{},"node -e \"if (require('.\u002Fpackage.json').name !== '\u003Cexpected>') process.exit(1)\""," 사전 체크",[84,41827,41828,89,41831,41834],{},[21,41829,41830],{},"wrangler.toml의 pages 프로젝트 매칭",[32,41832,41833],{},"pnpm run deploy:pages"," 같은 알리아스 스크립트로 cwd + project-name을 묶음",[84,41836,41837,41840,41841,41844],{},[21,41838,41839],{},"이력 추적 단순화"," — 매 배포 직후 ",[32,41842,41843],{},"wrangler deployments list \u003Cproject>"," 결과의 commit hash가 expected repo HEAD와 일치하는지 확인",[84,41846,41847,41850,41851,4361,41853,5069,41855,41857,41858,11686,41860,41862],{},[21,41848,41849],{},"base font-size 명시 패턴 금지"," — Tailwind 토큰이 16px base를 가정하므로, ",[32,41852,13555],{},[32,41854,14975],{},[32,41856,41566],{},"를 다른 값으로 명시하면 모든 토큰이 어긋남. 필요한 경우엔 토큰 자체(",[32,41859,4342],{},[32,41861,13415],{},")를 갱신.",[84,41864,41865,41868],{},[21,41866,41867],{},"chunk 수 sanity check"," — admin 같이 17 페이지 규모면 chunk가 ~100개 안팎. 600개가 떴다면 외부 자산 혼입 의심.",[73,41870],{},[11,41872,41874],{"id":41873},"_5-wbs-페이지-편집-기능-r2-json-정본-인라인-모달-workers-version-28f3e6a8-pages-alias-02bb58e6","§5. WBS 페이지 편집 기능 — R2 JSON 정본 + 인라인 모달 (Workers Version 28f3e6a8 \u002F Pages alias 02bb58e6)",[76,41876,41878],{"id":41877},"_51-배경","5.1 배경",[18,41880,41881,41883,41884,41888,41889,41892,41893,41896],{},[32,41882,26863],{}," 페이지는 그동안 ",[26,41885,41886],{"href":27139},[32,41887,26889],{}," 안에 STAGES 상수로 임베디드된 데이터 — 매번 코드 수정 + 배포해야 진척률·메모를 갱신할 수 있었다. 사용자가 \"",[21,41890,41891],{},"설명·링크·목표일·완료일·담당자를 수정할 수 있게","\" + \"",[21,41894,41895],{},"DB 미사용, R2에 JSON 파일로 저장","\" 정책을 지정.",[76,41898,41900],{"id":41899},"_52-결정","5.2 결정",[81,41902,41903,41915,41931,41944],{},[84,41904,41905,41908,41909,41911,41912,41914],{},[21,41906,41907],{},"저장소",": R2 단일 객체 (",[32,41910,35819],{}," 버킷 \u002F 키 ",[32,41913,58],{},"). 기존 FILES 바인딩 재사용 — 신규 바인딩 없음.",[84,41916,41917,47,41920,4420,41922,4420,41924,4420,41926,4420,41928,41930],{},[21,41918,41919],{},"편집 가능 필드 5개",[32,41921,27230],{},[32,41923,27251],{},[32,41925,27237],{},[32,41927,27244],{},[32,41929,27225],{},". 상태(완료\u002F진행 중\u002F대기) · 단계 가중치 · 진행률 · 그룹 · 제목은 본 화면 편집 대상 아님(시드\u002F코드 변경).",[84,41932,41933,41936,41937,41939,41940,41943],{},[21,41934,41935],{},"인증 정책",": GET 공개 \u002F PATCH 로그인 필요. 페이지 자체는 그대로 ",[32,41938,19861],{},", 편집 버튼이 ",[32,41941,41942],{},"auth.user","일 때만 노출.",[84,41945,41946,41949],{},[21,41947,41948],{},"동시성",": last-write-wins. 단일 운영자 저빈도 사용 가정. ETag\u002FIf-Match는 향후 도입 여지.",[76,41951,41953],{"id":41952},"_53-api-변경-malgn-noti-api","5.3 API 변경 (malgn-noti-api)",[18,41955,22931],{},[81,41957,41958,41971],{},[84,41959,41960,41966,41967,41970],{},[26,41961,41963],{"href":41962},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Fdata\u002Fwbs-seed.ts",[32,41964,41965],{},"src\u002Fdata\u002Fwbs-seed.ts"," — 5 stages \u002F ",[21,41968,41969],{},"142 tasks"," 시드. 현행 사용자단 임베디드 STAGES 그대로 복제. R2 미존재 시 첫 GET이 이 값을 PUT 후 반환.",[84,41972,41973,9194,41979],{},[26,41974,41976],{"href":41975},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Froutes\u002Fwbs.ts",[32,41977,41978],{},"src\u002Froutes\u002Fwbs.ts",[81,41980,41981,41990],{},[84,41982,41983,41985,41986,41989],{},[32,41984,54],{}," — 공개. ",[32,41987,41988],{},"loadDoc()","이 R2 객체 없으면 시드를 PUT 후 반환.",[84,41991,41992,89,41995,41997,41998,6219,42000,7505,42003,42005,42006,4339,42009,4703],{},[32,41993,41994],{},"PATCH \u002Fwbs\u002Ftasks\u002F:taskId",[32,41996,21781],{}," 미들웨어. body 5필드. ",[32,41999,30655],{},[32,42001,42002],{},"delete target[field]",[32,42004,37694],{}," → 유지 \u002F 값 → 갱신. 마지막에 ",[32,42007,42008],{},"lastUpdated = new Date().toISOString().slice(0, 10)",[32,42010,42011],{},"saveDoc()",[18,42013,41410],{},[81,42015,42016,42026],{},[84,42017,42018,89,42022,42025],{},[26,42019,42020],{"href":18661},[32,42021,11243],{},[32,42023,42024],{},"app.route('\u002Fwbs', wbs)"," 등록.",[84,42027,42028,89,42032,42035],{},[26,42029,42030],{"href":29675},[32,42031,22139],{},[32,42033,42034],{},"wbs"," 태그 + 2개 경로(GET 공개·PATCH 401 응답 포함).",[76,42037,42039],{"id":42038},"_54-사용자단-변경-malgn-noti","5.4 사용자단 변경 (malgn-noti)",[18,42041,42042,42046],{},[26,42043,42044],{"href":27139},[32,42045,26889],{}," 전면 재작성:",[81,42048,42049,42056,42063,42082,42092],{},[84,42050,42051,42052,42055],{},"임베디드 STAGES 제거 → top-level ",[32,42053,42054],{},"await api('\u002Fwbs')","로 비동기 로드. 로딩\u002F에러 상태 노출.",[84,42057,42058,42059,42062],{},"task 행 우측에 ✏️ 편집 버튼 — ",[32,42060,42061],{},"v-if=\"auth.user\"","로 로그인 사용자에게만 노출.",[84,42064,42065,42066,42068,42069],{},"모달 (",[32,42067,4476],{}," 기반): 담당자 \u002F 설명 \u002F 링크 \u002F 목표일 \u002F 완료일 5개 입력.\n",[81,42070,42071,42077],{},[84,42072,42073,42074,42076],{},"빈 문자열 저장 시 payload에 ",[32,42075,30655],{}," 전송 → 서버 R2에서 해당 필드 제거.",[84,42078,42079,42081],{},[32,42080,27225],{},"는 빈 값 불가 (Zod min(1)). 빈 값일 땐 payload에서 제외 → 미변경.",[84,42083,42084,42085,42087,42088,42091],{},"저장 성공 시 ",[32,42086,6633],{}," 알림 + ",[32,42089,42090],{},"Object.assign(t, res.data)","로 in-place 갱신(refetch 없음).",[84,42093,42094,42095,42098,42099,42102],{},"비로그인 부제에 ",[32,42096,42097],{},"· 로그인하면 편집 가능"," 힌트 노출 + ",[32,42100,42101],{},"\u002Flogin?redirect=\u002Fwbs"," 링크.",[76,42104,42106],{"id":42105},"_55-배포","5.5 배포",[81,42108,42109,42121],{},[84,42110,42111,42112,29918,42114,42117,42118,42120],{},"API: typecheck → ",[32,42113,18672],{},[32,42115,42116],{},"28f3e6a8-6b53-42ee-b3d7-a145584f43d0",". 번들 2672 KiB \u002F gzip 609. Worker Startup 75 ms. FILES 바인딩(",[32,42119,35819],{},") 정상.",[84,42122,42123,42124,6219,42126,42128,42129,4703],{},"Pages: ",[32,42125,11195],{},[32,42127,13324],{},". alias ",[32,42130,42131],{},"02bb58e6.malgn-noti.pages.dev",[76,42133,42135],{"id":42134},"_56-검증","5.6 검증",[81,42137,42138,42146,42158],{},[84,42139,42140,42142,42143,42145],{},[32,42141,22384],{}," 200 \u002F ",[32,42144,20116],{}," 200 (mysql 8.0.42)",[84,42147,42148,13923,42150,42153,42154,42157],{},[32,42149,54],{},[21,42151,42152],{},"30,406 bytes"," JSON. stages: 5 \u002F tasks: 142 \u002F lastUpdated: ",[32,42155,42156],{},"2026-06-01"," (시드 값 — R2 첫 PUT 직후 그대로). 시드 자동 적재 동작 확인.",[84,42159,42160,42162],{},[32,42161,27558],{}," 200, alias 200.",[76,42164,42166],{"id":42165},"_57-산출물","5.7 산출물",[81,42168,42169,42175,42181],{},[84,42170,42171,42174],{},[32,42172,42173],{},"malgn-noti-api: 9945db3"," feat(wbs): R2 JSON 정본 + GET 공개 \u002F PATCH 인증 라우트 (4 files, +452)",[84,42176,42177,42180],{},[32,42178,42179],{},"malgn-noti: 3ed473e"," feat(wbs): \u002Fwbs API 연동 + 인라인 편집 모달 (1 file, +376 -355)",[84,42182,42183,42184,42186],{},"R2 객체 ",[32,42185,58],{}," 라이브 (FILES 바인딩, 시드 자동 적재됨)",[76,42188,42190],{"id":42189},"_59-후속-날짜-포맷-정합화-workers-version-eb02206c-pages-alias-98bd09e2","5.9 후속 — 날짜 포맷 정합화 (Workers Version eb02206c \u002F Pages alias 98bd09e2)",[18,42192,42193],{},"사용자 지시: \"목표일과 완료일은 년.월.일 로 변경해 주세요.\"",[81,42195,42196,42220,42232,42241,42250,42264],{},[84,42197,42198,51,42200,7505,42203,7505,42206,42209,42210,42212,42213,42216,42217,42219],{},[21,42199,680],{},[32,42201,42202],{},"formatYmd",[32,42204,42205],{},"toDateInputValue",[32,42207,42208],{},"fromDateInputValue"," 헬퍼 도입. 어떤 입력(YYYY.MM.DD \u002F YYYY-MM-DD \u002F 레거시 M-D)이든 ",[32,42211,39804],{},"로 정규화. 표시에 일괄 적용 — 레거시 ",[32,42214,42215],{},"5\u002F8","은 2026 기준 ",[32,42218,301],{},"로 렌더.",[84,42221,42222,42225,42226,42228,42229,42231],{},[21,42223,42224],{},"편집 모달",": 텍스트 입력 → ",[32,42227,39808],{}," 두 개로 교체(브라우저 캘린더 위젯). 빈값은 ",[32,42230,30655],{},"로 전송 → R2 필드 제거.",[84,42233,42234,42236,42237,42240],{},[21,42235,31319],{}," Zod에 ",[32,42238,42239],{},"^\\d{4}\\.\\d{2}\\.\\d{2}$"," regex 검증 추가, 위반 시 400. OpenAPI 스키마도 동기화.",[84,42242,42243,42246,42247,42249],{},[21,42244,42245],{},"저장 정책",": 신규 PATCH는 무조건 ",[32,42248,39804],{},". 기존 R2의 레거시 값은 그대로 두고 다음 편집 시 자연 정합화.",[84,42251,13284,42252,42142,42254,42142,42256,42142,42258,42261,42262,13932],{},[32,42253,11248],{},[32,42255,20116],{},[32,42257,26863],{},[32,42259,42260],{},"PATCH \u002Fwbs\u002Ftasks\u002F1-1-1"," no-auth → 401(정상). prod & alias ",[32,42263,26863],{},[84,42265,20936,42266,42269,42270,7086,42273,42276,42277,5606],{},[32,42267,42268],{},"malgn-noti-api: 3a35464"," fix(wbs): targetDate\u002FcompletionDate 포맷 YYYY.MM.DD 강제 (Workers Version ",[32,42271,42272],{},"eb02206c-c076-4f09-8881-5f536feecb02",[32,42274,42275],{},"malgn-noti: 08e5c33"," fix(wbs): 목표일·완료일 YYYY.MM.DD 포맷 + native date picker (Pages alias ",[32,42278,42279],{},"98bd09e2.malgn-noti.pages.dev",[76,42281,42283],{"id":42282},"_510-한계-후속","5.10 한계 \u002F 후속",[81,42285,42286,42292,42298],{},[84,42287,42288,42291],{},[21,42289,42290],{},"동시 편집 — last-write-wins",". 두 명이 동시에 다른 task를 PATCH하면 둘 다 R2 read-modify-write를 하므로 한쪽이 사라질 수 있다. 강한 정합 필요 시 ETag(If-Match) 도입.",[84,42293,42294,42297],{},[21,42295,42296],{},"편집 범위 제한",": status \u002F weight \u002F progress \u002F title \u002F group \u002F 단계 추가·삭제는 본 화면에서 안 됨. 필요하면 별도 슬라이스에서 모달 확장 + 권한 강화(owner\u002Fadmin only).",[84,42299,42300,42303],{},[21,42301,42302],{},"lastUpdated"," 자동 갱신만 됨 — 누가 언제 무엇을 바꿨는지 감사 로그는 없음. 운영 단계 진입 전 도입 검토.",[73,42305],{},[73,42307],{},[11,42309,42311],{"id":42310},"_6-nhn-notification-hub-oauth-어댑터-email-실-발송-서비스-담당자-이메일-변경-라우트","§6. NHN Notification Hub OAuth 어댑터 + Email 실 발송 + 서비스 담당자 이메일 변경 라우트",[76,42313,27637],{"id":31856},[18,42315,42316,42317,42320,42321,35401,42324,4361,42326,42329,42330,42332,42333,4361,42335,42338,42339,42341,42342,42344,42345,42348,42349,42355,42356,42358,42359,42361],{},"NHN 신규 통합 서비스(Notification Hub)는 기존 AppKey + SecretKey 직접 호출이 아닌 ",[21,42318,42319],{},"OAuth2 client_credentials → Bearer 토큰"," 방식. 어댑터 전면 재작성(",[32,42322,42323],{},"src\u002Fadapters\u002Fnhn\u002Foauth.ts",[32,42325,25109],{},[32,42327,42328],{},"email.ts"," 재작성). Email은 ",[32,42331,3775],{}," 발신 도메인 콘솔 등록 + ",[32,42334,31517],{},[32,42336,42337],{},"EMAIL_FROM_NAME"," secret 등록 후 실 발송 검증 통과(messageId 발급). SMS는 콘솔 발신번호 등록 + ",[32,42340,31521],{}," secret 대기. 동시에 ",[32,42343,39621],{}," 신설 — OTP(",[32,42346,42347],{},"purpose=change_email",") + 비밀번호 검증 후 ",[21,42350,42351,42354],{},[32,42352,42353],{},"user.email"," 만"," UPDATE(",[32,42357,20087],{},"는 가입 시 식별자로 고정 유지). 사용자단 ",[32,42360,18591],{},"도 실 API로 교체.",[76,42363,42365],{"id":42364},"_61-어댑터-재작성","6.1 어댑터 재작성",[81,42367,42368,42392,42416,42427],{},[84,42369,42370,89,42372,42375,42376,42379,42380,42383,42384,42387,42388,42391],{},[32,42371,42323],{},[32,42373,42374],{},"https:\u002F\u002Foauth.api.nhncloudservice.com\u002Foauth2\u002Ftoken\u002Fcreate","로 Basic Auth(",[32,42377,42378],{},"userAccessKey:secretAccessKey",") + body ",[32,42381,42382],{},"grant_type=client_credentials&scope=appKey:{APP_KEY}",". 응답 ",[32,42385,42386],{},"access_token","은 메모리 캐시(",[32,42389,42390],{},"globalThis.__nhnTokenCache",", 60s 안전 마진).",[84,42393,42394,89,42396,42399,42400,4339,42403,4339,42406,42409,42410,4339,42413,4703],{},[32,42395,23543],{},[32,42397,42398],{},"POST {base}\u002Fmessage\u002Fv1.0\u002FSMS\u002Ffree-form-messages\u002F{messagePurpose}",". body: ",[32,42401,42402],{},"sender.senderPhoneNumber",[32,42404,42405],{},"recipients[].contacts[](contactType=PHONE_NUMBER, contact)",[32,42407,42408],{},"content(messageType, body, title?)",". 헤더 ",[32,42411,42412],{},"X-NC-APP-KEY",[32,42414,42415],{},"X-NHN-Authorization: Bearer ...",[84,42417,42418,42420,42421,4473,42424,5606],{},[32,42419,25716],{}," — 동형(",[32,42422,42423],{},"\u002FEMAIL\u002Ffree-form-messages\u002F{purpose}",[32,42425,42426],{},"contactType=EMAIL_ADDRESS",[84,42428,42429,42431,42432,4536,42435,42438,42439,42442,42443,42446],{},[32,42430,25095],{}," 확장 — ",[32,42433,42434],{},"userAccessKey",[32,42436,42437],{},"secretAccessKey"," 추가, 기존 ",[32,42440,42441],{},"secretKey","는 옵셔널로 다운그레이드. push\u002Frcs\u002Fkakao는 ",[32,42444,42445],{},"!creds.secretKey"," 가드로 mock fallback 유지(후속 마이그레이션).",[76,42448,42450],{"id":42449},"_62-email-실-발송-활성화","6.2 Email 실 발송 활성화",[81,42452,42453,42459,42468],{},[84,42454,42455,42456,42458],{},"NHN Notification Hub 콘솔에 ",[32,42457,3775],{}," 발신 도메인 등록 + SPF\u002FDKIM 설정.",[84,42460,42461,42462,7505,42465,42025],{},"Workers secret ",[32,42463,42464],{},"EMAIL_FROM=message@malgnsoft.com",[32,42466,42467],{},"EMAIL_FROM_NAME=맑은 메시징",[84,42469,42470,42471,42474,42475,4703],{},"라이브 검증 — ",[32,42472,42473],{},"POST \u002Fsend\u002Femail",": SUCCESS · ",[32,42476,42477],{},"messageId=20260604154608lR6MBAKn8v0",[76,42479,42481,42482,42484],{"id":42480},"_63-post-meemail-change-정책","6.3 ",[32,42483,39621],{}," 정책",[81,42486,42487,42495,42502,42511],{},[84,42488,42489,47,42491,42494],{},[21,42490,38989],{},[32,42492,42493],{},"user.loginid"," — 가입 시 발급된 식별자이며 전역 UNIQUE. 변경 가능하면 감사·연동 추적·세션이 깨진다.",[84,42496,42497,47,42499,42501],{},[21,42498,7525],{},[32,42500,42353],{}," 한 항목만. 알림·연락처용 이메일이 새 주소로 교체.",[84,42503,42504,42507,42508,42510],{},[21,42505,42506],{},"검증 흐름",": (1) 새 이메일에 OTP(",[32,42509,42347],{},") 발송 → (2) 클라이언트가 본인 비밀번호 + OTP 동시 입력 → (3) 서버에서 비밀번호 PBKDF2 검증 + OTP 코드 검증 + email-only UPDATE + 코드 소비 마킹.",[84,42512,42513],{},"5 시나리오 e2e 통과: 정상 \u002F 코드 만료 \u002F 코드 오타 \u002F 비밀번호 오타 \u002F 동일 이메일 재시도.",[76,42515,42517],{"id":42516},"_64-사용자단-변경","6.4 사용자단 변경",[81,42519,42520,42537,42551,42564],{},[84,42521,42522,47,42525,4361,42528,13746,42531,6100,42534,42536],{},[32,42523,42524],{},"AppEmailChangeDialog.vue",[32,42526,42527],{},"sendCode",[32,42529,42530],{},"confirmCode",[32,42532,42533],{},"\u002Fauth\u002Femail-code\u002F{send,verify}",[32,42535,42347],{},")로 교체.",[84,42538,42539,42540,47,42542,6219,42545,6685,42548,5606],{},"emit ",[32,42541,9175],{},[32,42543,42544],{},"[string]",[32,42546,42547],{},"[EmailChangePayload]",[32,42549,42550],{},"{newEmail, code, password}",[84,42552,42553,47,42555,42558,42559,6219,42561,29302],{},[32,42554,28955],{},[32,42556,42557],{},"changeEmail(payload)"," 액션 추가 — ",[32,42560,39621],{},[32,42562,42563],{},"user\u002Ftenant",[84,42565,42566,42568,42569,12916,42571,42573],{},[32,42567,3991],{}," 두 다이얼로그 중 서비스 담당자 이메일 변경 다이얼로그가 실 API로 교체. ",[21,42570,33130],{},[32,42572,32700],{},"로 별도 흐름 유지.",[76,42575,33396],{"id":33395},[81,42577,42578,42600,42614],{},[84,42579,32568,42580,42582,42583,4536,42585,42587,42588,42590,42591,51,42593,42596,42597,42599],{},[32,42581,42323],{}," 신규 \u002F ",[32,42584,23543],{},[32,42586,42328],{}," 재작성 \u002F ",[32,42589,25772],{}," 확장 \u002F ",[32,42592,32748],{},[32,42594,42595],{},"POST \u002Femail-change"," 추가 \u002F ",[32,42598,22139],{}," summary 갱신.",[84,42601,33415,42602,42587,42605,51,42607,7505,42610,42613],{},[32,42603,42604],{},"app\u002Fcomponents\u002FAppEmailChangeDialog.vue",[32,42606,28955],{},[32,42608,42609],{},"changeEmail",[32,42611,42612],{},"app\u002Fpages\u002Faccount\u002Fsettings.vue"," 토스트 메시지 정정.",[84,42615,42616],{},"배포: Workers Version 갱신(이메일 변경 + NHN OAuth) \u002F Pages alias 갱신.",[76,42618,42620],{"id":42619},"_66-알려진-한계","6.6 알려진 한계",[81,42622,42623,42626,42629],{},[84,42624,42625],{},"push\u002Frcs\u002Fkakao 어댑터는 아직 legacy AppKey + SecretKey 직호출 형태 — Notification Hub 마이그레이션 후속.",[84,42627,42628],{},"SMS 라이브 e2e 1건 미실행 (발신번호 등록 대기).",[84,42630,42631,42632,42635],{},"자격증명은 현재 Worker 환경변수에 직접 보관. envelope 암호화(",[32,42633,42634],{},"NhnCredential"," 테이블 + master key)는 멀티 테넌트 진입 시 도입.",[73,42637],{},[11,42639,42641,42642,42644],{"id":42640},"_7-wbs-현행화-r2-정본-docwbsmd-동시-갱신","§7. WBS 현행화 — R2 정본 + ",[32,42643,42],{}," 동시 갱신",[76,42646,27637],{"id":32704},[18,42648,42649,42650,4339,42652,42654],{},"오늘 §1~§6 작업을 WBS 정본 두 곳(R2 ",[32,42651,58],{},[32,42653,42],{},")에 반영. Step 5 task 9건 신규(5-2-19\u002F20\u002F21, 5-3-15, 5-3C-20, 5-4-14\u002F15\u002F16, 5-5-10\u002F11\u002F12) + 5-3C-7 완료 승급 + 5-2-16·5-5-5 in_progress 승급. 진척률 48% → 55%, 가중평균 약 47.5%.",[76,42656,42658],{"id":42657},"_71-추가갱신된-task","7.1 추가·갱신된 task",[81,42660,42661,42667,42674,42679,42688,42693,42700,42705,42710,42715,42723,42728,42735,42740,42745,42750],{},[84,42662,42663,51,42665,23936],{},[21,42664,2433],{},[32,42666,2438],{},[84,42668,42669,51,42671,42673],{},[21,42670,2453],{},[32,42672,39621],{}," — 서비스 담당자 이메일 변경 ✅",[84,42675,42676,42678],{},[21,42677,2472],{}," NHN Notification Hub 어댑터 신규(OAuth + Bearer) ✅",[84,42680,42681,42683,42684,42687],{},[21,42682,2308],{}," PG 어댑터 — ",[21,42685,42686],{},"TossPayments 확정","(메모 갱신)",[84,42689,42690,42692],{},[21,42691,2350],{}," NHN 실 모드 전환 — ⚪ → 🟢 (OAuth 어댑터 완료, envelope 후속)",[84,42694,42695,51,42697,42699],{},[21,42696,2783],{},[32,42698,26863],{}," 페이지 — R2 정본 비동기 로드 + 인라인 편집 모달 ✅",[84,42701,42702,42704],{},[21,42703,2981],{}," 회원 정보 변경 — 🟢 → ✅ (이메일 변경 OTP 연결 완료)",[84,42706,42707,42709],{},[21,42708,3209],{}," 서비스 담당자 이메일 변경 — 실 OTP API 연동 ✅",[84,42711,42712,42714],{},[21,42713,3493],{}," 관리자단 핸드오프 정본 17 페이지 풀세트 ✅",[84,42716,42717,42719,42720,23936],{},[21,42718,3512],{}," 페이지 진척 상태 라벨 ",[32,42721,42722],{},"dev=screen\u002Fpartial\u002Flive",[84,42724,42725,42727],{},[21,42726,3531],{}," 관리자 로고\u002F브랜드 — 사용자단 로고로 통일 + \"관리자\" 배지 ✅",[84,42729,42730,4361,42732,42734],{},[21,42731,3574],{},[32,42733,3612],{}," 배포 카운터 갱신",[84,42736,42737,42739],{},[21,42738,3650],{}," NHN Notification Hub real — ⚪ → 🟢 (Email ✅, SMS pending)",[84,42741,42742,42744],{},[21,42743,3746],{}," Hyperdrive Cloudflare Tunnel(Access) 전환 ✅",[84,42746,42747,42749],{},[21,42748,3765],{}," NHN Email 실 발송 활성화 ✅",[84,42751,42752,42754],{},[21,42753,3789],{}," NHN SMS 실 발송 활성화 ⚪ (발신번호 등록 대기)",[76,42756,42758],{"id":42757},"_72-산출물","7.2 산출물",[81,42760,42761,42768,42774],{},[84,42762,42763,42764,42767],{},"R2: ",[32,42765,42766],{},"malgn-noti-files\u002Fwbs\u002Fwbs.json"," 업로드 (142 → 155 task).",[84,42769,42770,42771,42773],{},"정본 MD: ",[32,42772,42],{}," 스냅샷 표 \u002F 5-2 \u002F 5-3A \u002F 5-3M \u002F 5-3C \u002F 5-4 \u002F 5-5 \u002F \"알려진 한계\" 갱신.",[84,42775,42776,42777,42780],{},"라이브 검증: ",[32,42778,42779],{},"GET https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fwbs"," → 155 task 확인.",[76,42782,42784],{"id":42783},"_73-한계-후속","7.3 한계 \u002F 후속",[81,42786,42787,42790],{},[84,42788,42789],{},"Step 1·2·3·4의 진척률은 6\u002F4 작업과 무관해 그대로(55\u002F55\u002F35\u002F20%). 컨설팅팀·기획팀 작업이 들어오면 별도 갱신.",[84,42791,42792],{},"\"WBS 현행화\" 자체의 감사 로그(누가 언제 무엇을 PATCH 했는지)는 미 — 운영 진입 시 도입.",[73,42794],{},[73,42796],{},[11,42798,42800],{"id":42799},"_8-서비스-담당자-이메일-변경-401-자동-로그아웃-otp-이중-소비-다이얼로그-데드락-fix-workers-version-772adb3b-pages-alias-cd6a5bb3","§8. 서비스 담당자 이메일 변경 — 401 자동 로그아웃 + OTP 이중 소비 + 다이얼로그 데드락 fix (Workers Version 772adb3b \u002F Pages alias cd6a5bb3)",[76,42802,27637],{"id":33481},[18,42804,42805,42806,42808,42809,42811,42812,42814,42815,42818,42819,39646,42821,42823,42824,42826,42827,39673,42829,42831],{},"사용자 보고 \"변경 완료 후 로그아웃 + 이메일 미변경\". 추적 결과 세 단계가 겹쳐 발생: (a) 다이얼로그 \"확인\" → ",[32,42807,39645],{}," 가 verification row 의 ",[32,42810,39652],{}," 을 마킹 → (b) 직후 \"변경\" → ",[32,42813,39656],{}," 가 다시 OTP 검증 시 ",[32,42816,42817],{},"isNull(consumedAt)"," 매치 실패 → 401 → (c) ",[32,42820,39660],{},[32,42822,30131],{}," 외 401 을 토큰 만료로 간주해 자동 로그아웃 + ",[32,42825,9335],{}," 이동. 추가로 다이얼로그는 ",[32,42828,39672],{},[32,42830,39676],{}," 를 기다려서, 에러 토스트는 떴지만 \"변경 중…\" 버튼이 영원히 잠겨 다시 시도 불가.",[76,42833,42835],{"id":42834},"_81-a-otp-이중-소비-verify-only-분기","8.1 (a) OTP 이중 소비 — verify-only 분기",[81,42837,42838,42849],{},[84,42839,42840,42841,42845,42846,42848],{},"다른 purpose (signup·reset_password·contract_sign) 는 verify 한 번에 검증·소비가 끝나는 흐름이라 그대로 두고, ",[21,42842,42843,42354],{},[32,42844,29557],{}," verify 단계에서 ",[32,42847,39652],{}," 마킹 건너뜀.",[84,42850,42851,42852,42854],{},"최종 ",[32,42853,39656],{}," 가 한 번 더 검증 + UPDATE + 소비.",[18,42856,41410],{},[5251,42858,42860],{"className":7962,"code":42859,"language":7964,"meta":4208,"style":4208},"\u002F\u002F src\u002Froutes\u002Fauth.ts — POST \u002Fauth\u002Femail-code\u002Fverify\nif (purpose !== 'change_email') {\n  await db.update(verification)\n    .set({ consumedAt: now })\n    .where(eq(verification.id, row.id))\n}\n",[32,42861,42862,42867,42881,42892,42901,42914],{"__ignoreMap":4208},[7968,42863,42864],{"class":5110,"line":7970},[7968,42865,42866],{"class":8398},"\u002F\u002F src\u002Froutes\u002Fauth.ts — POST \u002Fauth\u002Femail-code\u002Fverify\n",[7968,42868,42869,42871,42874,42876,42879],{"class":5110,"line":4212},[7968,42870,31692],{"class":7973},[7968,42872,42873],{"class":7984}," (purpose ",[7968,42875,33225],{"class":7973},[7968,42877,42878],{"class":7993}," 'change_email'",[7968,42880,21462],{"class":7984},[7968,42882,42883,42885,42887,42889],{"class":5110,"line":4209},[7968,42884,36160],{"class":7973},[7968,42886,34357],{"class":7984},[7968,42888,37043],{"class":7980},[7968,42890,42891],{"class":7984},"(verification)\n",[7968,42893,42894,42896,42898],{"class":5110,"line":4219},[7968,42895,37776],{"class":7984},[7968,42897,37048],{"class":7980},[7968,42899,42900],{"class":7984},"({ consumedAt: now })\n",[7968,42902,42903,42905,42907,42909,42911],{"class":5110,"line":8291},[7968,42904,37776],{"class":7984},[7968,42906,34376],{"class":7980},[7968,42908,6100],{"class":7984},[7968,42910,34381],{"class":7980},[7968,42912,42913],{"class":7984},"(verification.id, row.id))\n",[7968,42915,42916],{"class":5110,"line":8301},[7968,42917,8987],{"class":7984},[76,42919,42921],{"id":42920},"_82-b-401-422-분리","8.2 (b) 401 → 422 분리",[18,42923,42924,42926,42927,42930],{},[32,42925,29627],{}," 401 은 ",[21,42928,42929],{},"토큰 검증 실패(미들웨어 단계)"," 에만 한정하기로 의미 정리. 본인 재인증(비밀번호·OTP) 실패는 새 헬퍼:",[5251,42932,42934],{"className":7962,"code":42933,"language":7964,"meta":4208,"style":4208},"\u002F\u002F src\u002Flib\u002Ferrors.ts\nunprocessable: (msg: string) => new AppError('unprocessable', 422, msg),\n",[32,42935,42936,42941],{"__ignoreMap":4208},[7968,42937,42938],{"class":5110,"line":7970},[7968,42939,42940],{"class":8398},"\u002F\u002F src\u002Flib\u002Ferrors.ts\n",[7968,42942,42943,42946,42949,42952,42954,42956,42958,42960,42962,42965,42967,42970,42972,42975],{"class":5110,"line":4212},[7968,42944,42945],{"class":7980},"unprocessable",[7968,42947,42948],{"class":7984},": (",[7968,42950,42951],{"class":8939},"msg",[7968,42953,9148],{"class":7973},[7968,42955,9151],{"class":8892},[7968,42957,21438],{"class":7984},[7968,42959,9426],{"class":7973},[7968,42961,34147],{"class":7973},[7968,42963,42964],{"class":7980}," AppError",[7968,42966,6100],{"class":7984},[7968,42968,42969],{"class":7993},"'unprocessable'",[7968,42971,4473],{"class":7984},[7968,42973,42974],{"class":8892},"422",[7968,42976,42977],{"class":7984},", msg),\n",[18,42979,42980,42982,42983,42985],{},[32,42981,39656],{}," 의 비밀번호 불일치·OTP 만료·OTP 불일치 throw 를 모두 ",[32,42984,39664],{}," 로 교체. 클라이언트의 자동 로그아웃 트리거 회피 + 의미 정확.",[76,42987,42989],{"id":42988},"_83-c-다이얼로그-submit-데드락-async-callback-prop","8.3 (c) 다이얼로그 submit 데드락 — async callback prop",[18,42991,42992,42993,6219,42995,4339,42997,43000,43001,43003,43004,43007,43008,39669,43011,24198,43013,43016],{},"기존: ",[32,42994,39668],{},[32,42996,39672],{},[32,42998,42999],{},"emit('confirm', payload)"," → 부모가 ",[32,43002,37284],{}," 성공 시 ",[32,43005,43006],{},"emit('close')"," → 다이얼로그 ",[32,43009,43010],{},"watch(open)",[32,43012,38636],{},[32,43014,43015],{},"submitting=false",". 에러 시 부모가 toast 만 띄우고 close 하지 않으므로 데드락.",[18,43018,43019,43020,43022,43023,4339,43025,43028],{},"변경: 다이얼로그 prop 에 ",[32,43021,39680],{}," 추가. 다이얼로그가 직접 ",[32,43024,37284],{},[32,43026,43027],{},"try\u002Fcatch\u002Ffinally"," 로 자기 상태 관리.",[5251,43030,43032],{"className":7962,"code":43031,"language":7964,"meta":4208,"style":4208},"\u002F\u002F AppEmailChangeDialog.vue\nasync function submit() {\n  if (submitting.value) return\n  submitting.value = true\n  try {\n    await props.onConfirm({ newEmail: ..., code: ..., password: ... })\n    emit('close')\n  } catch (e) {\n    toast.add({ title: msgFromError(e), color: 'error' })\n  } finally {\n    submitting.value = false   \u002F\u002F 성공·실패 모두 잠금 해제\n  }\n}\n",[32,43033,43034,43039,43051,43060,43070,43077,43104,43116,43126,43146,43155,43167,43171],{"__ignoreMap":4208},[7968,43035,43036],{"class":5110,"line":7970},[7968,43037,43038],{"class":8398},"\u002F\u002F AppEmailChangeDialog.vue\n",[7968,43040,43041,43043,43045,43048],{"class":5110,"line":4212},[7968,43042,9453],{"class":7973},[7968,43044,34185],{"class":7973},[7968,43046,43047],{"class":7980}," submit",[7968,43049,43050],{"class":7984},"() {\n",[7968,43052,43053,43055,43058],{"class":5110,"line":4209},[7968,43054,21447],{"class":7973},[7968,43056,43057],{"class":7984}," (submitting.value) ",[7968,43059,31709],{"class":7973},[7968,43061,43062,43065,43067],{"class":5110,"line":4219},[7968,43063,43064],{"class":7984},"  submitting.value ",[7968,43066,8254],{"class":7973},[7968,43068,43069],{"class":8892}," true\n",[7968,43071,43072,43075],{"class":5110,"line":8291},[7968,43073,43074],{"class":7973},"  try",[7968,43076,8887],{"class":7984},[7968,43078,43079,43081,43084,43087,43090,43092,43095,43097,43100,43102],{"class":5110,"line":8301},[7968,43080,34477],{"class":7973},[7968,43082,43083],{"class":7984}," props.",[7968,43085,43086],{"class":7980},"onConfirm",[7968,43088,43089],{"class":7984},"({ newEmail: ",[7968,43091,9432],{"class":7973},[7968,43093,43094],{"class":7984},", code: ",[7968,43096,9432],{"class":7973},[7968,43098,43099],{"class":7984},", password: ",[7968,43101,9432],{"class":7973},[7968,43103,8274],{"class":7984},[7968,43105,43106,43109,43111,43114],{"class":5110,"line":8310},[7968,43107,43108],{"class":7980},"    emit",[7968,43110,6100],{"class":7984},[7968,43112,43113],{"class":7993},"'close'",[7968,43115,9563],{"class":7984},[7968,43117,43118,43121,43123],{"class":5110,"line":8321},[7968,43119,43120],{"class":7984},"  } ",[7968,43122,38180],{"class":7973},[7968,43124,43125],{"class":7984}," (e) {\n",[7968,43127,43128,43131,43133,43135,43138,43141,43144],{"class":5110,"line":8332},[7968,43129,43130],{"class":7984},"    toast.",[7968,43132,37338],{"class":7980},[7968,43134,8268],{"class":7984},[7968,43136,43137],{"class":7980},"msgFromError",[7968,43139,43140],{"class":7984},"(e), color: ",[7968,43142,43143],{"class":7993},"'error'",[7968,43145,8274],{"class":7984},[7968,43147,43148,43150,43153],{"class":5110,"line":8343},[7968,43149,43120],{"class":7984},[7968,43151,43152],{"class":7973},"finally",[7968,43154,8887],{"class":7984},[7968,43156,43157,43160,43162,43164],{"class":5110,"line":8349},[7968,43158,43159],{"class":7984},"    submitting.value ",[7968,43161,8254],{"class":7973},[7968,43163,35108],{"class":8892},[7968,43165,43166],{"class":8398},"   \u002F\u002F 성공·실패 모두 잠금 해제\n",[7968,43168,43169],{"class":5110,"line":8454},[7968,43170,21497],{"class":7984},[7968,43172,43173],{"class":5110,"line":8472},[7968,43174,8987],{"class":7984},[18,43176,43177,43178,43180,43181,43184],{},"부모(",[32,43179,32723],{},") 는 ",[32,43182,43183],{},":on-confirm=\"handleEmailConfirm\""," 로 비동기 함수만 전달. 성공 toast 만 부모가 띄우고, 에러 toast 는 다이얼로그가 처리.",[76,43186,43188],{"id":43187},"_84-검증","8.4 검증",[81,43190,43191,43198],{},[84,43192,35914,43193,42142,43195,43197],{},[32,43194,11248],{},[32,43196,39656],{}," 401(인증 미들웨어, 토큰 없는 호출) 정상.",[84,43199,43200],{},"다이얼로그: 비밀번호 오타 → 422 → 에러 토스트 + 버튼 활성화 + 재시도 가능. OTP 만료 → 422 → 동일 흐름. 정상 케이스 → 200 → 성공 토스트 + 다이얼로그 닫힘 + 새 이메일 반영.",[76,43202,43204],{"id":43203},"_85-산출물","8.5 산출물",[81,43206,43207,43226,43240],{},[84,43208,43209,47,43211,43213,43214,43216,43217,6685,43219,6219,43222,43225],{},[32,43210,50],{},[32,43212,21753],{}," (unprocessable 추가) \u002F ",[32,43215,22961],{}," (verify-only 분기) \u002F ",[32,43218,32748],{},[32,43220,43221],{},"errors.unauthenticated",[32,43223,43224],{},"errors.unprocessable"," 3 곳).",[84,43227,43228,47,43230,43232,43233,43235,43236,43239],{},[32,43229,7664],{},[32,43231,42604],{}," (prop onConfirm 도입, emit confirm 제거) \u002F ",[32,43234,33421],{}," (handler 시그니처 변경 + ",[32,43237,43238],{},":on-confirm"," 바인딩).",[84,43241,43242,43243,43246,43247,4703],{},"배포: Workers Version ",[32,43244,43245],{},"772adb3b-cb30-4e43-b539-7d60254c6195"," \u002F Pages alias ",[32,43248,43249],{},"cd6a5bb3.malgn-noti.pages.dev",[76,43251,43253],{"id":43252},"_86-알려진-한계-후속","8.6 알려진 한계 \u002F 후속",[81,43255,43256,43266],{},[84,43257,43258,43259,4473,43262,43265],{},"비슷한 본인 재인증 라우트(예정: ",[32,43260,43261],{},"\u002Fme\u002Fpassword",[32,43263,43264],{},"\u002Fme\u002Fsecurity"," 2FA)도 같은 패턴 적용 — 비밀번호·OTP 오류는 401 이 아닌 422 로 응답.",[84,43267,43268,43270],{},[32,43269,39645],{}," 가 purpose 별로 consume 정책이 다른 분기를 갖게 됐다. 다른 흐름(예: 휴대폰 변경)이 추가되면 동일 패턴 확장 검토.",[73,43272],{},[11,43274,43276],{"id":43275},"_9-광고성-메일-수신-동의거부-일시-기록-ddl-0006-workers-version-3671ce95-pages-alias-0f43b158","§9. 광고성 메일 수신 동의\u002F거부 일시 기록 (DDL 0006 \u002F Workers Version 3671ce95 \u002F Pages alias 0f43b158)",[76,43278,27637],{"id":34087},[18,43280,43281,43282,43284],{},"정보통신망법 50조에 따라 광고성 정보 수신 동의는 동의·거부 일시를 보관해야 한다. ",[32,43283,39688],{}," 신규 컬럼 + 사용자 의사 표시 시점마다 갱신 + 사용자단 패널에 마지막 변경 시각 노출 + 토스트에 처리 일시 표기.",[76,43286,43288],{"id":43287},"_91-ddl-0006_company_ad_receive_atsql","9.1 DDL (0006_company_ad_receive_at.sql)",[5251,43290,43292],{"className":31016,"code":43291,"language":31018,"meta":4208,"style":4208},"ALTER TABLE TB_COMPANY\n  ADD COLUMN ad_receive_at DATETIME NULL AFTER ad_receive;\n",[32,43293,43294,43298],{"__ignoreMap":4208},[7968,43295,43296],{"class":5110,"line":7970},[7968,43297,33623],{},[7968,43299,43300],{"class":5110,"line":4212},[7968,43301,43302],{},"  ADD COLUMN ad_receive_at DATETIME NULL AFTER ad_receive;\n",[81,43304,43305,43311],{},[84,43306,43307,43308,4703],{},"Aurora 직결로 적용 + 컬럼 확인: ",[32,43309,43310],{},"ad_receive_at | datetime | YES | | NULL",[84,43312,43313],{},"기존 행은 모두 NULL — 의사 표시 이력이 없다는 의미. 다음 변경 시점부터 기록.",[76,43315,43317],{"id":43316},"_92-api-변경","9.2 API 변경",[81,43319,43320,43328,43340],{},[84,43321,43322,89,43324,43327],{},[32,43323,21654],{},[32,43325,43326],{},"adReceiveAt: datetime('ad_receive_at')"," nullable.",[84,43329,43330,51,43332,89,43334,43336,43337,43339],{},[32,43331,32748],{},[32,43333,32700],{},[32,43335,39694],{}," 가 있으면 ",[32,43338,39698],{}," 동시 set. 같은 값으로 다시 눌러도 의사 표시 갱신으로 간주해 시각 갱신.",[84,43341,43342,43345,43346,4420,43348,4420,43350,4420,43352,43354,43355,43358],{},[32,43343,43344],{},"readContext"," + 4 개 응답 라우트(",[32,43347,21820],{},[32,43349,32697],{},[32,43351,32700],{},[32,43353,39621],{},") 모두 ",[32,43356,43357],{},"company.adReceiveAt"," 포함.",[76,43360,43362],{"id":43361},"_93-사용자단-변경","9.3 사용자단 변경",[81,43364,43365,43373],{},[84,43366,43367,89,43369,43372],{},[32,43368,28955],{},[32,43370,43371],{},"AuthCompany.adReceiveAt?: string | null"," 타입 추가.",[84,43374,43375,9194,43377],{},[32,43376,33421],{},[81,43378,43379,43389,43397,43407],{},[84,43380,43381,43384,43385,43388],{},[32,43382,43383],{},"adReceiveAtLabel"," computed — ",[32,43386,43387],{},"YYYY.MM.DD HH:mm"," 포맷 (JetBrains Mono \u002F tabular-nums).",[84,43390,43391,43384,43394,4703],{},[32,43392,43393],{},"adReceiveNotice",[32,43395,43396],{},"\"YYYY.MM.DD HH:mm 광고성 메일 수신에 동의\u002F거부함\"",[84,43398,43399,43400,4339,43403,43406],{},"토글 옆에 회색 칩(",[32,43401,43402],{},".ad-stamp",[32,43404,43405],{},"i-lucide-clock-3"," 아이콘)으로 노출. 한 번도 변경한 적 없는 회사(기존 데이터)는 칩 미표시.",[84,43408,43409,43410,14770],{},"토스트 성공 메시지 ",[32,43411,43412],{},"description: 처리 일시: YYYY.MM.DD HH:mm",[76,43414,43416],{"id":43415},"_94-검증","9.4 검증",[81,43418,43419,43426],{},[84,43420,35914,43421,43423,43424,13461],{},[32,43422,3987],{}," 응답에 ",[32,43425,43357],{},[84,43427,43428,43429,43431],{},"패널에서 수신동의\u002F거부 토글 → 토스트에 일시 + 칩에 새 일시 즉시 반영 (",[32,43430,32979],{}," 가 응답으로 store hydrate).",[76,43433,35394],{"id":35393},[81,43435,43436,43448,43456],{},[84,43437,43438,47,43440,7505,43443,7505,43445,43447],{},[32,43439,50],{},[32,43441,43442],{},"src\u002Fdb\u002Fmigrations\u002F0006_company_ad_receive_at.sql",[32,43444,21654],{},[32,43446,32748],{}," (응답 4 곳 + UPDATE 1 곳).",[84,43449,43450,47,43452,7505,43454,4703],{},[32,43451,7664],{},[32,43453,28955],{},[32,43455,33421],{},[84,43457,43242,43458,43246,43461,4703],{},[32,43459,43460],{},"3671ce95-be05-4ba0-8611-60bd168e7b80",[32,43462,43463],{},"0f43b158.malgn-noti.pages.dev",[76,43465,43467],{"id":43466},"_96-알려진-한계-후속","9.6 알려진 한계 \u002F 후속",[81,43469,43470,43481,43484],{},[84,43471,43472,43473,43476,43477,43480],{},"의사 표시 ",[21,43474,43475],{},"마지막 시점","만 보관 — 이력(언제 동의→거부→동의→…)은 미. 광고성 메시지 발송 분쟁 시 시점 증빙용으로 마지막 값만으로도 충분하지만, 운영 진입 시 별도 ",[32,43478,43479],{},"TB_AD_RECEIVE_LOG"," 도입 검토.",[84,43482,43483],{},"IP·User-Agent 등 의사 표시 환경 정보는 미기록. 분쟁 대응 강화 시 추가.",[84,43485,43486,43487,43490],{},"광고 수신 거부자에 대한 발송 차단 로직(쿼리 조건)은 이번 변경 범위 밖. ",[32,43488,43489],{},"dispatch"," producer 측에서 별도 처리.",[73,43492],{},[76,43494,43496],{"id":43495},"한계-다음-단계-오늘-누적","한계 \u002F 다음 단계 (오늘 누적)",[81,43498,43499,43504,43513,43529,43538,43543],{},[84,43500,43501,43503],{},[21,43502,39392],{}," (§1·6\u002F2 §16) — 사용자가 IP 정책 결정 대기. IPv6 대역 등록 또는 검사 OFF.",[84,43505,43506,43509,43510,43512],{},[21,43507,43508],{},"NHN Notification Hub real 전환"," (6\u002F2 §16 + 6\u002F4 §6) — Email ✅ 라이브 검증 통과. SMS는 NHN 콘솔 발신번호 등록 + ",[32,43511,31521],{}," secret 대기. push\u002Frcs\u002Fkakao 어댑터는 Notification Hub로 마이그레이션 미.",[84,43514,43515,43518,43519,4339,43522,4361,43525,43528],{},[21,43516,43517],{},"PG = TossPayments 확정"," (6\u002F4) — ",[32,43520,43521],{},"src\u002Fadapters\u002Fpg\u002Ftoss.ts",[32,43523,43524],{},"TOSS_CLIENT_KEY",[32,43526,43527],{},"TOSS_SECRET_KEY"," + 카드 등록\u002F결제\u002F취소\u002Fwebhook 미구현.",[84,43530,43531,43534,43535,43537],{},[21,43532,43533],{},"운영자단 P0 진입"," — admin 셸·페이지 완성됐으므로 다음 단계는 (a) 운영자 인증·RBAC, (b) ",[32,43536,21053],{}," 백엔드 라우트 신설(특히 사업자 승인 화면 연동), (c) 실 API 연동.",[84,43539,43540,43542],{},[21,43541,40391],{}," (§2.5) — Tunnel 전환 후속 정리.",[84,43544,43545,43548],{},[21,43546,43547],{},"deploy 사고 재발 방지"," (§4.4) — 멀티 레포 deploy 가드 도입 검토.",[8560,43550,43551],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":4208,"searchDepth":4209,"depth":4209,"links":43553},[43554,43555,43556,43557,43558,43559,43560,43561,43562,43563,43564,43565,43566,43567,43568,43569,43570,43571,43572,43573,43574,43575,43576,43577,43578,43579,43580,43581,43582,43583,43584,43585,43586,43587,43588,43589,43590,43591,43592,43593,43595,43596,43597,43598,43599,43600,43601,43602,43603,43604,43605,43606,43607,43608,43609,43610,43611,43612,43613,43614,43615,43616],{"id":10635,"depth":4212,"text":10636},{"id":27636,"depth":4212,"text":27637},{"id":39877,"depth":4212,"text":39878},{"id":39896,"depth":4212,"text":39897},{"id":40071,"depth":4212,"text":40072},{"id":40119,"depth":4212,"text":40120},{"id":40147,"depth":4212,"text":40148},{"id":28262,"depth":4212,"text":27637},{"id":40191,"depth":4212,"text":40192},{"id":40263,"depth":4212,"text":40264},{"id":40286,"depth":4212,"text":40287},{"id":30780,"depth":4212,"text":30781},{"id":40383,"depth":4212,"text":40384},{"id":28712,"depth":4212,"text":27637},{"id":40436,"depth":4212,"text":40437},{"id":40781,"depth":4212,"text":40782},{"id":40845,"depth":4212,"text":40846},{"id":41046,"depth":4212,"text":41047},{"id":41088,"depth":4212,"text":41089},{"id":41120,"depth":4212,"text":41121},{"id":41133,"depth":4212,"text":41134},{"id":41149,"depth":4212,"text":41150},{"id":29444,"depth":4212,"text":27637},{"id":41254,"depth":4212,"text":41255},{"id":41501,"depth":4212,"text":41502},{"id":41773,"depth":4212,"text":41774},{"id":41796,"depth":4212,"text":41797},{"id":41877,"depth":4212,"text":41878},{"id":41899,"depth":4212,"text":41900},{"id":41952,"depth":4212,"text":41953},{"id":42038,"depth":4212,"text":42039},{"id":42105,"depth":4212,"text":42106},{"id":42134,"depth":4212,"text":42135},{"id":42165,"depth":4212,"text":42166},{"id":42189,"depth":4212,"text":42190},{"id":42282,"depth":4212,"text":42283},{"id":31856,"depth":4212,"text":27637},{"id":42364,"depth":4212,"text":42365},{"id":42449,"depth":4212,"text":42450},{"id":42480,"depth":4212,"text":43594},"6.3 POST \u002Fme\u002Femail-change 정책",{"id":42516,"depth":4212,"text":42517},{"id":33395,"depth":4212,"text":33396},{"id":42619,"depth":4212,"text":42620},{"id":32704,"depth":4212,"text":27637},{"id":42657,"depth":4212,"text":42658},{"id":42757,"depth":4212,"text":42758},{"id":42783,"depth":4212,"text":42784},{"id":33481,"depth":4212,"text":27637},{"id":42834,"depth":4212,"text":42835},{"id":42920,"depth":4212,"text":42921},{"id":42988,"depth":4212,"text":42989},{"id":43187,"depth":4212,"text":43188},{"id":43203,"depth":4212,"text":43204},{"id":43252,"depth":4212,"text":43253},{"id":34087,"depth":4212,"text":27637},{"id":43287,"depth":4212,"text":43288},{"id":43316,"depth":4212,"text":43317},{"id":43361,"depth":4212,"text":43362},{"id":43415,"depth":4212,"text":43416},{"id":35393,"depth":4212,"text":35394},{"id":43466,"depth":4212,"text":43467},{"id":43495,"depth":4212,"text":43496},{},"\u002Fhistory\u002Fhistory.20260604",{"title":39604,"description":4208},"history\u002Fhistory.20260604","11pZBKhXlZAMqIh7Ou9dsNSnRiaas8pHcLl-tind5m0",{"id":43623,"title":43624,"body":43625,"description":44491,"extension":4257,"meta":44492,"navigation":4259,"path":44493,"seo":44494,"stem":44495,"__hash__":44496},"docs\u002Fhistory\u002Fhistory.20260605.md","2026-06-05 — malgn-noti-mng 관리 레포 신설: 문서 집약 + Nuxt 문서\u002F이력 브라우저 앱 + 현황판 + Cloudflare 배포",{"type":8,"value":43626,"toc":44478},[43627,43630,43669,43671,43675,43716,43720,43754,43758,43868,43872,43958,43962,44032,44036,44131,44142,44304,44311,44446,44448],[11,43628,43624],{"id":43629},"_2026-06-05-malgn-noti-mng-관리-레포-신설-문서-집약-nuxt-문서이력-브라우저-앱-현황판-cloudflare-배포",[18,43631,43632,43634,43635,43638,43639,11686,43641,43643,43644,43647,43648,9651,43651,43654,43655,43659,43660,43662,43663,43665,43666,43668],{},[21,43633,10636],{},": 맑은노티 프로젝트를 관리할 신규 레포 ",[32,43636,43637],{},"malgn-noti-mng","를 GitHub에 연결하고, ",[32,43640,7664],{},[32,43642,11502],{}," 트리(공통 참조·도메인 정본·작업 이력)를 집약한 뒤, malgn-noti와 ",[21,43645,43646],{},"동일 스택","(Nuxt 3 + Tailwind v4 + Nuxt UI v3) + ",[32,43649,43650],{},"@nuxt\u002Fcontent",[21,43652,43653],{},"문서\u002F이력 브라우저 앱","으로 구현해 Cloudflare Pages에 정적 배포(",[26,43656,43657],{"href":43657,"rel":43658},"https:\u002F\u002Fmalgn-noti-mng.pages.dev",[30],"). 추가로 malgn-noti ",[32,43661,26863],{},"를 읽기 전용 **\"맑은노티 현황판\"(",[32,43664,34],{},")**으로 이식하고 대시보드에 WBS 현황 요약을 추가(공개 API ",[32,43667,54],{}," 조회). 앞으로의 작업 이력은 이 레포에서 작성·갱신한다.",[73,43670],{},[76,43672,43674],{"id":43673},"_1-레포-연결-문서-집약","1. 레포 연결 + 문서 집약",[81,43676,43677,43692,43706],{},[84,43678,43679,43680,43682,43683,43686,43687,43691],{},"빈 ",[32,43681,43637],{}," 폴더를 ",[32,43684,43685],{},"git init","(main) 후 원격 ",[26,43688,43689],{"href":43689,"rel":43690},"https:\u002F\u002Fgithub.com\u002Fmalgnsoft\u002Fmalgn-noti-mng.git",[30]," 연결, 첫 커밋·푸시.",[84,43693,43694,43697,43698,43701,43702,43705],{},[32,43695,43696],{},"malgn-noti\u002Fdoc\u002F"," 트리 전체(24개 md)를 ",[32,43699,43700],{},"malgn-noti-mng\u002Fdoc\u002F","로 복사 — 공통 참조(DESIGN\u002FFRONTEND\u002FSTACK\u002FWBS), 도메인 정본(MEMBERSHIP\u002FNICE_AUTH\u002FPAGES\u002Fpages\u002F",[6818,43703,43704],{},"), 일자별 작업 이력(history\u002F"," 14개 + README).",[84,43707,43708,43709,43712,43713,43715],{},"세션 중 생성된 ",[32,43710,43711],{},".claude\u002F"," 로컬 권한 설정은 문서 레포에 부적합 → ",[32,43714,10935],{}," 처리.",[76,43717,43719],{"id":43718},"_2-claudemd-병합현행화-양쪽-동일-유지","2. CLAUDE.md 병합·현행화 (양쪽 동일 유지)",[81,43721,43722,43735],{},[84,43723,43724,43726,43727,43730,43731,43734],{},[32,43725,7664],{},"의 CLAUDE.md를 기본으로, 관리 레포 설명을 ",[21,43728,43729],{},"§11","로 통합. 두 레포의 CLAUDE.md를 ",[21,43732,43733],{},"항상 동일","하게 유지하는 규칙 명시.",[84,43736,43737,43740,43741,43743,43744,43750,43751,43753],{},[21,43738,43739],{},"작업 이력 작성처 변경(§7.1·§11)",": 앞으로 history는 ",[32,43742,7664],{},"가 아니라 ",[21,43745,43746,43749],{},[32,43747,43748],{},"malgn-noti-mng\u002Fdoc\u002Fhistory\u002F","에서 작성·갱신","하고, 작성 후 이 레포에 커밋·푸시한다. 이에 따라 ",[32,43752,20140],{}," 폴더와 15개 파일은 삭제(사본은 본 레포에 보존).",[76,43755,43757],{"id":43756},"_3-nuxt-문서이력-브라우저-앱-스캐폴딩","3. Nuxt 문서\u002F이력 브라우저 앱 스캐폴딩",[81,43759,43760,43769,43800,43811,43834,43850],{},[84,43761,43762,43765,43766,43768],{},[21,43763,43764],{},"스택",": malgn-noti 미러링 — Nuxt 3(compat v4) + Nuxt UI v3(Reka UI + Tailwind v4) + Pinia + ESLint(@nuxt\u002Feslint), pnpm. ",[32,43767,7782],{}," 미설치 원칙 동일.",[84,43770,43771,47,43774,43776,43777,43780,43781,13746,43784,43786,43787,43790,43791,6071,43794,11686,43796,43799],{},[21,43772,43773],{},"+ @nuxt\u002Fcontent v3",[32,43775,11502],{}," 마크다운 렌더링. ",[32,43778,43779],{},"content.config.ts","에서 소스 ",[32,43782,43783],{},"cwd",[32,43785,11502],{},"로 매핑(단일 ",[32,43788,43789],{},"docs"," 컬렉션). SQLite 어댑터 ",[32,43792,43793],{},"better-sqlite3",[32,43795,10928],{},[32,43797,43798],{},"pnpm.onlyBuiltDependencies","로 네이티브 빌드 허용.",[84,43801,43802,47,43805,43807,43808,43810],{},[21,43803,43804],{},"디자인 시스템 이식",[32,43806,4352],{},"(Relay-inspired v1.0, 1911줄) + ",[32,43809,4392],{},"(zinc)를 malgn-noti에서 그대로 복사 → 형제 앱과 시각 일관성.",[84,43812,43813,47,43815,43817,43818,43821,43822,43825,43826,43829,43830,43833],{},[21,43814,40459],{},[32,43816,4361],{},"(대시보드 — 문서 바로가기 + 최근 이력), ",[32,43819,43820],{},"\u002Fdocs","(문서 목록) + ",[32,43823,43824],{},"\u002Fdocs\u002F[...slug]","(마크다운 렌더, ContentRenderer), ",[32,43827,43828],{},"\u002Fhistory","(작업 이력 타임라인). 공용 훅 ",[32,43831,43832],{},"composables\u002FuseDocs.ts","(history 판별·날짜 포맷).",[84,43835,43836,43837,4536,43839,4536,43841,13923,43843,43846,43847,43849],{},"로컬 검증: 콘텐츠 24개 파싱, ",[32,43838,4361],{},[32,43840,43820],{},[32,43842,43828],{},[32,43844,43845],{},"\u002Fdocs\u002Fdesign"," 등 렌더 확인, ",[32,43848,10434],{}," 통과.",[84,43851,43852,43853,6100,43856,43859,43860,43863,43864,43867],{},"환경 노트: 이 샌드박스에서 macOS 기본 ",[32,43854,43855],{},"$TMPDIR",[32,43857,43858],{},"\u002Fvar\u002Ffolders\u002F...",")가 길어 Nuxt vite-node Unix 소켓이 104자 제한 초과 → ",[32,43861,43862],{},"connect EINVAL"," 500. ",[32,43865,43866],{},"TMPDIR=\u002Ftmp\u002Fmtmp pnpm dev","로 우회(환경 한정 이슈, 설정엔 미반영).",[76,43869,43871],{"id":43870},"_4-cloudflare-pages-정적-배포","4. Cloudflare Pages 정적 배포",[81,43873,43874,43885,43935,43953],{},[84,43875,43876,43877,43884],{},"@nuxt\u002Fcontent(better-sqlite3)는 Cloudflare 런타임 DB가 없으므로 ",[21,43878,43879,43880,43883],{},"정적 프리렌더(",[32,43881,43882],{},"nuxt generate",") → Pages"," 방식 채택. nitro 프리렌더로 전 페이지 HTML 생성.",[84,43886,43887,43888],{},"프리렌더 이슈 2건 해결:\n",[6674,43889,43890,43914],{},[84,43891,43892,43895,43896,4536,43899,4536,43902,43905,43906,51,43909,43911,43912,5606],{},[21,43893,43894],{},"마크다운 내부 상대 링크 크롤",": 문서의 ",[32,43897,43898],{},".\u002FFRONTEND.md",[32,43900,43901],{},"..\u002F..\u002Fapp\u002F...",[32,43903,43904],{},".\u002FCLAUDE.md"," 등 원본 레포 기준 링크를 크롤러가 따라가 404 → 빌드 실패. ",[21,43907,43908],{},"crawlLinks 끄고",[32,43910,11502],{}," 트리를 재귀 순회해 정규 라우트만 직접 열거(",[32,43913,4405],{},[84,43915,43916,43919,43920,43923,43924,43926,43927,43930,43931,43934],{},[21,43917,43918],{},"대소문자 구분",": macOS(케이스 무시)에선 안 보였으나, 크롤이 만든 ",[32,43921,43922],{},"docs\u002FDESIGN\u002F"," 대문자 디렉터리 때문에 Cloudflare(케이스 구분)에서 소문자 ",[32,43925,43845],{},"이 404. 라우트를 ",[21,43928,43929],{},"전부 소문자로 정규화","해 해결. 파일명 점(",[32,43932,43933],{},"history.20260604",")으로 크롤러가 라우트를 건너뛰던 문제도 같은 열거 방식으로 회피.",[84,43936,43937,43938,43940,43941,43944,43945,43948,43949,43952],{},"Pages 프로젝트 ",[32,43939,43637],{}," 생성(production-branch=main) 후 ",[32,43942,43943],{},".output\u002Fpublic"," 배포. 재배포로 57개 정규 라우트 전부 라이브 200 확인(",[32,43946,43947],{},"\u002Fdocs\u002F*"," 9개 문서 + ",[32,43950,43951],{},"\u002Fdocs\u002Fhistory\u002F*"," 16개 이력 + 인덱스).",[84,43954,43955,43956,5606],{},"인증: wrangler OAuth(",[26,43957,11171],{"href":11170},[76,43959,43961],{"id":43960},"_5-현황판board-대시보드-wbs-현황-요약","5. 현황판(\u002Fboard) + 대시보드 WBS 현황 요약",[81,43963,43964,43977,43991,44008,44018,44026],{},[84,43965,43966,43967,43969,43970,43973,43974,43976],{},"malgn-noti ",[32,43968,26863],{}," 페이지를 ",[32,43971,43972],{},"malgn-noti-mng\u002Fboard","에 **\"맑은노티 현황판\"**으로 이식. mng는 인증\u002F편집이 없는 정적 사이트이므로 ",[21,43975,34593],{},"(편집 모달·auth·toast 제거), 자체 sticky 헤더 대신 mng 기본 레이아웃(GNB) 사용.",[84,43978,43979,43980,6100,43982,43985,43986,43988,43989,14770],{},"데이터는 공개 API ",[32,43981,54],{},[32,43983,43984],{},"malgn-noti-api.malgnsoft.workers.dev",")를 ",[32,43987,8689],{},"로 조회 — 프리렌더 시 빌드 타임에 베이크 + 클라이언트에서 라이브 갱신. ",[32,43990,29105],{},[84,43992,43993,43994,43997,43998,44001,44002,44004,44005,44007],{},"공용화: ",[32,43995,43996],{},"composables\u002FuseWbs.ts","(조회 + 가중평균·상태 카운트·날짜 포맷·그룹화) + ",[32,43999,44000],{},"components\u002FAppWbsOverview.vue","(hero stats 3종 + 단계별 진행률 리스트). 대시보드(",[32,44003,4361],{},")와 현황판(",[32,44006,34],{},")이 동일 컴포넌트 공유.",[84,44009,44010,44013,44014,44017],{},[21,44011,44012],{},"대시보드에 \"프로젝트 현황\" 요약 추가","(첨부 이미지 내용 — 전체 진행률·완료·진행 중·단계별 진행률) + GNB에 \"현황판\" 링크. 개요 행 클릭 → ",[32,44015,44016],{},"\u002Fboard#stage-\u003Cid>"," 상세로 이동.",[84,44019,44020,44022,44023,44025],{},[32,44021,34],{}," 프리렌더 라우트 추가. 라이브 검증: ",[32,44024,34],{}," 200, \"맑은노티 현황판\"·Step 1~5·47.5%\u002F55%\u002F35% 베이크 확인, 대시보드 현황 요약 노출.",[84,44027,44028,44031],{},[21,44029,44030],{},"WBS 문서 페이지는 별도"," — 현황판은 진행률 뷰이고, WBS 정본 문서는 추후 별도 구성.",[76,44033,44035],{"id":44034},"_6-boardmd-신설-step-5-세부-항목-단순화-재정의","6. BOARD.md 신설 — Step 5 세부 항목 단순화 재정의",[81,44037,44038,44056,44070,44073,44079,44102,44119],{},[84,44039,44040,44041,44043,44044,44047,44048,44055],{},"현황판 Step 5(92개 세부 task)를 ",[21,44042,3818],{},"한 정본 ",[32,44045,44046],{},"doc\u002FBOARD.md"," 작성. 문서 뷰어에서 ",[26,44049,44052],{"href":44050,"rel":44051},"https:\u002F\u002Fmalgn-noti-mng.pages.dev\u002Fdocs\u002Fboard",[30],[32,44053,44054],{},"\u002Fdocs\u002Fboard","로 노출.",[84,44057,44058,44059,44062,44063,44062,44066,44069],{},"대상 4개 영역 재정의: ",[21,44060,44061],{},"API 서버 + API 엔드포인트 → \"API 백엔드\"로 통합","(6 카테고리), ",[21,44064,44065],{},"사용자단 ↔ API 연동",[21,44067,44068],{},"관리자단 화면","(6 카테고리). 각 카테고리에 종합 상태(✅\u002F🟡\u002F⚪) + 기존 task id 매핑(추적용).",[84,44071,44072],{},"그 외 영역(설계·준비, 사용자단 목업 UI, 통합·배포)은 현 상태만 참고 표로 기록.",[84,44074,44075,44078],{},[21,44076,44077],{},"WBS 상세(작업 단위·일정·의존성)는 간트 차트로 별도 구성 예정"," — BOARD.md는 카테고리 단위 현황 정본, 세부 진척은 향후 간트 WBS가 정본.",[84,44080,44081,44087,44088,44090,44091,44094,44095,4361,44098,44101],{},[21,44082,44083,44084,44086],{},"현황판(",[32,44085,34],{},") 페이지도 BOARD.md 렌더로 전환"," — 기존 공개 API ",[32,44089,54],{},"(옛 92항목 상세) 기반에서 BOARD.md(단순화 카테고리) ",[32,44092,44093],{},"ContentRenderer"," 렌더로 교체해 정본 일원화. ",[32,44096,44097],{},"useWbs",[32,44099,44100],{},"AppWbsOverview"," 의존 제거(대시보드 현황 요약은 그대로 유지).",[84,44103,44104,44110,44111,6071,44113,44115,44116,44118],{},[21,44105,44106,44107,44109],{},"(후속) ",[32,44108,34],{},"는 원래 시각적 보드로 되돌림"," — 사용자가 시각적 현황판을 선호. ",[32,44112,34],{},[32,44114,54],{}," 기반 보드(전체 진행률 + 단계 행 + 상세) 유지, BOARD.md 정본은 ",[32,44117,44054],{},"로 분리.",[84,44120,44121,44124,44125,44127,44128,44130],{},[21,44122,44123],{},"(후속) BOARD.md를 현황판 전체 미러로 현행화"," — WBS.md처럼 현황판의 ",[21,44126,38],{},"(진행률 스냅샷 + 전체 5단계 155 태스크 상세 표 + Step 5 단순화 카테고리 부록)을 담도록 ",[32,44129,54],{}," 라이브 데이터에서 생성. 데이터 기준 2026-06-04 \u002F 문서 현행화 2026-06-05.",[76,44132,44134,44135,44138,44139,44141],{"id":44133},"_7-현황판-데이터-자립-자체-d1-apiboard-외부-get-wbs-의존-제거","7. 현황판 데이터 자립 — 자체 D1 + ",[32,44136,44137],{},"\u002Fapi\u002Fboard"," (외부 ",[32,44140,54],{}," 의존 제거)",[81,44143,44144,44167,44194,44214,44238,44245,44259],{},[84,44145,44146,44147,44149,44150,44152,44153,6100,44156,44159,44160,44163,44164,44166],{},"외부 ",[32,44148,50],{},"의 공개 ",[32,44151,54],{}," 의존을 끊고, ",[21,44154,44155],{},"malgn-noti-mng 자체 Cloudflare D1",[32,44157,44158],{},"malgn-noti-project",", id ",[32,44161,44162],{},"3c8c37e3…",") + 내부 API ",[32,44165,44137],{},"로 전환.",[84,44168,44169,6100,44172,24937,44175,44178,44179,44182,44183,44186,44187,6100,44190,44193],{},[21,44170,44171],{},"스키마",[32,44173,44174],{},"server\u002Fdb\u002Fschema.sql",[32,44176,44177],{},"board_meta","(프로젝트·현행화일) + ",[32,44180,44181],{},"stage","(단계) + ",[32,44184,44185],{},"task","(작업) 3 테이블. ",[21,44188,44189],{},"시드",[32,44191,44192],{},"server\u002Fdb\u002Fseed.sql","): 5단계 115 태스크(Step 5는 단순화 카테고리 반영) — 원격 D1에 적용.",[84,44195,44196,51,44199,44202,44203,44205,44206,44209,44210,44213],{},[21,44197,44198],{},"서버 라우트",[32,44200,44201],{},"server\u002Fapi\u002Fboard.get.ts",": D1(",[32,44204,10745],{}," 바인딩) 조회 → ",[32,44207,44208],{},"{ data: WbsDocument }"," 조립. D1 없는 로컬 dev는 ",[32,44211,44212],{},"server\u002Futils\u002FboardSeed.ts"," 폴백.",[84,44215,44216,44219,44220,6219,44222,5439,44225,4473,44228,44230,44231,7086,44233,4536,44235,44237],{},[21,44217,44218],{},"배포 전환",": 정적 ",[32,44221,43882],{},[21,44223,44224],{},"Cloudflare Pages Functions(SSR)",[32,44226,44227],{},"nitro.preset='cloudflare-pages'",[32,44229,11253],{},"에 D1 바인딩(",[32,44232,10745],{},[32,44234,4361],{},[32,44236,34],{},"는 런타임 D1 조회(SSR), 문서·이력은 프리렌더 유지.",[84,44239,44240,6071,44242,44244],{},[32,44241,44097],{},[32,44243,44137],{}," 조회로 변경(외부 baseURL·클라이언트 단순화 로직 제거 — 단순화는 D1 시드에 반영).",[84,44246,13284,44247,44249,44250,44253,44254,4536,44256,44258],{},[32,44248,44137],{}," 200 + D1 값 변경(",[32,44251,44252],{},"last_updated",")이 즉시 반영됨을 확인(폴백 아님). ",[32,44255,4361],{},[32,44257,34],{},"·문서·이력 전부 200.",[84,44260,44261,44264,44265,44268,44269,44272,44273,6100,44276,44279,44280,44283,44284,6100,44287,44290,44291,44294,44295,4361,44297,4361,44300,44303],{},[21,44262,44263],{},"(후속) Drizzle ORM 도입"," — 브라우저↔D1 직결은 불가(서버 한 겹 필수)임을 정리하고, raw SQL 대신 ",[21,44266,44267],{},"Drizzle","로 D1 직접 쿼리. ",[32,44270,44271],{},"server\u002Fdb\u002Fschema.ts","(스키마 정본) + ",[32,44274,44275],{},"server\u002Futils\u002Fdb.ts",[32,44277,44278],{},"useDb()"," 공용) + ",[32,44281,44282],{},"board.get.ts"," 리팩터. 마이그레이션은 ",[32,44285,44286],{},"drizzle-kit",[32,44288,44289],{},"server\u002Fdb\u002Fmigrations\u002F",")으로 일원화(수기 ",[32,44292,44293],{},"schema.sql"," 제거). ",[32,44296,20543],{},[32,44298,44299],{},"db:apply",[32,44301,44302],{},"db:seed"," 스크립트 추가.",[76,44305,44307,44308,44310],{"id":44306},"_8-간트-wbs-신규wbs-엑셀-스타일","8. 간트 WBS 신규(",[32,44309,26863],{},") — 엑셀 스타일",[81,44312,44313,44322,44329,44339,44342,44352,44366,44379,44396,44421,44433],{},[84,44314,44315,44316,13746,44319,44321],{},"별도 예고했던 ",[21,44317,44318],{},"간트 차트 WBS",[32,44320,26863],{},"로 신규 구현(엑셀 스타일 그리드 + 일 단위 간트).",[84,44323,44324,44325,44328],{},"대상: ",[21,44326,44327],{},"Step 1 · 3 · 5",", 화면 단위로 최대 분해(이미지의 Step 5 화면 항목 + Step 1·3 항목, 총 61개).",[84,44330,44331,44332,4420,44335,44338],{},"컬럼: 구분(병합 표시) · 작업(화면) · 담당 · ",[21,44333,44334],{},"시작일",[21,44336,44337],{},"종료일","(기존 목표일) · 진척율. 좌측 열은 가로 스크롤 시 고정(sticky).",[84,44340,44341],{},"간트: 시작일~종료일에 해당하는 날짜 칸을 자동 채색(진척율만큼 완료=진한 초록, 잔여=연한 초록), 주말(토·일) 칸 적색 틴트 + 오늘(2026-06-05) 마커. 월 헤더 + 일\u002F요일 헤더.",[84,44343,44344,44345,44348,44349,44351],{},"데이터는 정적 모듈(",[32,44346,44347],{},"app\u002Futils\u002FwbsData.ts",")이라 프리렌더. 시작일은 작업 기간 기준 부여(추정), 종료일·진척율은 소스(이미지\u002F보드) 기준. GNB에 ",[32,44350,27349],{}," 메뉴 추가.",[84,44353,44354,47,44357,44359,44360,44363,44364,5606],{},[21,44355,44356],{},"신규 레포",[32,44358,43637],{}," (GitHub ",[32,44361,44362],{},"malgnsoft\u002Fmalgn-noti-mng",", branch ",[32,44365,12995],{},[84,44367,44368,47,44371,44374,44375,44378],{},[21,44369,44370],{},"프로덕션",[26,44372,43657],{"href":43657,"rel":44373},[30]," (배포마다 ",[32,44376,44377],{},"https:\u002F\u002F\u003Cid>.malgn-noti-mng.pages.dev"," alias).",[84,44380,44381,44383,44384,44387,44388,44391,44392,44395],{},[21,44382,16672],{},": 문서 집약 ",[32,44385,44386],{},"0db385a"," · history 작성처 변경 ",[32,44389,44390],{},"bebb5b8"," · 앱 스캐폴딩 ",[32,44393,44394],{},"5ac81c6"," · 프리렌더 대소문자 fix(이번).",[84,44397,44398,47,44401,44403,44404,44406,44407,4473,44410,4473,44412,4473,44415,4473,44418,44420],{},[21,44399,44400],{},"주요 파일",[32,44402,4405],{},"(프리렌더 라우트 열거 + apiBaseUrl), ",[32,44405,43779],{},"(doc\u002F 매핑), ",[32,44408,44409],{},"app\u002Fpages\u002F{index,board,docs\u002Findex,docs\u002F[...slug],history\u002Findex}.vue",[32,44411,24750],{},[32,44413,44414],{},"app\u002Fcomposables\u002F{useDocs,useWbs}.ts",[32,44416,44417],{},"app\u002Fcomponents\u002FAppWbsOverview.vue",[32,44419,10928],{},"(@nuxt\u002Fcontent + better-sqlite3 + pnpm.onlyBuiltDependencies).",[84,44422,44423,47,44426,44429,44430,44432],{},[21,44424,44425],{},"현황판",[26,44427,28],{"href":28,"rel":44428},[30]," (맑은노티 현황판, 읽기 전용, ",[32,44431,54],{}," 라이브).",[84,44434,44435,47,44438,44443,44444,5606],{},[21,44436,44437],{},"현황판 정본 문서",[26,44439,44441],{"href":44440},"..\u002FBOARD",[32,44442,44046],{}," — Step 5 세부 항목 단순화 재정의 (",[32,44445,44054],{},[76,44447,12598],{"id":13153},[81,44449,44450,44460,44463,44472],{},[84,44451,44452,44453,44455,44456,44459],{},"문서 마크다운 내부의 원본 레포 상대 링크(",[32,44454,43898],{}," 등)는 이 앱에서 클릭 시 404 — 앱 라우트(",[32,44457,44458],{},"\u002Fdocs\u002F...",")로 재작성하거나 무효화 필요.",[84,44461,44462],{},"문서 상세에 좌측 TOC\u002F사이드바, 전체 검색 미구현.",[84,44464,44465,44466,44468,44469,44471],{},"콘텐츠 현행화는 수동 복사(§11 규칙) — ",[32,44467,43696],{}," 갱신 시 ",[32,44470,43700],{},"로 동기화하는 절차 자동화 미정.",[84,44473,44474,44475,44477],{},"배포는 로컬 ",[32,44476,43882],{}," 산출물 수동 업로드 — CI(예: GitHub 연동 자동 빌드) 미설정.",{"title":4208,"searchDepth":4209,"depth":4209,"links":44479},[44480,44481,44482,44483,44484,44485,44486,44488,44490],{"id":43673,"depth":4212,"text":43674},{"id":43718,"depth":4212,"text":43719},{"id":43756,"depth":4212,"text":43757},{"id":43870,"depth":4212,"text":43871},{"id":43960,"depth":4212,"text":43961},{"id":44034,"depth":4212,"text":44035},{"id":44133,"depth":4212,"text":44487},"7. 현황판 데이터 자립 — 자체 D1 + \u002Fapi\u002Fboard (외부 GET \u002Fwbs 의존 제거)",{"id":44306,"depth":4212,"text":44489},"8. 간트 WBS 신규(\u002Fwbs) — 엑셀 스타일",{"id":13153,"depth":4212,"text":12598},"한 줄 요약: 맑은노티 프로젝트를 관리할 신규 레포 malgn-noti-mng를 GitHub에 연결하고, malgn-noti의 doc\u002F 트리(공통 참조·도메인 정본·작업 이력)를 집약한 뒤, malgn-noti와 동일 스택(Nuxt 3 + Tailwind v4 + Nuxt UI v3) + @nuxt\u002Fcontent 기반 문서\u002F이력 브라우저 앱으로 구현해 Cloudflare Pages에 정적 배포(https:\u002F\u002Fmalgn-noti-mng.pages.dev). 추가로 malgn-noti \u002Fwbs를 읽기 전용 **\"맑은노티 현황판\"(\u002Fboard)**으로 이식하고 대시보드에 WBS 현황 요약을 추가(공개 API GET \u002Fwbs 조회). 앞으로의 작업 이력은 이 레포에서 작성·갱신한다.",{},"\u002Fhistory\u002Fhistory.20260605",{"title":43624,"description":44491},"history\u002Fhistory.20260605","OKAZdTTg_ULqwtqD-oKXV-M0UvuQqYJkfLidG7MC3to",{"id":44498,"title":44499,"body":44500,"description":45538,"extension":4257,"meta":45539,"navigation":4259,"path":45540,"seo":45541,"stem":45542,"__hash__":45543},"docs\u002Fhistory\u002FREADME.md","작업 이력 (doc\u002Fhistory\u002F)",{"type":8,"value":44501,"toc":45531},[44502,44508,44515,44517,44523,44530,44534,44537,44557,44560,45479,45483,45491,45494,45499,45528],[11,44503,44505,44506,4343],{"id":44504},"작업-이력-dochistory","작업 이력 (",[32,44507,12440],{},[18,44509,44510,44511,44514],{},"날짜별 작업 내역을 누적해 두는 폴더입니다. ",[21,44512,44513],{},"하루에 한 파일"," 원칙.",[76,44516,12560],{"id":12559},[5251,44518,44521],{"className":44519,"code":44520,"language":5256},[5254],"history.yyyyMMdd.md\n",[32,44522,44520],{"__ignoreMap":4208},[18,44524,44525,44526,44529],{},"예: ",[32,44527,44528],{},"history.20260514.md",". 작업이 있는 날만 생성합니다.",[76,44531,44533],{"id":44532},"파일-구조-공통","파일 구조 (공통)",[18,44535,44536],{},"각 파일은 다음 순서를 따릅니다.",[6674,44538,44539,44543,44548,44553],{},[84,44540,44541,12582],{},[21,44542,10636],{},[84,44544,44545,44547],{},[21,44546,12587],{}," — 결정 사항 \u002F 코드 변경 \u002F 배포 \u002F 미세 조정",[84,44549,44550,44552],{},[21,44551,10916],{}," — 추가·수정된 파일, 배포 버전, 외부 URL",[84,44554,44555],{},[21,44556,12598],{},[76,44558,44559],{"id":44559},"인덱스",[101,44561,44562,44570],{},[104,44563,44564],{},[107,44565,44566,44568],{},[110,44567,7522],{},[110,44569,124],{},[126,44571,44572,44619,44630,44648,44662,44676,44688,44701,44711,44722,44733,44795,44954,45228,45367],{},[107,44573,44574,44580],{},[131,44575,44576],{},[26,44577,44579],{"href":44578},".\u002Fhistory.20260605","2026-06-05",[131,44581,44582,44585,44586,44588,44589,44591,44592,9651,44594,44596,44597,6100,44600,44603,44604,44609,44610,44612,44613,44615,44616,44618],{},[21,44583,44584],{},"malgn-noti-mng 관리 레포 신설"," — GitHub 연결 + ",[32,44587,43696],{}," 트리 집약(공통·도메인·이력) + CLAUDE.md 병합·현행화(이력 작성처를 본 레포로 변경, malgn-noti ",[32,44590,12440],{}," 삭제) + malgn-noti 동일 스택(Nuxt 3 + Tailwind v4 + Nuxt UI v3) + ",[32,44593,43650],{},[21,44595,43653],{}," 스캐폴딩(대시보드·문서·이력 타임라인) + ",[21,44598,44599],{},"Cloudflare Pages 정적 배포",[26,44601,43657],{"href":43657,"rel":44602},[30],", 프리렌더 라우트 직접 열거로 마크다운 내부 링크 크롤·대소문자·파일명 점 이슈 해결) + ",[21,44605,44606,44607,4343],{},"맑은노티 현황판(",[32,44608,34],{}," — malgn-noti ",[32,44611,26863],{}," 읽기 전용 이식(공개 API ",[32,44614,54],{}," 라이브) + 대시보드 WBS 현황 요약(",[32,44617,44100],{}," 공유)",[107,44620,44621,44627],{},[131,44622,44623],{},[26,44624,44626],{"href":44625},".\u002Fhistory.20260511","2026-05-11",[131,44628,44629],{},"프로젝트 착수 — 세 레포 부트스트랩 + 첫 GitHub 푸시 + Cloudflare 첫 배포 + 시안 GNB 도입",[107,44631,44632,44638],{},[131,44633,44634],{},[26,44635,44637],{"href":44636},".\u002Fhistory.20260512","2026-05-12",[131,44639,44640,44641,4361,44643,4361,44646,4343],{},"시안 정밀 매칭 — 콘텐츠 폭 1200px 통일, Utility Bar 분리, 메뉴 8개, 문서 3종(",[32,44642,10623],{},[32,44644,44645],{},"STACK",[32,44647,7646],{},[107,44649,44650,44656],{},[131,44651,44652],{},[26,44653,44655],{"href":44654},".\u002Fhistory.20260513","2026-05-13",[131,44657,44658,44659,44661],{},"Smart Placement + 발송 페이지 풍부화 — 공용 컴포넌트 21종, ",[32,44660,12370],{},", SMS\u002F알림톡 발송 페이지",[107,44663,44664,44670],{},[131,44665,44666],{},[26,44667,44669],{"href":44668},".\u002Fhistory.20260514","2026-05-14",[131,44671,44672,44673,44675],{},"GitHub 일괄 푸시 + ",[32,44674,12440],{}," 폴더 정리",[107,44677,44678,44683],{},[131,44679,44680],{},[26,44681,7542],{"href":44682},".\u002Fhistory.20260518",[131,44684,44685,44686,15283],{},"디자인 시스템 전면 피벗 — Relay-inspired(핸드오프 정본), 셸+홈+발송 전 채널 적용, ",[32,44687,12683],{},[107,44689,44690,44695],{},[131,44691,44692],{},[26,44693,7555],{"href":44694},".\u002Fhistory.20260519",[131,44696,44697,44698,44700],{},"디자인 가이드 페이지(",[32,44699,4457],{},", 18섹션 라이브 카탈로그) + Cloudflare Pages 프로덕션 배포",[107,44702,44703,44708],{},[131,44704,44705],{},[26,44706,7566],{"href":44707},".\u002Fhistory.20260520",[131,44709,44710],{},"발송 페이지 UX 폴리시 2차 + PUSH 부가항목·플로우 관리 + 문서 현행화 + 발송 조회 페이지 재작업·btn-sky 제거 + 통계 페이지 재구성(Chart.js)·폰트 토큰화·zoom 제거 + 발송 템플릿 토글 개선 + 주소록 관리 페이지 강화 + 발신 번호 관리 페이지·등록 마법사 + 그룹 관리 페이지 + RCS 브랜드 관리 페이지 + 이메일 도메인 관리 페이지 (배포 #15~#26)",[107,44712,44713,44719],{},[131,44714,44715],{},[26,44716,44718],{"href":44717},".\u002Fhistory.20260521","2026-05-21",[131,44720,44721],{},"발신 정보 페이지 마무리(발신 프로필·PUSH 인증·080 수신 거부) + 테이블 스타일 A\u002FB\u002FC 정의·발송 조회 툴바 재배치 + GNB 정리 + 수신 거부 관리 3종 + 문자메시지·알림톡 템플릿 관리 페이지 + 미리보기 시각 현재화 + 로그인 페이지 시안 IA 재구성 + 비밀번호 재설정 입력란 보정 + 메시지 관리 5채널 템플릿 페이지(SMS·알림톡·RCS·이메일·PUSH) + 보안 인증 페이지 보정 + 사이트맵 페이지 + 회원가입 5단계 마법사 + 메시지 상세 설정 페이지 + 새 비밀번호 설정 페이지 보정 + 랜딩페이지 만들기 + 문의하기 페이지 \u002Faccount\u002Finquiry 이동 + 나의 페이지 섹션 + 크레딧 충전 플로우 + API 루트 → \u002Fdoc 리다이렉트 (배포 #27~#46 + API)",[107,44723,44724,44730],{},[131,44725,44726],{},[26,44727,44729],{"href":44728},".\u002Fhistory.20260522","2026-05-22",[131,44731,44732],{},"나의 페이지 화면 신규 구성 — 비밀번호 변경·보안로그인 설정·멀티 계정 추가·계약 관리(계약서 확인·3스텝 전자서명 위저드·PDF 미리보기) + 크레딧 관리(A형 내역 테이블·영수증 모달) + 나의 문의(목록·상세 댓글 스레드)·문의하기 LNB + 서비스 담당자 초대 플로우(\u002Finvite 등록 페이지)·문의 등록 완료 페이지 + 사이트맵 현행화 + 운영 가이드 페이지(\u002Fhelp)·GNB 연결 + 비로그인 공개 랜딩 페이지(\u002F, 히어로·장점·채널 단가 비교) (배포 #47~#52)",[107,44734,44735,44741],{},[131,44736,44737],{},[26,44738,44740],{"href":44739},".\u002Fhistory.20260526","2026-05-26",[131,44742,44743,44745,44746,44749,44750,44752,44753,44755,44756,44758,44759,44761,44762,4536,44764,4536,44767,44770,44771,44773,44774,44777,44778,44781,44782,6219,44785,44788,44789,44791,44792,44794],{},[32,44744,50],{}," 데이터 모델·초기 DDL·Hyperdrive 연결·첫 프로덕션 배포 — 49 테이블 데이터 모델(TB_·company_id·status INT 1\u002F0\u002F-1 + ",[6818,44747,44748],{},"_state·","_yn·loginid\u002Femail 분리) + Mermaid ERD 9종 + 확장성 전략(월 RANGE 파티셔닝·Hot\u002FWarm\u002FCold·R2 오프로드) + 0000_initial.sql(49 테이블, 파티션 5종) + Hyperdrive(",[32,44751,39727],{},") 바인딩 + drizzle-orm\u002Fmysql2 + \u002Fhealth\u002Fdb + ",[32,44754,20120],{},"로 로컬도 실제 Aurora + ",[32,44757,11267],{}," 프로덕션 첫 배포(\u002Fhealth\u002Fdb → mysql_version 8.0.42) + ",[32,44760,20133],{},"에 배포·Git·작업 이력 운영 컨벤션 명문화 + malgn-noti 프론트 배포 #53 (list 툴바 새로고침 버튼 일괄 제거 + guide\u002FDESIGN 갱신) + ",[32,44763,21708],{},[32,44765,44766],{},"\u002Fadmin\u002Ftables",[32,44768,44769],{},"\u002Fadmin\u002Fpartitions"," 라우트 + 0000_initial.sql Aurora 적용 (49 테이블 + 75 파티션 라이브) + 기본 CRUD API 골격 (errors\u002Fpagination\u002Fauth\u002FDrizzle schema + \u002Fme \u002Fcontacts \u002Fcontact-groups \u002Fsender-phones) + ",[32,44772,16900],{}," API 가이드(Scalar UI + OpenAPI 3.1) + malgn-noti-api 프로덕션 배포 #2 (Version ",[32,44775,44776],{},"1fdc3b12...",", 가드 작동 확인) + 14개 도메인 라우트 추가(발신정보 5종\u002Foptout-entries\u002Ftemplates 2종\u002Finquiries\u002Fcompany-settings\u002Fpayment-methods\u002Flanding-pages\u002Fcredit-ledger) + Phase 1·2·3 (\u002Fdoc 확장 paths 10→37·schemas 11→45 + signup\u002Flogin + PBKDF2 + JWT HS256 + 발송 이력 read-only with 90일 윈도우 강제) + malgn-noti-api 프로덕션 배포 #3 (Version ",[32,44779,44780],{},"926017d2...",", JWT_SECRET secret, 실제 \u002Fauth\u002Fsignup → \u002Fme 동작 확인) + POST \u002Fsend\u002Fsms 발송 producer(DB 적재까지 — 발신번호 검증·옵트아웃 필터·크레딧 hold·트랜잭션, 알려진 멱등 버그 TODO) + malgn-noti-api 프로덕션 배포 #4·#5 (Version ",[32,44783,44784],{},"4d9e1fbe...",[32,44786,44787],{},"afaa4c89...",", \u002Fsend\u002Fsms 라이브 가드·검증 흐름 9건 + 재배포 검증 4건, jwt.ts linter 캐스트 명시화) + 🐛 멱등 버그 해결 (TB_IDEMPOTENCY + INSERT-then-conflict race-free 패턴, 검증 3건 통과) + NHN SMS 어댑터(mock\u002Freal) + Cloudflare Queues(",[32,44790,24037],{},") + consumer worker (dispatch_state 천이) + 프로덕션 배포 #6 (Version ",[32,44793,24505],{},", Producer+Consumer 동시 바인딩 라이브, 큐 e2e는 Cloudflare 1105 회복 후 재검증)",[107,44796,44797,44803],{},[131,44798,44799],{},[26,44800,44802],{"href":44801},".\u002Fhistory.20260604","2026-06-04",[131,44804,44805,44808,44809,44811,44812,44814,44815,39844,44820,44822,44823,44826,44827,6219,44829,44831,44832,40178,44834,44836,44837,5439,44840,44843,44844,44846,44847,44849,44850,39755,44852,39759,44854,44857,44858,44860,44861,44863,44864,44867,44868,41671,44871,44874,44875,44877,44878,4339,44881,5439,44884,44887,44888,44890,44891,4339,44893,44895,44896,44898,44899,44901,44902,44905,44906,44908,44909,44912,44913,44915,44916,39805,44918,44920,44921,44923,44924,44926,44927,44929,44930,44905,44933,44935,44936,44912,44939,5439,44941,44944,44945,37244,44947,44949,44950,44953],{},[21,44806,44807],{},"§1"," NICE IPv6 진단 — 6\u002F2 §16의 1007 차단 후속. 사용자 옵션 B(NICE 콘솔에 Cloudflare egress IP 대역 등록) 진행 후 재시도 → 여전히 1007. 진단용 ",[32,44810,39709],{}," 임시 endpoint로 ",[32,44813,39837],{}," 8회 호출 → Workers outbound가 ",[21,44816,44817,44818],{},"IPv6 ",[32,44819,39716],{},[32,44821,39720],{}," 소속)임을 확인. NICE에 IPv4만 등록되어 IPv6 출발지 거부가 원인. 사용자에게 Cloudflare IPv6 대역 7개 추가 등록 또는 IP 검사 OFF 안내 후 보류. 진단 endpoint 즉시 제거 + Workers 재배포. CLIENT_ID\u002FSECRET 덮어쓰기(값 일치 보장만). ",[21,44824,44825],{},"§2"," Hyperdrive 교체 — ",[32,44828,39727],{},[32,44830,39730],{},". 신규 origin host ",[32,44833,39738],{},[32,44835,39741],{},". Aurora 노출이 \"퍼블릭+SG egress IP 화이트리스트\"에서 \"Tunnel(Access) 기반\"으로 전환 — egress IP 동기화 부담 해소, CLAUDE.md §12 TODO \"SG 갱신 운영 절차\" 항목 자연 달성. 코드 변경 0. 라이브 검증(GET \u002Fhealth\u002Fdb mysql_version 8.0.42 + POST \u002Fauth\u002Fnice\u002Finit DB 쓰기 정상). 정본 3개 동기화 — API CLAUDE.md §3·§8·§12, SCALABILITY.md §6 신규 절(전\u002F후 비교 + 전환 이유 + 운영 영향), MIGRATION.md §1 통로 다이어그램에 Tunnel 단계 추가. Workers 배포 Version ",[32,44838,44839],{},"a457b7dc...",[21,44841,44842],{},"§3"," 관리자단 핸드오프 풀세트 — ",[32,44845,39750],{}," (3,129줄 jsx)를 Vue 3 + Nuxt UI v3로 1:1 포팅. 셸 완전 재정비(AppLnb 핸드오프 정본 9 그룹\u002F17 라우트로 교체 + AppTopbar useState 기반 동적 브레드크럼 + useBreadcrumb composable) + 공유 컴포넌트 14종(PageHeader\u002FSectionCard\u002FTabs\u002FSegmented\u002FFilterBar\u002FDateRange\u002FDataTable generic+",[32,44848,40946],{}," scoped slot\u002FPagination\u002FStatusBadge 값→톤 자동 매핑\u002FChannelChip\u002FStatCard accent 7종\u002FDrawer\u002FModal Teleport+scroll lock\u002FField\u002FEmptyState) + 차트 4종(BarChart 그룹 막대\u002FAreaChart SVG path+그라데이션\u002FDonut stroke-dasharray\u002FProgressBar) + ",[21,44851,39754],{},[32,44853,39758],{},[21,44855,44856],{},"§4"," 폰트 사이즈 정합화 — 사용자 보고로 두 원인 동시 발견: (a) main.css ",[32,44859,39771],{},"가 Tailwind 토큰(16px base 가정)을 모두 18% 축소시킴 — 핸드오프는 base font-size 명시 없이 16px Tailwind 기본 사용 + ",[32,44862,39779],{},". (b) 직전 turn의 cwd가 ",[32,44865,44866],{},"\u002Fmalgn-noti","(사용자단)였고 거기서 빌드한 dist를 ",[32,44869,44870],{},"wrangler pages deploy --project-name=malgn-noti-admin",[21,44872,44873],{},"사용자단을 admin 프로젝트에 잘못 배포","한 상태였음(chunk 600개 \u002F AppGnb·AppContractPanel·AppCardAddDialog 등 사용자단 chunk 혼입). 둘 다 정정 — main.css 13px 제거 + letter-spacing -0.01em 추가, admin 디렉토리에서 clean rebuild + 재배포 alias ",[32,44876,39783],{}," (chunk 96개로 정상화). 교훈으로 멀티 레포 deploy 가드(prebuild name 체크 + chunk 수 sanity)·base font-size 명시 금지 패턴 정리. malgn-noti-admin 커밋 ",[32,44879,44880],{},"0227cae",[32,44882,44883],{},"1b63200",[21,44885,44886],{},"§5"," WBS 페이지 편집 — DB 미사용\u002FR2 단일 JSON 객체 정본(",[32,44889,58],{},", FILES 바인딩). API에 GET 공개 + PATCH 인증 2 라우트 + 142 task 시드(",[32,44892,41965],{},[32,44894,41978],{},"). 사용자단은 임베디드 STAGES 제거 → top-level ",[32,44897,42054],{}," + AppModal 인라인 편집 다이얼로그(owner·note·href·targetDate·completionDate 5 필드, 빈값=",[32,44900,30655],{},"=필드 제거). 비로그인은 읽기 전용 + \"로그인하면 편집 가능\" 힌트. 동시 편집은 last-write-wins. malgn-noti-api 커밋 ",[32,44903,44904],{},"9945db3"," Workers Version ",[32,44907,39796],{},", malgn-noti 커밋 ",[32,44910,44911],{},"3ed473e"," Pages alias ",[32,44914,39800],{},". 후속(§5.9) 목표일·완료일 ",[32,44917,39804],{},[32,44919,39808],{}," 캘린더 위젯 + API Zod regex ",[32,44922,42239],{}," 강제. 레거시 ",[32,44925,42215],{}," 값은 표시 시 2026 기준 ",[32,44928,301],{},"로 자동 변환, 다음 PATCH 시 R2에도 정합화. malgn-noti-api ",[32,44931,44932],{},"3a35464",[32,44934,39812],{},", malgn-noti ",[32,44937,44938],{},"08e5c33",[32,44940,39815],{},[21,44942,44943],{},"§6"," NHN Notification Hub OAuth 어댑터 + Email 실 발송 + 서비스 담당자 이메일 변경 라우트 — Notification Hub 신규 통합 서비스의 인증이 OAuth2 client_credentials → Bearer 토큰임을 확인 후 어댑터 전면 재작성. ",[32,44946,42323],{},[32,44948,42374],{}," Basic Auth + ",[32,44951,44952],{},"scope=appKey:{APP_KEY}"," 토큰 발급 + 메모리 캐시). SMS·Email 어댑터 `POST {base}\u002Fmessage\u002Fv1.0\u002F{SMS",[107,44955,44956,44962],{},[131,44957,44958],{},[26,44959,44961],{"href":44960},".\u002Fhistory.20260602","2026-06-02",[131,44963,44964,44965,44967,44968,44970,44971,30180,44973,44975,44976,7086,44979,44981,44982,44984,44985,29457,44988,13735,44990,44992,44993,30525,44995,44997,44998,51,45000,45002,45003,45005,45006,45008,45009,45011,45012,45014,45015,45018,45019,45021,45022,45024,45025,45053,45054,45056,45057,45059,45060,45062,45063,45065,45066,51,45068,45070,45071,45073,45074,45076,45077,45079,45080,45082,45083,45085,45086,45089,45090,45093,45094,45096,45097,45100,45101,45104,45105,45107,45108,45110,45111,45113,45114,45116,45117,45120,45121,45124,45125,45127,45128,45130,45131,45133,45134,4536,45137,4536,45139,4536,45141,4536,45143,4536,45145,6251,45147,45149,45150,45152,45153,7086,45156,45159,45160,45162,45163,45165,45166,6219,45168,45170,45171,7086,45174,51,45176,45178,45179,35820,45181,45183,45184,45186,45187,4536,45189,45191,45192,45194,45195,45197,45198,4536,45200,4536,45202,35873,45204,45206,45207,45209,45210,45212,45213,45216,45217,45220,45221,45224,45225,45227],{},"회원·인증 트랙 첫 날. ",[21,44966,44807],{}," WBS 구조 개편 — 사용자단을 ",[21,44969,30175],{},"(목업) + ",[21,44972,30179],{},[21,44974,30183],{},"(실 데이터) 3 트랙으로 분리. 5-3-15 단일 항목을 16개 도메인별 5-3C-1~16으로 펼침. Step 5 진척률 55%→40%, 전체 가중평균 45%→38%. Pages 배포 #51(alias ",[32,44977,44978],{},"bca573ce",[21,44980,44825],{}," 로그인 UX 개선 — ",[32,44983,30107],{}," 신설(이메일+비번 → 회사 자동 찾기, 단일 매치 즉시 토큰 \u002F 복수 매치 시 ",[32,44986,44987],{},"multipleCompanies:true + companies[]",[32,44989,14160],{},[21,44991,30630],{},", 복수 매치 시 회사 선택 카드 UI. Workers 배포 #10(Version ",[32,44994,30810],{},[32,44996,30814],{},"). 라이브 e2e 5 시나리오 통과. WBS 5-3C-4 ⚪→✅. ",[21,44999,44842],{},[32,45001,30114],{}," 전역 UNIQUE 정합화 — 정책상 한 이메일이 여러 회사에 가입되어선 안 됨. ",[32,45004,30897],{}," 신규 + 라이브 적용(",[32,45007,30901],{}," 복합 DROP → ",[32,45010,30905],{}," 단독 ADD). schema.ts에 ",[32,45013,30909],{}," 명시. login-by-email 단순화(",[32,45016,45017],{},"for-of"," 다중 verify 루프 제거, ",[32,45020,31138],{},") + OpenAPI에서 ",[32,45023,30611],{}," 삭제 + 프런트 회사 선택 UI(",[20367,45026,45027,45028,45031,45032,45035,45036,45038,45039,4536,45041,45043,45044,45046,45047,45049,45050,45052],{},"80줄) 제거. 사전 검증용 임시 데이터 cleanup(회사 8 + 사용자 12). Workers 배포 #11(Version ",[32,45029,45030],{},"f7f42855...","), Pages 배포 #53(alias ",[32,45033,45034],{},"f150ea0a","). 라이브 e2e 4건 통과(signup 정상 \u002F loginid 중복 409 \u002F login-by-email 단일 토큰 \u002F UNIQUE 인덱스 단독 확인). \"한 이메일 = 한 회사 = 한 로그인\". ",[21,45037,44856],{}," 휴대폰 SMS OTP 라우트(",[32,45040,29991],{},[32,45042,29440],{}," 이메일 OTP와 동일 패턴, ",[32,45045,31462],{},") + signup.vue Step 4 실 API 연동 + useApi.ts 401 처리 분리(",[32,45048,30131],{}," 호출은 호출자 처리 — 가입 도중 OTP 잘못 입력 시 페이지 이동 버그 수정) + 가입 완료 화면 고객사 ID 노출 제거 + 토스트 위치(오른쪽 위) + 크기 강화(17px·440px). Workers #12(",[32,45051,31440],{},"), Pages #54","#58. ",[21,45055,44886],{}," NICE 통합인증(휴대폰 본인확인) 인프라 — doc\u002FNICE_AUTH.md 신규 정본(12 섹션) + ",[32,45058,30146],{}," 라이브 적용(TB_NICE_AUTH 신설 + TB_USER에 birthdate\u002Fgender\u002Fnational_info\u002Fci\u002Fmobile_co + UNIQUE ci) + NICE 어댑터(mock\u002Freal, AES-256-GCM + PBKDF2 + HMAC, Workers Web Crypto만) + 3 라우트(init\u002Fcallback\u002Fstatus) + \u002Fauth\u002Fsignup 확장(niceSession 검증·CI 중복 차단·NICE 결과로 이름·휴대폰·생년월일 덮어쓰기·state='consumed' 재사용 차단) + signup.vue Step 4 통째로 NICE 흐름으로 교체(\"본인 인증하기\" 버튼 + popup + 폴링 + 결과 표시) + NICE_MOCK secret 적용(자격증명 발급 전 mock 통과). Workers #13(",[32,45061,31870],{},"), Pages #60(",[32,45064,31874],{},"). 라이브 e2e 6 시나리오 통과. ",[21,45067,44943],{},[32,45069,3991],{}," 실 API 연동 — 백엔드 ",[32,45072,21820],{}," 응답을 TB_USER 13 + TB_COMPANY 14 컬럼 풀로 확장 + ",[32,45075,32697],{},"(name·phone) + ",[32,45078,32700],{},"(companyPhone·billingEmail·adReceive, owner\u002Fadmin) 신설. 사용자단 ",[32,45081,32723],{}," 전면 교체 — 목업 데이터 제거 + useAuthStore 기반 실 데이터 + 광고수신 토글 즉시 PATCH + 저장하기는 변경 필드만 한 번에 PATCH. Workers #14(",[32,45084,32734],{},"), Pages #61(",[32,45087,45088],{},"ea35651d","). 라이브 e2e 5건 통과. WBS 5-3C-7 ⚪→🟢. ",[21,45091,45092],{},"§7"," 사업자등록증 심사 승인 게이트 — 새 정책: 법인\u002F개인사업자 가입 후 승인 받아야 서비스·정보 수정 가능, 개인은 즉시. 0005 라이브 적용(TB_COMPANY에 company_type\u002Fapproval_state\u002Frejected_reason + 기존 5행 'approved' 호환). signup 자동 분기 + PATCH \u002Fme·\u002Fme\u002Fcompany에 승인 게이트 403 + \u002Fme 응답에 승인 정보 노출. 사용자단 — signup.vue Step 5 분기 + AppMemberInfoPanel에 승인 배너(pending=warning, rejected=danger+사유) + 모든 입력 disabled. Workers #15(",[32,45095,33507],{},"), Pages #64(",[32,45098,45099],{},"56e94e5b","). 라이브 e2e 8 시나리오 통과(법인 pending \u002F 수정 403 \u002F 개인 통과 \u002F 운영자 승인 후 통과 \u002F 반려 사유 노출). WBS 5-3C-6 ⚪→🟢 + 5-3C-17 ✅. ",[21,45102,45103],{},"§8"," 승인 게이트 전 도메인 일관 적용 — ",[32,45106,33745],{}," 미들웨어를 ",[32,45109,34124],{},"로 추출 + 18 도메인 라우트에 일괄 적용(",[32,45112,34109],{}," 옵션 — GET 통과, POST\u002FPATCH\u002FPUT\u002FDELETE만 차단). ",[32,45115,4073],{},"는 예외(승인 관련 문의 가능). 자동화 스크립트(grep + perl)로 18 라우트의 import + use 라인 일관 갱신. Workers #16(",[32,45118,45119],{},"798bf6f5...","). 라이브 e2e 6 시나리오 통과(GET 통과 \u002F POST 4건 403 \u002F 개인 통과 \u002F 문의 작성 차단 안 됨). ",[21,45122,45123],{},"§9"," 사용자단 승인 게이트 UI 일관화 — ",[32,45126,34887],{}," 신규(layout 최상단·GNB 위 글로벌 띠, pending=warning·rejected=danger+사유) + ",[32,45129,34894],{}," 신규(차단 페이지 접근 시 ",[32,45132,3991],{},"로 리다이렉트, 허용은 ",[32,45135,45136],{},"\u002Faccount",[32,45138,11278],{},[32,45140,5744],{},[32,45142,4457],{},[32,45144,26863],{},[32,45146,18473],{},[32,45148,11278],{}," 미승인 분기(KPI\u002F채널 대신 ",[32,45151,34907],{}," 큰 안내 카드 + CTA). Pages #65(",[32,45154,45155],{},"2eec9e0b",[21,45157,45158],{},"§10"," 미승인 진입점을 ",[32,45161,19458],{},"(계약 관리)로 변경 — 정책상 사업자등록증 제출\u002F재제출 화면이 미승인 흐름의 메인. middleware ALLOWED_PREFIXES에서 ",[32,45164,11278],{}," 제거 + 리다이렉트 대상을 ",[32,45167,3991],{},[32,45169,19458],{},". AppApprovalBanner CTA \"계약 관리\". signup.vue Step 5 사업자\u002F개인 분기(\"계약 관리로 이동\" \u002F \"대시보드로 이동\"). \u002Fhome.vue §9의 미승인 분기 코드 제거(미들웨어가 차단해 진입 불가). Pages #66(",[32,45172,45173],{},"5256d66d",[21,45175,43729],{},[32,45177,19458],{}," 실 API + R2 첨부 인프라 — R2 bucket ",[32,45180,35819],{},[32,45182,11253],{}," FILES 바인딩 + ",[32,45185,28258],{},"에 라이브 DDL 매칭(",[32,45188,35831],{},[32,45190,35778],{},") + signup corp\u002Fsole 분기에서 'initial' 이용계약 자동 생성. ",[32,45193,35862],{}," 라우트 신규 5종(list · ",[27203,45196],{},"\u002Fsign · files list · files upload(multipart) · files download(stream) · files delete) — PDF·10MB 제한, name 접두사(",[32,45199,35866],{},[32,45201,35869],{},[32,45203,35872],{},[32,45205,35778],{},"에 kind 컬럼 없음), 회사 단위 권한(companyId 매칭), renew 체결 시 다른 done 자동 expired. OpenAPI 5 paths + 2 schemas. 사용자단 ",[32,45208,35887],{}," 전면 교체 — 목업 제거 + ",[32,45211,37629],{}," SSR + FormData 멀티파트 업로드 + 미리보기는 인증 fetch → blob → object URL(iframe Auth 헤더 우회). Workers #17(Version ",[32,45214,45215],{},"7213946f...","), Pages alias ",[32,45218,45219],{},"9808fe42",". 라이브 e2e 4 시나리오 통과(corp signup auto-contract \u002F 파일 업로드 R2 \u002F 파일 목록 \u002F 체결 → done+expires +2y). ",[21,45222,45223],{},"§12"," 사업자등록증 첨부 시 회사 승인 상태 'reviewing' 자동 전이 — ",[32,45226,20267],{}," enum 4단계 확장(`pending → reviewing → approved",[107,45229,45230,45235],{},[131,45231,45232],{},[26,45233,42156],{"href":45234},".\u002Fhistory.20260601",[131,45236,45237,45239,45240,45242,45243,45245,45246,26872,45248,45250,45251,4536,45253,45255,45256,7086,45258,51,45260,45262,45263,45265,45266,4536,45269,45272,45273,45275,45276,45279,45280,4536,45282,45284,45285,45287,45288,51,45290,45292,45293,45296,45297,45299,45300,4420,45302,4420,45304,45306,45307,45309,45310,45312,45313,45315,45316,45318,45319,45321,45322,45324,45325,45327,45328,45330,45331,4420,45334,4536,45336,4536,45338,45340,45341,45343,45344,45346,45347,4536,45349,45351,45352,45354,45355,4361,45357,45359,45360,45362,45363,45366],{},[21,45238,44807],{}," WBS 정본화 — ",[32,45241,42],{}," 신규(5 단계 가중치·Step 1~5 작업 내역·알려진 한계, ~220 라인) + 사용자단 ",[32,45244,26863],{}," 공개 라이브 카탈로그(",[32,45247,26871],{},[32,45249,26875],{}," Notion soft SaaS 디자인 차용 — Hero stats·단계별 진행률 오버뷰·Stage 상세·그룹별 작업 카드·상태 칩·외부 링크 + ",[32,45252,19695],{},[32,45254,19861],{},", ~650 라인). Step 5(서비스 개발)는 원본 채널·도메인 단위 항목(대부분 0%)을 실제 진행에 맞춰 5-1 설계(7) \u002F 5-2 API(16) \u002F 5-3 사용자단(15) \u002F 5-4 관리자단(13) \u002F 5-5 통합·배포(7)의 5 그룹 58 작업으로 재정렬. Cloudflare Pages 배포 #47 (alias ",[32,45257,26898],{},[21,45259,44825],{},[32,45261,24556],{}," DDL — ",[32,45264,20120],{}," 1105 잔류 3회 재시도 모두 실패(Ray ID ",[32,45267,45268],{},"a04a8ca2...",[32,45270,45271],{},"a04a9753...",") → Aurora 직결(",[32,45274,27648],{}," 계정·SSL REQUIRED)로 우회 점검. 4 신규 테이블(TB_EXPORT_JOB \u002F TB_FLOW_*) ",[21,45277,45278],{},"이미 라이브 적용된 상태 확인"," — 컬럼 100% 일치, 인덱스·FK는 라이브가 더 정교(FK 6 + 의미 인덱스명). 라이브 워커 e2e 검증(",[32,45281,23355],{},[32,45283,24552],{}," GET 200 \u002F POST 201) + 테스트 데이터 cleanup + ",[32,45286,24556],{}," 파일을 라이브 정본에 맞춰 동기화. WBS 5-2-11\u002F12 🟢 (CRUD ✅·처리 worker 미) + 5-5-4 ⛔→✅. ",[21,45289,44842],{},[32,45291,28258],{}," 정합화 — export\u002Fflow 4 테이블에 라이브 정본의 인덱스 6 + FK 6 명시화(",[32,45294,45295],{},"malgn-noti-api: 0475bd2",", schema.ts 단일 파일 +22 -4, 컬럼 정의 변경 0, 다른 테이블 변경 0, typecheck 통과, 런타임 영향 0). drizzle-kit introspect drift 12건 → 0. ",[21,45298,44856],{}," 사용자단 인증 백엔드 연동 — ",[32,45301,23258],{},[32,45303,23261],{},[32,45305,3987],{}," 실 API 연동(JWT ",[32,45308,28722],{}," 쿠키 + ",[32,45311,9248],{}," Bearer 자동 주입 + 글로벌 미들웨어 1차 가드 + ",[32,45314,28746],{}," 클라이언트 부트스트랩 페치). 회원가입 Step 4 → 실 API → 자동 로그인 → ",[32,45317,11278],{},". 로그인은 ",[32,45320,28742],{}," 쿠키 자동 사용. 첫 SSR 시도에서 ",[32,45323,29129],{},"가 Nuxt instance 컨텍스트 손실로 500 → 미들웨어\u002F플러그인 분리로 우회. 6 파일 수정 + 1 신규(",[32,45326,28746],{},"), typecheck 통과, 로컬 + 프로덕션 e2e(쿠키 동봉 시 ",[32,45329,11278],{}," 200 · 없으면 ",[32,45332,45333],{},"\u002Flogin?redirect",[32,45335,9335],{},[32,45337,17640],{},[32,45339,26863],{}," 200). Pages 배포 #49 (alias ",[32,45342,28762],{},"). WBS 5-3-15 ⚪ → 🟢. ",[21,45345,44886],{}," 이메일 OTP 인증 — ",[32,45348,29660],{},[32,45350,29440],{}," 백엔드 신설(TB_VERIFICATION + SHA-256 코드 해시 + TTL 10분·재발송 시 직전 코드 만료·5회 시도 제한·소비 후 재사용 차단), Drizzle schema.ts에 verification 정의(라이브 정본 정합), OpenAPI 2 paths + 3 schemas, Workers 배포 #9(Version ",[32,45353,29456],{},"). 프런트 signup.vue ",[32,45356,29462],{},[32,45358,29465],{}," 실 API 연동(",[32,45361,29476],{}," 토스트 노출은 NHN_MOCK=1 한정 — production 자동 차단, 로딩 상태 + 재발송 라벨). Pages 배포 #50(alias ",[32,45364,45365],{},"c2100890","). 라이브 e2e 6 시나리오 모두 통과(발송·잘못된 코드 401·올바른 코드 200·소비 후 재시도 401·재발송·DB 행 검증) + 검증 후 임시 NHN_MOCK secret 제거 확인. doc\u002FSIGNUP.md §8 #4 ⚪→✅.",[107,45368,45369,45375],{},[131,45370,45371],{},[26,45372,45374],{"href":45373},".\u002Fhistory.20260527","2026-05-27",[131,45376,45377,89,45380,45382,45383,45385,45386,45388,45389,89,45392,45394,45395,24495,45397,45399,45400,45402,45403,45405,45406,45408,45409,45411,45412,89,45415,45417,45418,45420,45421,89,45424,45426,45427,24526,45429,45431,45432,7086,45434,89,45437,45439,45440,45443,45444,45447,45448,4339,45451,51,45454,4339,45456,45458,45459,51,45462,4339,45464,45466,45467,45469,45470,45472,45473,45475,45476,45478],{},[21,45378,45379],{},"트랙 A",[32,45381,7672],{}," Nuxt 3 + Nuxt UI v3 부트스트랩 + LNB(256px · 8그룹) + TopBar(64px) 셸 레이아웃(",[32,45384,24477],{}," 정본 참조) + 첫 실 Nuxt 앱 프로덕션 배포(",[32,45387,11230],{},", 이전 정적 placeholder 대체). ",[21,45390,45391],{},"트랙 B",[32,45393,50],{}," §19 멱등 버그 정식 해결(TB_IDEMPOTENCY + INSERT-then-conflict race-free) + NHN SMS 어댑터(mock\u002Freal) + Cloudflare Queues(",[32,45396,24037],{},[32,45398,20261],{}," 천이) + 프로덕션 배포 #6(Version ",[32,45401,24505],{},", Producer+Consumer 동시 바인딩) + NHN webhook 핸들러(",[32,45404,24501],{},", HMAC-SHA256 서명 검증·dedup_key 멱등·recv_state 자동 천이) + ",[21,45407,24508],{},"(adapter·producer·worker·OpenAPI 4지점, channel branching generic화·EMAIL_PRICING=0.65·KAKAO_PRICING={alimtalk:8,friendtalk:12}·PUSH_PRICING=0.5) + 프로덕션 배포 #7(Version ",[32,45410,24512],{},"). 큐 e2e + 웹훅 검증은 Cloudflare 1105 회복 후 재시도. ",[21,45413,45414],{},"트랙 C",[32,45416,7664],{}," 사용자단 추가 작업으로 메시지 관리 랜딩페이지 만들기(목록·기본형\u002F확장형 폼·미리보기) + 문의하기 경로를 ",[32,45419,16882],{},"로 이동(GNB·푸터·사이트맵 링크 정리) + 나의 페이지 섹션(공통 셸 + 9개 라우트·회원 정보 변경·결제 카드 관리) + 크레딧 충전 플로우(시안 기반 재구성·결제 컨펌·결과 화면) Pages 배포 #44·#45·#46. ",[21,45422,45423],{},"트랙 D",[32,45425,50],{}," 루트(",[32,45428,4361],{},[32,45430,18666],{},"로 교체해 Scalar API 문서로 302 이동, Workers 재배포(Version ",[32,45433,24532],{},[21,45435,45436],{},"트랙 E",[32,45438,50],{}," §13 working tree WIP 정식 커밋 — ",[21,45441,45442],{},"RCS 채널(5채널째)"," adapter(",[32,45445,45446],{},"rcs.ts"," sms\u002Flms\u002Fmms\u002Ftemplate 4타입)·producer·consumer·webhook + ",[32,45449,45450],{},"RCS_PRICING{sms:12,lms:40,mms:120,template:50}",[21,45452,45453],{},"Export 잡",[32,45455,24543],{},[32,45457,23355],{}," CRUD(시안 다운로드 요청 PU 대응, 90일 윈도우 우회) + ",[21,45460,45461],{},"Flow 정의",[32,45463,24549],{},[32,45465,24552],{}," CRUD(nodes JSON 검증: order 연속·첫 노드 always·5채널 enum) + OpenAPI 4지점(+230) + ",[32,45468,24556],{}," 마이그레이션 → 단일 배치 커밋 ",[32,45471,24560],{},"(12 files +1228 −16) + 프로덕션 배포 #8(Version ",[32,45474,24564],{},"). DDL 적용은 1105 회복 후 ",[32,45477,21708],{}," 경로로 보류(프런트 호출처 0개 — 무영향).",[76,45480,45482],{"id":45481},"작성-시점","작성 시점",[81,45484,45485,45488],{},[84,45486,45487],{},"큰 마일스톤 마무리 직후",[84,45489,45490],{},"그날 끝낼 때 짧게라도 한 줄 요약 + 산출물 목록만이라도",[76,45492,45493],{"id":45493},"검색",[18,45495,45496,45497,9148],{},"특정 결정을 언제 했는지 찾을 때 ",[32,45498,21908],{},[5251,45500,45502],{"className":10197,"code":45501,"language":10199,"meta":4208,"style":4208},"grep -rn \"Smart Placement\" doc\u002Fhistory\u002F\ngrep -rn \"Aurora 노출\" doc\u002Fhistory\u002F\n",[32,45503,45504,45517],{"__ignoreMap":4208},[7968,45505,45506,45508,45511,45514],{"class":5110,"line":7970},[7968,45507,21908],{"class":7980},[7968,45509,45510],{"class":8892}," -rn",[7968,45512,45513],{"class":7993}," \"Smart Placement\"",[7968,45515,45516],{"class":7993}," doc\u002Fhistory\u002F\n",[7968,45518,45519,45521,45523,45526],{"class":5110,"line":4212},[7968,45520,21908],{"class":7980},[7968,45522,45510],{"class":8892},[7968,45524,45525],{"class":7993}," \"Aurora 노출\"",[7968,45527,45516],{"class":7993},[8560,45529,45530],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":4208,"searchDepth":4209,"depth":4209,"links":45532},[45533,45534,45535,45536,45537],{"id":12559,"depth":4212,"text":12560},{"id":44532,"depth":4212,"text":44533},{"id":44559,"depth":4212,"text":44559},{"id":45481,"depth":4212,"text":45482},{"id":45493,"depth":4212,"text":45493},"날짜별 작업 내역을 누적해 두는 폴더입니다. 하루에 한 파일 원칙.",{},"\u002Fhistory\u002Freadme",{"title":44499,"description":45538},"history\u002FREADME","vMWqMtDSGy1TbI-Hhw5GUGfT3Mx_OR7_8mSljI196zU",{"id":45545,"title":45546,"body":45547,"description":4208,"extension":4257,"meta":48758,"navigation":4259,"path":48759,"seo":48760,"stem":48761,"__hash__":48762},"docs\u002FMEMBERSHIP.md","회원·인증 통합 인덱스",{"type":8,"value":45548,"toc":48694},[45549,45552,45594,45596,45602,45608,45740,45747,45973,45981,45985,46093,46097,46223,46227,46270,46272,46276,46281,46288,46375,46381,46385,46459,46463,46488,46491,46506,46510,46515,46520,46546,46553,46573,46575,46582,46671,46680,46682,46688,46694,46955,46961,47048,47054,47060,47108,47111,47115,47142,47149,47229,47236,47240,47269,47275,47320,47322,47326,47330,47452,47456,47487,47491,47522,47525,47529,47590,47597,47601,47640,47642,47646,47660,47666,47677,47681,47699,47703,47771,47785,47789,47797,47801,47807,47811,47817,47821,47866,47877,47881,47928,47935,47939,47948,47950,47954,48106,48108,48112,48469,48471,48475,48479,48531,48535,48579,48583,48627,48631,48654,48658],[11,45550,45546],{"id":45551},"회원인증-통합-인덱스",[15,45553,45554,45588],{},[18,45555,45556,45558,45559,45562,45563,45567,45568,45572,45573,45577,45578,45582,45583,45587],{},[21,45557,23],{},": 회원가입·로그인·계정관리·인증·승인 게이트의 ",[21,45560,45561],{},"모든 페이지·API·DB 테이블·정책","을 한 곳에서 조망.\n가입 절차 단계별 상세는 ",[26,45564,45566],{"href":45565},".\u002Fpages\u002FSIGNUP",".\u002Fpages\u002FSIGNUP.md",", 계약 관리 페이지는 ",[26,45569,45571],{"href":45570},".\u002Fpages\u002FCONTRACT",".\u002Fpages\u002FCONTRACT.md",",\nNICE 본인확인 인프라는 ",[26,45574,45576],{"href":45575},".\u002FNICE_AUTH",".\u002FNICE_AUTH.md",", 일자별 작업 이력은\n",[26,45579,45581],{"href":45580},".\u002Fhistory\u002Fhistory.20260601",".\u002Fhistory\u002Fhistory.20260601.md"," §4·§5 \u002F ",[26,45584,45586],{"href":45585},".\u002Fhistory\u002Fhistory.20260602",".\u002Fhistory\u002Fhistory.20260602.md"," §4~§16 참조.",[18,45589,45590,45593],{},[21,45591,45592],{},"마지막 현행화",": 2026-06-02 (§11~§16 반영 — 계약·서류 R2 라우트, reviewing 상태 추가, 계약 서명 본인인증, NICE\u002FNHN 운영 상태)",[73,45595],{},[76,45597,45599,45600,4343],{"id":45598},"_1-사용자단-페이지-malgn-noti","1. 사용자단 페이지 (",[32,45601,7664],{},[237,45603,45605,45606,4343],{"id":45604},"_11-가입로그인재설정-인증-게이트-metaauth-false","1.1 가입·로그인·재설정 (인증 게이트 — ",[32,45607,29216],{},[101,45609,45610,45623],{},[104,45611,45612],{},[107,45613,45614,45616,45618,45620],{},[110,45615,22556],{},[110,45617,28918],{},[110,45619,23],{},[110,45621,45622],{},"백엔드 연동",[126,45624,45625,45648,45666,45684,45702,45719],{},[107,45626,45627,45631,45635,45638],{},[131,45628,45629],{},[32,45630,17640],{},[131,45632,45633],{},[26,45634,14163],{"href":4551},[131,45636,45637],{},"5단계 마법사. Step 3 이메일 OTP · Step 4 NICE 본인확인 · Step 5 가입 + 자동 로그인 + 유형 분기",[131,45639,36626,45640,4420,45643,4420,45646],{},[32,45641,45642],{},"\u002Fauth\u002Femail-code\u002F*",[32,45644,45645],{},"\u002Fauth\u002Fnice\u002F*",[32,45647,23258],{},[107,45649,45650,45654,45659,45662],{},[131,45651,45652],{},[32,45653,9335],{},[131,45655,45656],{},[26,45657,14160],{"href":45658},"..\u002Fapp\u002Fpages\u002Flogin\u002Findex.vue",[131,45660,45661],{},"이메일\u002F아이디 + 비밀번호 (회사 자동 매칭)",[131,45663,36626,45664],{},[32,45665,30107],{},[107,45667,45668,45672,45678,45681],{},[131,45669,45670],{},[32,45671,17881],{},[131,45673,45674],{},[26,45675,45677],{"href":45676},"..\u002Fapp\u002Fpages\u002Flogin\u002Fsecurity.vue","login\u002Fsecurity.vue",[131,45679,45680],{},"보안로그인 추가 인증(OTP\u002F이메일)",[131,45682,45683],{},"⚪ 백엔드 미구현",[107,45685,45686,45690,45696,45699],{},[131,45687,45688],{},[32,45689,17738],{},[131,45691,45692],{},[26,45693,45695],{"href":45694},"..\u002Fapp\u002Fpages\u002Freset-password\u002Findex.vue","reset-password\u002Findex.vue",[131,45697,45698],{},"비밀번호 재설정 요청",[131,45700,45701],{},"⚪ OTP 인프라 재활용 가능",[107,45703,45704,45708,45714,45717],{},[131,45705,45706],{},[32,45707,18318],{},[131,45709,45710],{},[26,45711,45713],{"href":45712},"..\u002Fapp\u002Fpages\u002Freset-password\u002Fnew.vue","reset-password\u002Fnew.vue",[131,45715,45716],{},"새 비밀번호 입력",[131,45718,3931],{},[107,45720,45721,45725,45730,45733],{},[131,45722,45723],{},[32,45724,19208],{},[131,45726,45727],{},[26,45728,19674],{"href":45729},"..\u002Fapp\u002Fpages\u002Finvite.vue",[131,45731,45732],{},"초대 메일 링크 → 멀티계정 등록",[131,45734,45735,45736,45739],{},"⚪ ",[32,45737,45738],{},"TB_MANAGER_INVITE"," 라우트 미구현",[237,45741,45743,45744,45746],{"id":45742},"_12-계정-관리-account-인증-필요","1.2 계정 관리 (",[32,45745,5699],{},", 인증 필요)",[101,45748,45749,45761],{},[104,45750,45751],{},[107,45752,45753,45755,45757,45759],{},[110,45754,22556],{},[110,45756,28918],{},[110,45758,23],{},[110,45760,45622],{},[126,45762,45763,45787,45815,45836,45856,45875,45895,45912,45931,45953],{},[107,45764,45765,45769,45775,45778],{},[131,45766,45767],{},[32,45768,3991],{},[131,45770,45771],{},[26,45772,45774],{"href":45773},"..\u002Fapp\u002Fpages\u002Faccount\u002Fsettings.vue","settings.vue",[131,45776,45777],{},"회원 정보 변경 (이름·휴대폰·회사 전화·결제 이메일·광고 수신)",[131,45779,36626,45780,4420,45782,4420,45784,45786],{},[32,45781,21820],{},[32,45783,32697],{},[32,45785,32700],{},". 이메일·휴대폰 변경 다이얼로그는 OTP 미연결(후속)",[107,45788,45789,45793,45799,45809],{},[131,45790,45791],{},[32,45792,19458],{},[131,45794,45795],{},[26,45796,45798],{"href":45797},"..\u002Fapp\u002Fpages\u002Faccount\u002Fcontract.vue","contract.vue",[131,45800,45801,45802,45805,45806],{},"계약서·전자서명·",[21,45803,45804],{},"사업자등록증 제출\u002F재제출"," (미승인 사용자 메인 진입점). 상세는 ",[26,45807,45808],{"href":45570},"pages\u002FCONTRACT.md",[131,45810,36626,45811,45814],{},[32,45812,45813],{},"\u002Fcontracts\u002F*"," 5 라우트 + R2 업로드 + 휴대폰 본인인증 서명 — 운영자 승인 화면만 미구현",[107,45816,45817,45822,45828,45830],{},[131,45818,45819],{},[32,45820,45821],{},"\u002Faccount\u002Fpassword",[131,45823,45824],{},[26,45825,45827],{"href":45826},"..\u002Fapp\u002Fpages\u002Faccount\u002Fpassword.vue","password.vue",[131,45829,19166],{},[131,45831,45735,45832,45835],{},[32,45833,45834],{},"POST \u002Fauth\u002Fpassword"," 미구현",[107,45837,45838,45842,45848,45851],{},[131,45839,45840],{},[32,45841,19452],{},[131,45843,45844],{},[26,45845,45847],{"href":45846},"..\u002Fapp\u002Fpages\u002Faccount\u002Fsecurity.vue","security.vue",[131,45849,45850],{},"보안로그인 토글 (2FA: OTP\u002F이메일)",[131,45852,45735,45853,45835],{},[32,45854,45855],{},"PATCH \u002Fme\u002Fsecurity",[107,45857,45858,45862,45868,45871],{},[131,45859,45860],{},[32,45861,19455],{},[131,45863,45864],{},[26,45865,45867],{"href":45866},"..\u002Fapp\u002Fpages\u002Faccount\u002Fmulti.vue","multi.vue",[131,45869,45870],{},"서비스 담당자 초대 (사업자만)",[131,45872,45735,45873,45835],{},[32,45874,29403],{},[107,45876,45877,45881,45887,45890],{},[131,45878,45879],{},[32,45880,18635],{},[131,45882,45883],{},[26,45884,45886],{"href":45885},"..\u002Fapp\u002Fpages\u002Faccount\u002Fcards.vue","cards.vue",[131,45888,45889],{},"결제 카드 관리",[131,45891,45735,45892,45894],{},[32,45893,22706],{}," 부분",[107,45896,45897,45901,45907,45910],{},[131,45898,45899],{},[32,45900,20012],{},[131,45902,45903],{},[26,45904,45906],{"href":45905},"..\u002Fapp\u002Fpages\u002Faccount\u002Fbilling.vue","billing.vue",[131,45908,45909],{},"결제 이력",[131,45911,3931],{},[107,45913,45914,45918,45924,45927],{},[131,45915,45916],{},[32,45917,19629],{},[131,45919,45920],{},[26,45921,45923],{"href":45922},"..\u002Fapp\u002Fpages\u002Faccount\u002Fcredit.vue","credit.vue",[131,45925,45926],{},"크레딧 내역·영수증",[131,45928,45735,45929,45894],{},[32,45930,22730],{},[107,45932,45933,45940,45946,45949],{},[131,45934,45935,4420,45937],{},[32,45936,18439],{},[32,45938,45939],{},"\u002Fdetail",[131,45941,45942],{},[26,45943,45945],{"href":45944},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiries\u002F","inquiries\u002F",[131,45947,45948],{},"나의 문의 목록·상세",[131,45950,45735,45951,45894],{},[32,45952,4073],{},[107,45954,45955,45962,45968,45971],{},[131,45956,45957,4420,45959],{},[32,45958,16882],{},[32,45960,45961],{},"\u002Fcomplete",[131,45963,45964],{},[26,45965,45967],{"href":45966},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiry\u002F","inquiry\u002F",[131,45969,45970],{},"문의 작성·완료",[131,45972,3931],{},[18,45974,45975,45976,45980],{},"공통 셸: ",[26,45977,45978],{"href":5695},[32,45979,5836],{}," — 좌측 메뉴 + 본문 슬롯.",[237,45982,45984],{"id":45983},"_13-회원인증-관련-공용-컴포넌트","1.3 회원·인증 관련 공용 컴포넌트",[101,45986,45987,45995],{},[104,45988,45989],{},[107,45990,45991,45993],{},[110,45992,40855],{},[110,45994,4803],{},[126,45996,45997,46010,46020,46030,46040,46050,46063,46073],{},[107,45998,45999,46004],{},[131,46000,46001],{},[26,46002,34887],{"href":46003},"..\u002Fapp\u002Fcomponents\u002FAppApprovalBanner.vue",[131,46005,46006,46009],{},[21,46007,46008],{},"사업자등록증 심사 상태 글로벌 띠"," — layout 최상단, pending\u002Freviewing=warning · rejected=danger+사유. CTA(pending=\"등록\"·reviewing=\"진행 상태 보기\"·rejected=\"다시 제출하기\")",[107,46011,46012,46017],{},[131,46013,46014],{},[26,46015,18178],{"href":46016},"..\u002Fapp\u002Fcomponents\u002FAppSignupTermsDialog.vue",[131,46018,46019],{},"약관 동의 모달",[107,46021,46022,46027],{},[131,46023,46024],{},[26,46025,18572],{"href":46026},"..\u002Fapp\u002Fcomponents\u002FAppMemberInfoPanel.vue",[131,46028,46029],{},"회원 정보 표시·편집 패널 — 승인 배너 + isLocked 입력 disabled",[107,46031,46032,46037],{},[131,46033,46034],{},[26,46035,18591],{"href":46036},"..\u002Fapp\u002Fcomponents\u002FAppEmailChangeDialog.vue",[131,46038,46039],{},"이메일 변경 + OTP (실 API 미연결)",[107,46041,46042,46047],{},[131,46043,46044],{},[26,46045,18595],{"href":46046},"..\u002Fapp\u002Fcomponents\u002FAppPhoneVerifyDialog.vue",[131,46048,46049],{},"휴대폰 본인 인증 (실 API 미연결)",[107,46051,46052,46060],{},[131,46053,46054,4420,46057],{},[26,46055,18578],{"href":46056},"..\u002Fapp\u002Fcomponents\u002FAppCardListPanel.vue",[26,46058,18599],{"href":46059},"..\u002Fapp\u002Fcomponents\u002FAppCardAddDialog.vue",[131,46061,46062],{},"결제 카드",[107,46064,46065,46070],{},[131,46066,46067],{},[26,46068,19668],{"href":46069},"..\u002Fapp\u002Fcomponents\u002FAppManagerInviteDialog.vue",[131,46071,46072],{},"서비스 담당자 초대",[107,46074,46075,46086],{},[131,46076,46077,4420,46080,4420,46083],{},[26,46078,19343],{"href":46079},"..\u002Fapp\u002Fcomponents\u002FAppContractPanel.vue",[26,46081,19398],{"href":46082},"..\u002Fapp\u002Fcomponents\u002FAppContractSignDialog.vue",[26,46084,19392],{"href":46085},"..\u002Fapp\u002Fcomponents\u002FAppContractViewDialog.vue",[131,46087,46088,46089,46092],{},"계약 관리 — 사업자등록증 제출 + 전자서명. ",[21,46090,46091],{},"실 API 연동 완료"," (§11~§15). 서명 다이얼로그에 휴대폰 본인인증 sub-step 포함",[237,46094,46096],{"id":46095},"_14-인프라-인증-글루","1.4 인프라 (인증 글루)",[101,46098,46099,46108],{},[104,46100,46101],{},[107,46102,46103,46105],{},[110,46104,28918],{},[110,46106,46107],{},"역할",[126,46109,46110,46131,46158,46169,46184,46197,46213],{},[107,46111,46112,46117],{},[131,46113,46114],{},[26,46115,46116],{"href":9245},"composables\u002FuseApi.ts",[131,46118,46119,46121,46122,46124,46125,46127,46128,46130],{},[32,46120,8692],{}," 래퍼 + ",[32,46123,28722],{}," 쿠키 + Bearer 자동 주입 + 401 처리 (",[32,46126,30131],{}," 호출은 호출자 처리, 그 외 만료 시 ",[32,46129,9335],{}," 리다이렉트)",[107,46132,46133,46138],{},[131,46134,46135],{},[26,46136,11040],{"href":46137},"..\u002Fapp\u002Fstores\u002Fauth.ts",[131,46139,46140,46141,4361,46143,4361,46145,4361,46148,4361,46150,4361,46152,4361,46154,46157],{},"Pinia: ",[32,46142,4552],{},[32,46144,22088],{},[32,46146,46147],{},"loginByEmail",[32,46149,28992],{},[32,46151,32965],{},[32,46153,32979],{},[32,46155,46156],{},"logout"," 액션 + AuthUser\u002FAuthCompany 풀 타입",[107,46159,46160,46166],{},[131,46161,46162],{},[26,46163,46165],{"href":46164},"..\u002Fapp\u002Fmiddleware\u002Fauth.global.ts","middleware\u002Fauth.global.ts",[131,46167,46168],{},"토큰 쿠키 가드 (SSR 안전)",[107,46170,46171,46176],{},[131,46172,46173],{},[26,46174,34894],{"href":46175},"..\u002Fapp\u002Fmiddleware\u002Fapproval.global.ts",[131,46177,46178,46181,46182,35377],{},[21,46179,46180],{},"사업자등록증 승인 게이트"," — 미승인이면 차단 페이지 접근 시 ",[32,46183,19458],{},[107,46185,46186,46191],{},[131,46187,46188],{},[26,46189,28746],{"href":46190},"..\u002Fapp\u002Fplugins\u002Fauth.client.ts",[131,46192,46193,46194,46196],{},"클라이언트 부트스트랩 — 토큰 쿠키 있으면 ",[32,46195,3987],{},"로 스토어 hydrate",[107,46198,46199,46204],{},[131,46200,46201],{},[26,46202,11355],{"href":46203},"..\u002Fapp\u002Flayouts\u002Fdefault.vue",[131,46205,46206,6219,46208,46210,46211],{},[32,46207,34887],{},[32,46209,11316],{}," → main → ",[32,46212,13094],{},[107,46214,46215,46220],{},[131,46216,46217,46219],{},[32,46218,28722],{}," 쿠키",[131,46221,46222],{},"JWT 7일·SameSite=Lax·secure-in-prod (HttpOnly 아님 — 후속에서 백엔드 Set-Cookie로 강화)",[237,46224,46226],{"id":46225},"_15-시스템-페이지-인증-관련-안내","1.5 시스템 페이지 (인증 관련 안내)",[101,46228,46229,46239],{},[104,46230,46231],{},[107,46232,46233,46235,46237],{},[110,46234,22556],{},[110,46236,28918],{},[110,46238,7718],{},[126,46240,46241,46257],{},[107,46242,46243,46248,46254],{},[131,46244,46245],{},[32,46246,46247],{},"\u002Ftemplete\u002Femail\u002Freset-password",[131,46249,46250],{},[26,46251,46253],{"href":46252},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Femail\u002Freset-password.vue","reset-password.vue",[131,46255,46256],{},"이메일 인증 메일 템플릿(미연동)",[107,46258,46259,46264,46267],{},[131,46260,46261],{},[32,46262,46263],{},"\u002Ftemplete\u002Femail\u002Fverify",[131,46265,46266],{},"(시안에 있으나 미작성)",[131,46268,46269],{},"가입 인증 메일 템플릿",[73,46271],{},[76,46273,46275],{"id":46274},"_2-사업자등록증-심사-승인-게이트-new-2026-06-02","2. 사업자등록증 심사 승인 게이트 (NEW — 2026-06-02)",[15,46277,46278],{},[18,46279,46280],{},"정책: 법인사업자(corp) \u002F 개인사업자(sole)는 가입 후 사업자등록증 심사 승인을 받아야\n서비스 이용 및 가입 정보 수정 가능. 개인(personal)은 즉시 사용 가능.",[237,46282,46284,46285,46287],{"id":46283},"_21-승인-상태-approval_state-4단계-712","2.1 승인 상태 (",[32,46286,20267],{},") — 4단계 (§7·§12)",[101,46289,46290,46303],{},[104,46291,46292],{},[107,46293,46294,46296,46298,46300],{},[110,46295,270],{},[110,46297,4629],{},[110,46299,36854],{},[110,46301,46302],{},"가능한 동작",[126,46304,46305,46319,46335,46350],{},[107,46306,46307,46311,46314,46316],{},[131,46308,46309],{},[32,46310,27043],{},[131,46312,46313],{},"사업자가 가입 직후 — 사업자등록증 미제출",[131,46315,36871],{},[131,46317,46318],{},"조회·계약 관리·1:1 문의만",[107,46320,46321,46325,46328,46333],{},[131,46322,46323],{},[32,46324,36757],{},[131,46326,46327],{},"사업자등록증 제출 후 — 운영자 심사 대기",[131,46329,46330,46332],{},[32,46331,36314],{}," kind=biz 시 자동(§12)",[131,46334,46318],{},[107,46336,46337,46341,46344,46347],{},[131,46338,46339],{},[32,46340,33595],{},[131,46342,46343],{},"운영자가 승인 또는 개인 가입자",[131,46345,46346],{},"운영자(BackOffice, 미구현) \u002F personal signup",[131,46348,46349],{},"모든 서비스",[107,46351,46352,46356,46362,46365],{},[131,46353,46354],{},[32,46355,33601],{},[131,46357,46358,46359,46361],{},"운영자가 반려 (",[32,46360,36785],{}," 함께)",[131,46363,46364],{},"운영자(BackOffice, 미구현)",[131,46366,46367,46368,46374],{},"조회·계약 관리(재제출)·1:1 문의만. ",[21,46369,46370,46371,46373],{},"재첨부 시 자동 ",[32,46372,36757],{},"으로 전이"," (§12)",[18,46376,46377,46378,4703],{},"상세 분기는 ",[26,46379,46380],{"href":45570},".\u002Fpages\u002FCONTRACT.md §3.1·§6.2·§7.3",[237,46382,46384],{"id":46383},"_22-미승인-사용자-ux-흐름","2.2 미승인 사용자 UX 흐름",[6674,46386,46387,46398,46411,46419,46432,46444,46453],{},[84,46388,46389,46392,46393,46395,46396],{},[21,46390,46391],{},"가입 완료"," → Step 5 → 사업자: ",[32,46394,19458],{}," \u002F 개인: ",[32,46397,11278],{},[84,46399,46400,6219,46402,6219,46404,46407,46408,46410],{},[21,46401,35607],{},[32,46403,28992],{},[32,46405,46406],{},"approval_state !== 'approved'"," → 어떤 경로로 가도 미들웨어가 ",[32,46409,19458],{},"로 자동 이동",[84,46412,46413,46415,46416,46418],{},[21,46414,34884],{}," (모든 페이지 상단) — ",[32,46417,34887],{}," 3분기 안내 + CTA",[84,46420,46421,6685,46423,4536,46425,4536,46427,46429,46430,35377],{},[21,46422,35622],{},[32,46424,11278],{},[32,46426,34052],{},[32,46428,21826],{}," 등) → 즉시 ",[32,46431,19458],{},[84,46433,46434,46437,46438,46440,46441,46443],{},[21,46435,46436],{},"사업자등록증 업로드"," → 백엔드가 자동 ",[32,46439,37085],{}," 전이 + ",[32,46442,29025],{},"로 store 갱신 → 글로벌 띠·페이지 배너 즉시 \"심사 중\"으로 전환 (§12)",[84,46445,46446,46449,46450,46452],{},[21,46447,46448],{},"승인 완료"," → 모든 페이지 정상 접근 + ",[32,46451,11278],{}," 진입 가능",[84,46454,46455,46458],{},[21,46456,46457],{},"§11 배포 이전 가입자 \u002F §12 배포 이전 첨부자"," — GET 시점 lazy backfill로 자동 복구 (§13)",[237,46460,46462],{"id":46461},"_23-백엔드-가드","2.3 백엔드 가드",[18,46464,46465,89,46467,46469,46470,4536,46473,4536,46475,46478,46479,46484,46485,46487],{},[32,46466,34124],{},[32,46468,33745],{}," 미들웨어를 18 라우트(",[32,46471,46472],{},"send",[32,46474,34551],{},[32,46476,46477],{},"sender-phones"," 등)에 일괄 적용. ",[21,46480,46481,46483],{},[32,46482,34109],{}," 모드 — POST\u002FPATCH\u002FPUT\u002FDELETE만 차단",", GET 조회는 허용. ",[32,46486,3987],{},"의 PATCH도 §7에서 인라인으로 차단.",[18,46489,46490],{},"예외:",[81,46492,46493,46498],{},[84,46494,46495,46497],{},[32,46496,4073],{}," — 승인 관련 문의 가능해야 함 → 미적용",[84,46499,46500,4420,46503,46505],{},[32,46501,46502],{},"\u002Fdispatch-history",[32,46504,22730],{}," — GET 전용이라 자동 통과",[237,46507,46509],{"id":46508},"_24-프런트-가드","2.4 프런트 가드",[18,46511,46512,46514],{},[32,46513,34894],{}," — 허용 경로 외 접근 시 자동 리다이렉트:",[18,46516,46517,9148],{},[21,46518,46519],{},"허용",[81,46521,46522,46527,46536,46541],{},[84,46523,46524,46526],{},[32,46525,5699],{}," (계약·회원 정보·문의)",[84,46528,46529,4420,46531,4420,46533,46535],{},[32,46530,5744],{},[32,46532,4457],{},[32,46534,26863],{}," (정적 문서)",[84,46537,46538,46540],{},[32,46539,18473],{}," (1:1 문의)",[84,46542,46543,46545],{},[32,46544,29216],{}," (로그인·가입 등)",[18,46547,46548,9148],{},[21,46549,46550,46551],{},"차단 → ",[32,46552,19458],{},[81,46554,46555],{},[84,46556,46557,4420,46559,4420,46561,4420,46563,4420,46565,4420,46567,4420,46569,4420,46571],{},[32,46558,11278],{},[32,46560,34052],{},[32,46562,22063],{},[32,46564,35270],{},[32,46566,35273],{},[32,46568,35276],{},[32,46570,35279],{},[32,46572,35282],{},[73,46574],{},[76,46576,46578,46579,46581],{"id":46577},"_3-운영자단-페이지-malgn-noti-admin-기획-md만-존재","3. 운영자단 페이지 (",[32,46580,7672],{},", 기획 MD만 존재)",[101,46583,46584,46596],{},[104,46585,46586],{},[107,46587,46588,46591,46594],{},[110,46589,46590],{},"기획 MD",[110,46592,46593],{},"라우트(예정)",[110,46595,46107],{},[126,46597,46598,46621,46639,46655],{},[107,46599,46600,46606,46614],{},[131,46601,46602],{},[26,46603,46605],{"href":46604},"..\u002F..\u002Fmalgn-noti-admin\u002Fdoc\u002Fpages\u002Fmember\u002Fcompany","member\u002Fcompany.md",[131,46607,46608,4420,46611],{},[32,46609,46610],{},"\u002Fadmin\u002Fmember\u002Fcompany",[32,46612,46613],{},"\u002F[id]",[131,46615,46616,46617,46620],{},"고객사 목록·",[21,46618,46619],{},"승인\u002F반려","·한도·차단",[107,46622,46623,46629,46636],{},[131,46624,46625],{},[26,46626,46628],{"href":46627},"..\u002F..\u002Fmalgn-noti-admin\u002Fdoc\u002Fpages\u002Fmember\u002Faccount","member\u002Faccount.md",[131,46630,46631,4420,46634],{},[32,46632,46633],{},"\u002Fadmin\u002Fmember\u002Faccount",[32,46635,46613],{},[131,46637,46638],{},"개별 사용자 계정·OTP 진단·임시 비번·2FA 초기화",[107,46640,46641,46647,46652],{},[131,46642,46643],{},[26,46644,46646],{"href":46645},"..\u002F..\u002Fmalgn-noti-admin\u002Fdoc\u002Fpages\u002Fmember\u002Faudit","member\u002Faudit.md",[131,46648,46649],{},[32,46650,46651],{},"\u002Fadmin\u002Fmember\u002Faudit",[131,46653,46654],{},"감사 로그",[107,46656,46657,46663,46668],{},[131,46658,46659],{},[26,46660,46662],{"href":46661},"..\u002F..\u002Fmalgn-noti-admin\u002Fdoc\u002Fpages\u002Fmember\u002Fblock","member\u002Fblock.md",[131,46664,46665],{},[32,46666,46667],{},"\u002Fadmin\u002Fmember\u002Fblock",[131,46669,46670],{},"강제 차단·복구",[18,46672,28038,46673,46676,46677,4703],{},[21,46674,46675],{},"운영자단 화면은 모두 미개발","(셸 + 기획만). 사업자 승인은 현재 라이브 DB 직접 UPDATE로만 처리 가능. ",[21,46678,46679],{},"승인 화면은 P0 1순위",[73,46681],{},[76,46683,46685,46686,4343],{"id":46684},"_4-api-엔드포인트-malgn-noti-api","4. API 엔드포인트 (",[32,46687,50],{},[237,46689,46691,46692,4343],{"id":46690},"_41-인증-srcroutesauthts","4.1 인증 (",[32,46693,22961],{},[101,46695,46696,46708],{},[104,46697,46698],{},[107,46699,46700,46702,46704,46706],{},[110,46701,22556],{},[110,46703,28786],{},[110,46705,28789],{},[110,46707,270],{},[126,46709,46710,46732,46751,46768,46787,46805,46826,46843,46861,46877,46892,46908,46924,46940],{},[107,46711,46712,46716,46721,46730],{},[131,46713,46714],{},[32,46715,22968],{},[131,46717,46718],{},[32,46719,46720],{},"{companyName, companyType?, loginid, password, name?, email?, phone?, niceSession?}",[131,46722,46723,46726,46727,46729],{},[32,46724,46725],{},"201 {data:{user, company, token}}",". companyType='corp'\u002F'sole'면 ",[32,46728,33549],{}," 자동, 그 외 'approved'",[131,46731,3869],{},[107,46733,46734,46738,46743,46749],{},[131,46735,46736],{},[32,46737,22974],{},[131,46739,46740],{},[32,46741,46742],{},"{companyId, loginid, password}",[131,46744,46745,46748],{},[32,46746,46747],{},"200 {data:{user, company, token}}"," (legacy — 사용 안 함)",[131,46750,3869],{},[107,46752,46753,46757,46761,46766],{},[131,46754,46755],{},[32,46756,30107],{},[131,46758,46759],{},[32,46760,30545],{},[131,46762,46763,46765],{},[32,46764,46747],{}," — loginid 전역 UNIQUE 기반 단일 매치",[131,46767,3869],{},[107,46769,46770,46774,46779,46784],{},[131,46771,46772],{},[32,46773,29660],{},[131,46775,46776],{},[32,46777,46778],{},"{email, purpose:'signup'\u002F'reset_password'\u002F'change_email'}",[131,46780,46781],{},[32,46782,46783],{},"200 {data:{sent, expiresAt, mockCode?}}",[131,46785,46786],{},"✅ (NHN 자격증명 등록 전까지 mock fallback)",[107,46788,46789,46793,46798,46803],{},[131,46790,46791],{},[32,46792,29663],{},[131,46794,46795],{},[32,46796,46797],{},"{email, purpose, code}",[131,46799,46800],{},[32,46801,46802],{},"200 {data:{verified}}",[131,46804,3869],{},[107,46806,46807,46811,46816,46820],{},[131,46808,46809],{},[32,46810,31450],{},[131,46812,46813],{},[32,46814,46815],{},"{phone, purpose:'signup'\u002F'reset_password'\u002F'change_phone'\u002F'contract_sign'}",[131,46817,46818],{},[32,46819,46783],{},[131,46821,46822,46823,46825],{},"✅ (자체 SMS OTP — NICE와 별개. §15에서 ",[32,46824,38570],{}," 추가)",[107,46827,46828,46832,46837,46841],{},[131,46829,46830],{},[32,46831,31453],{},[131,46833,46834],{},[32,46835,46836],{},"{phone, purpose, code}",[131,46838,46839],{},[32,46840,46802],{},[131,46842,3869],{},[107,46844,46845,46849,46854,46859],{},[131,46846,46847],{},[32,46848,32203],{},[131,46850,46851],{},[32,46852,46853],{},"{purpose:'signup'\u002F'change_phone'}",[131,46855,46856],{},[32,46857,46858],{},"200 {data:{sessionId, authUrl, mockMode}}",[131,46860,3869],{},[107,46862,46863,46867,46872,46875],{},[131,46864,46865],{},[32,46866,32227],{},[131,46868,46869,46870],{},"NICE form ",[32,46871,32233],{},[131,46873,46874],{},"HTML (자동 팝업 닫기)",[131,46876,3869],{},[107,46878,46879,46883,46885,46890],{},[131,46880,46881],{},[32,46882,32244],{},[131,46884,322],{},[131,46886,46887],{},[32,46888,46889],{},"200 {data:{state, name?, birthdate?, ...}}",[131,46891,3869],{},[107,46893,46894,46898,46903,46906],{},[131,46895,46896],{},[32,46897,45834],{},[131,46899,46900],{},[32,46901,46902],{},"{currentPassword, newPassword}",[131,46904,46905],{},"TBD",[131,46907,3931],{},[107,46909,46910,46915,46920,46922],{},[131,46911,46912],{},[32,46913,46914],{},"POST \u002Fauth\u002Fpassword\u002Freset",[131,46916,46917],{},[32,46918,46919],{},"{email, code, newPassword}",[131,46921,46905],{},[131,46923,3931],{},[107,46925,46926,46931,46936,46938],{},[131,46927,46928],{},[32,46929,46930],{},"POST \u002Fauth\u002Fagree-terms",[131,46932,46933],{},[32,46934,46935],{},"{terms:[{id, version, requiredYn}]}",[131,46937,46905],{},[131,46939,3931],{},[107,46941,46942,46947,46950,46952],{},[131,46943,46944],{},[32,46945,46946],{},"POST \u002Fauth\u002Flogout",[131,46948,46949],{},"(Bearer)",[131,46951,46905],{},[131,46953,46954],{},"⚪ — 클라이언트 쿠키 삭제만",[237,46956,46958,46959,4343],{"id":46957},"_42-현재-사용자-srcroutesmets","4.2 현재 사용자 (",[32,46960,32748],{},[101,46962,46963,46975],{},[104,46964,46965],{},[107,46966,46967,46969,46971,46973],{},[110,46968,22556],{},[110,46970,28786],{},[110,46972,28789],{},[110,46974,270],{},[126,46976,46977,46994,47015,47033],{},[107,46978,46979,46983,46986,46992],{},[131,46980,46981],{},[32,46982,21820],{},[131,46984,46985],{},"Bearer",[131,46987,46988,46991],{},[32,46989,46990],{},"200 {data:{user, company, ctxRole}}"," — TB_USER 13 + TB_COMPANY 17 컬럼 풀 노출(approvalState 포함)",[131,46993,3869],{},[107,46995,46996,47000,47005,47013],{},[131,46997,46998],{},[32,46999,32697],{},[131,47001,47002],{},[32,47003,47004],{},"{name?, phone?}",[131,47006,47007,89,47010,47012],{},[32,47008,47009],{},"200 {data:...}",[32,47011,46406],{},"면 403",[131,47014,3869],{},[107,47016,47017,47021,47026,47031],{},[131,47018,47019],{},[32,47020,32700],{},[131,47022,47023],{},[32,47024,47025],{},"{companyPhone?, billingEmail?, adReceive?}",[131,47027,47028,47030],{},[32,47029,47009],{}," — owner\u002Fadmin만 + 승인 게이트",[131,47032,3869],{},[107,47034,47035,47039,47044,47046],{},[131,47036,47037],{},[32,47038,45855],{},[131,47040,47041],{},[32,47042,47043],{},"{securityLoginYn, securityLoginMethod}",[131,47045,46905],{},[131,47047,3931],{},[237,47049,47051,47052,4343],{"id":47050},"_43-승인-게이트-미들웨어-srcmiddlewareapprovalts","4.3 승인 게이트 미들웨어 (",[32,47053,34124],{},[18,47055,47056,47059],{},[32,47057,47058],{},"requireApproved({method?: 'mutate-only' | 'all'})"," — 적용 라우트 18종:",[81,47061,47062,47072,47088,47096,47102],{},[84,47063,47064,4420,47066,4420,47068,4420,47070],{},[32,47065,34052],{},[32,47067,21826],{},[32,47069,21839],{},[32,47071,22056],{},[84,47073,47074,4420,47076,4420,47078,4420,47080,4420,47082,4420,47084,4420,47086],{},[32,47075,21849],{},[32,47077,22571],{},[32,47079,22584],{},[32,47081,22596],{},[32,47083,22620],{},[32,47085,22608],{},[32,47087,22632],{},[84,47089,47090,4420,47092,4420,47094],{},[32,47091,22059],{},[32,47093,22655],{},[32,47095,22718],{},[84,47097,47098,4420,47100],{},[32,47099,24552],{},[32,47101,23355],{},[84,47103,47104,4420,47106],{},[32,47105,22706],{},[32,47107,22693],{},[18,47109,47110],{},"미승인이면 변경(POST\u002FPATCH\u002FPUT\u002FDELETE) 차단 → 403 + 상황별 메시지(pending: 심사 후 가능, rejected: 사유 안내).",[237,47112,47114],{"id":47113},"_44-서비스-담당자-초대-미구현","4.4 서비스 담당자 초대 (미구현)",[101,47116,47117,47125],{},[104,47118,47119],{},[107,47120,47121,47123],{},[110,47122,22556],{},[110,47124,270],{},[126,47126,47127],{},[107,47128,47129,47140],{},[131,47130,47131,4420,47134,4420,47137],{},[32,47132,47133],{},"GET\u002FPOST \u002Fmanager-invites",[32,47135,47136],{},"POST \u002F{token}\u002Faccept",[32,47138,47139],{},"DELETE \u002F{id}",[131,47141,3931],{},[237,47143,47145,47146,47148],{"id":47144},"_45-계약서류-srcroutescontractsts-11","4.5 계약·서류 (",[32,47147,36248],{},", §11)",[101,47150,47151,47161],{},[104,47152,47153],{},[107,47154,47155,47157,47159],{},[110,47156,22556],{},[110,47158,270],{},[110,47160,7718],{},[126,47162,47163,47174,47185,47196,47207,47218],{},[107,47164,47165,47169,47171],{},[131,47166,47167],{},[32,47168,36268],{},[131,47170,3869],{},[131,47172,47173],{},"본 회사 계약 목록. §13 lazy auto-create (corp\u002Fsole + 0건 → 'initial' 자동)",[107,47175,47176,47180,47182],{},[131,47177,47178],{},[32,47179,36278],{},[131,47181,3869],{},[131,47183,47184],{},"전자서명 완료 → done + signed_at + expires_at=+2y. renew면 다른 done 자동 expired",[107,47186,47187,47191,47193],{},[131,47188,47189],{},[32,47190,36301],{},[131,47192,3869],{},[131,47194,47195],{},"본 회사 파일 목록. §13 자동 회복 (pending + biz 파일 ≥1 → reviewing)",[107,47197,47198,47202,47204],{},[131,47199,47200],{},[32,47201,36314],{},[131,47203,3869],{},[131,47205,47206],{},"멀티파트 PDF·10MB R2 적재 + DB insert. §12 kind=biz면 자동 `pending",[107,47208,47209,47213,47215],{},[131,47210,47211],{},[32,47212,36324],{},[131,47214,3869],{},[131,47216,47217],{},"R2 stream → application\u002Fpdf",[107,47219,47220,47224,47226],{},[131,47221,47222],{},[32,47223,36341],{},[131,47225,3869],{},[131,47227,47228],{},"R2 + DB 삭제 (사용자단은 rejected 상태에서만 호출 §14)",[18,47230,47231,47232,4703],{},"상세는 ",[26,47233,47235],{"href":47234},".\u002Fpages\u002FCONTRACT#8-api-%EC%97%94%EB%93%9C%ED%8F%AC%EC%9D%B8%ED%8A%B8--%EA%B5%AC%ED%98%84%EB%90%A8-11",".\u002Fpages\u002FCONTRACT.md §8",[237,47237,47239],{"id":47238},"_46-r2-bucket","4.6 R2 bucket",[101,47241,47242,47254],{},[104,47243,47244],{},[107,47245,47246,47249,47252],{},[110,47247,47248],{},"바인딩",[110,47250,47251],{},"bucket",[110,47253,4803],{},[126,47255,47256],{},[107,47257,47258,47262,47266],{},[131,47259,47260],{},[32,47261,35825],{},[131,47263,47264],{},[32,47265,35819],{},[131,47267,47268],{},"사업자등록증·대부업등록증·보험증권 등 계약 첨부 (§11)",[237,47270,47272,47273,4343],{"id":47271},"_47-운영자단-미구현-admin","4.7 운영자단 (미구현, ",[32,47274,21053],{},[101,47276,47277,47285],{},[104,47278,47279],{},[107,47280,47281,47283],{},[110,47282,22556],{},[110,47284,270],{},[126,47286,47287,47297,47310],{},[107,47288,47289,47295],{},[131,47290,47291,47294],{},[32,47292,47293],{},"GET \u002Fadmin\u002Fcompanies"," · 목록 + 필터",[131,47296,3931],{},[107,47298,47299,47307],{},[131,47300,47301,4420,47304],{},[32,47302,47303],{},"POST \u002Fadmin\u002Fcompanies\u002F{id}\u002Fapprove",[32,47305,47306],{},"POST \u002Freject {reason}",[131,47308,47309],{},"⚪ — 현재 라이브 DB 직접 UPDATE",[107,47311,47312,47318],{},[131,47313,47314,47317],{},[32,47315,47316],{},"GET \u002Fadmin\u002Fusers"," · 강제 비활성·2FA 초기화·임시 비번",[131,47319,3931],{},[73,47321],{},[76,47323,47325],{"id":47324},"_5-db-테이블-회원인증-관련","5. DB 테이블 (회원·인증 관련)",[237,47327,47329],{"id":47328},"_51-핵심-라이브-schemats","5.1 핵심 (라이브 + schema.ts)",[101,47331,47332,47346],{},[104,47333,47334],{},[107,47335,47336,47338,47341,47343],{},[110,47337,28308],{},[110,47339,47340],{},"핵심 컬럼",[110,47342,28258],{},[110,47344,47345],{},"라이브",[126,47347,47348,47369,47398,47412,47425,47438],{},[107,47349,47350,47354,47365,47367],{},[131,47351,47352],{},[32,47353,28064],{},[131,47355,47356,47357,4473,47360,4473,47362,47364],{},"name, ",[21,47358,47359],{},"company_type",[21,47361,20267],{},[21,47363,36785],{},", biz_no, biz_type, ceo_name, up_tae, up_jong, address, company_phone, billing_email, credit_balance, ad_receive, status",[131,47366,3869],{},[131,47368,3869],{},[107,47370,47371,47375,47394,47396],{},[131,47372,47373],{},[32,47374,20302],{},[131,47376,47377,47378,47381,47382,4473,47384,4473,47386,4473,47388,4473,47391,47393],{},"company_id, ",[21,47379,47380],{},"loginid(UNIQUE 전역)",", email, password_hash, name, phone, ",[21,47383,31940],{},[21,47385,31943],{},[21,47387,31946],{},[21,47389,47390],{},"ci(UNIQUE)",[21,47392,31952],{},", role, member_type, security_login_yn, security_login_method, join_state, status, last_login_at",[131,47395,3869],{},[131,47397,3869],{},[107,47399,47400,47405,47408,47410],{},[131,47401,47402],{},[32,47403,47404],{},"TB_COMPANY_SETTINGS",[131,47406,47407],{},"테넌트별 발송 설정",[131,47409,3869],{},[131,47411,3869],{},[107,47413,47414,47418,47421,47423],{},[131,47415,47416],{},[32,47417,29386],{},[131,47419,47420],{},"target_type(email\u002Fphone), target, code_hash, purpose, attempts, expires_at, consumed_at",[131,47422,3869],{},[131,47424,3869],{},[107,47426,47427,47431,47434,47436],{},[131,47428,47429],{},[32,47430,31969],{},[131,47432,47433],{},"request_no, transaction_id, ticket, iterators, state(pending\u002Fcompleted\u002Ffailed\u002Fexpired\u002Fconsumed), name, birthdate, gender, ci, di, mobile_no, mobile_co, expires_at, consumed 후 재사용 차단",[131,47435,3869],{},[131,47437,3869],{},[107,47439,47440,47445,47448,47450],{},[131,47441,47442],{},[32,47443,47444],{},"TB_SESSION",[131,47446,47447],{},"발급 JWT 추적용 (logout revoke 시)",[131,47449,3931],{},[131,47451,3869],{},[237,47453,47455],{"id":47454},"_52-멀티-계정초대-라이브-있음-schemats-미정의","5.2 멀티 계정·초대 (라이브 있음, schema.ts 미정의)",[101,47457,47458,47466],{},[104,47459,47460],{},[107,47461,47462,47464],{},[110,47463,28308],{},[110,47465,46107],{},[126,47467,47468,47478],{},[107,47469,47470,47475],{},[131,47471,47472],{},[32,47473,47474],{},"TB_MEMBER_VERIFICATION",[131,47476,47477],{},"멀티계정 본인 확인",[107,47479,47480,47484],{},[131,47481,47482],{},[32,47483,45738],{},[131,47485,47486],{},"서비스 담당자 초대 토큰",[237,47488,47490],{"id":47489},"_53-약관-라이브-미확정","5.3 약관 (라이브 미확정)",[101,47492,47493,47501],{},[104,47494,47495],{},[107,47496,47497,47499],{},[110,47498,28308],{},[110,47500,46107],{},[126,47502,47503,47513],{},[107,47504,47505,47510],{},[131,47506,47507],{},[32,47508,47509],{},"TB_TERMS",[131,47511,47512],{},"약관 정본 (추정)",[107,47514,47515,47519],{},[131,47516,47517],{},[32,47518,28079],{},[131,47520,47521],{},"동의 기록 (추정)",[18,47523,47524],{},"→ schema.ts 미정의. 후속 작업에서 확정.",[237,47526,47528],{"id":47527},"_54-계약-라이브-schemats-11","5.4 계약 (라이브 + schema.ts §11)",[101,47530,47531,47543],{},[104,47532,47533],{},[107,47534,47535,47537,47539,47541],{},[110,47536,28308],{},[110,47538,47340],{},[110,47540,28258],{},[110,47542,47345],{},[126,47544,47545,47561],{},[107,47546,47547,47551,47557,47559],{},[131,47548,47549],{},[32,47550,35831],{},[131,47552,47553,47554,47556],{},"company_id, title, version, ",[21,47555,36068],{},"(initial\u002Fdone\u002Frenew\u002Fexpired), status, signer_user_id, signed_at, expires_at",[131,47558,3869],{},[131,47560,3869],{},[107,47562,47563,47567,47586,47588],{},[131,47564,47565],{},[32,47566,35778],{},[131,47568,47569,47570,47572,47573,4361,47576,4361,47579,47582,47583,47585],{},"contract_id, ",[21,47571,14992],{},"(한국어 접두사로 종류 구분 — ",[32,47574,47575],{},"사업자등록증_",[32,47577,47578],{},"대부업등록증_",[32,47580,47581],{},"지급이행보증보험증권_","), size_bytes, ",[21,47584,26257],{},", uploaded_at",[131,47587,3869],{},[131,47589,3869],{},[18,47591,47592,47593,4703],{},"상세 스키마 + 정책: ",[26,47594,47596],{"href":47595},".\u002Fpages\u002FCONTRACT#9-db-%ED%85%8C%EC%9D%B4%EB%B8%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C--schemats-%EC%A0%95%EC%9D%98-11",".\u002Fpages\u002FCONTRACT.md §9",[237,47598,47600],{"id":47599},"_55-결제-ddl-이력","5.5 결제 \u002F DDL 이력",[101,47602,47603,47612],{},[104,47604,47605],{},[107,47606,47607,47610],{},[110,47608,47609],{},"테이블 \u002F 마이그레이션",[110,47611,270],{},[126,47613,47614,47623],{},[107,47615,47616,47621],{},[131,47617,47618],{},[32,47619,47620],{},"TB_PAYMENT_METHOD",[131,47622,3869],{},[107,47624,47625,47637],{},[131,47626,47627,4420,47629,4420,47631,4420,47633,4420,47635],{},[32,47628,23837],{},[32,47630,24556],{},[32,47632,30897],{},[32,47634,32581],{},[32,47636,33975],{},[131,47638,47639],{},"모두 라이브 적용 완료",[73,47641],{},[76,47643,47645],{"id":47644},"_6-정책-결정-사항","6. 정책 결정 사항",[15,47647,47648],{},[18,47649,47650,47651,4473,47653,4473,47655,47659],{},"상세: ",[26,47652,45566],{"href":45565},[26,47654,45576],{"href":45575},[26,47656,47658],{"href":47657},".\u002FWBS",".\u002FWBS.md"," §2-4·§2-6",[237,47661,47663,47664,4343],{"id":47662},"_61-회원-유형-company_type","6.1 회원 유형 (",[32,47665,47359],{},[18,47667,47668,47670,47671,47673,47674,47676],{},[32,47669,33543],{}," (법인사업자) · ",[32,47672,33560],{}," (개인사업자) · ",[32,47675,33576],{}," (개인) 3종. signup 시 전달, TB_COMPANY 적재.",[237,47678,47680],{"id":47679},"_62-결제-방식","6.2 결제 방식",[81,47682,47683,47693],{},[84,47684,47685,47686,26162,47689,47692],{},"법인·개인사업자: ",[21,47687,47688],{},"카드 충전",[21,47690,47691],{},"후불 결제"," 선택",[84,47694,47695,47696],{},"개인: ",[21,47697,47698],{},"카드 충전만",[237,47700,47702],{"id":47701},"_63-가입-직후-승인-구현-완료","6.3 가입 직후 승인 (구현 완료)",[101,47704,47705,47720],{},[104,47706,47707],{},[107,47708,47709,47711,47716,47718],{},[110,47710,33525],{},[110,47712,47713,47715],{},[32,47714,20267],{}," 초기값",[110,47717,33531],{},[110,47719,33534],{},[126,47721,47722,47740,47757],{},[107,47723,47724,47729,47736,47738],{},[131,47725,47726,47727],{},"법인사업자 ",[32,47728,33543],{},[131,47730,47731,47733,47734,46374],{},[32,47732,27043],{}," → biz 첨부 시 ",[32,47735,36757],{},[131,47737,5788],{},[131,47739,5788],{},[107,47741,47742,47747,47753,47755],{},[131,47743,47744,47745],{},"개인사업자 ",[32,47746,33560],{},[131,47748,47749,47733,47751,46374],{},[32,47750,27043],{},[32,47752,36757],{},[131,47754,5788],{},[131,47756,5788],{},[107,47758,47759,47763,47767,47769],{},[131,47760,33916,47761],{},[32,47762,33576],{},[131,47764,47765],{},[32,47766,33595],{},[131,47768,3869],{},[131,47770,3869],{},[18,47772,47773,47774,47776,47777,4339,47779,47781,47782,47784],{},"운영자 승인 → ",[32,47775,33595],{}," 또는 반려 → ",[32,47778,33601],{},[32,47780,36785],{},". 반려 후 재첨부 시 자동 ",[32,47783,36757],{},"으로 재진입. 사용자단·백엔드 둘 다 게이트 적용 완료. 운영자단 승인 화면은 미구현(P0).",[237,47786,47788],{"id":47787},"_64-loginid-전역-unique","6.4 loginid 전역 UNIQUE",[18,47790,47791,47793,47794,47796],{},[32,47792,30114],{}," 전역 UNIQUE (0003). \"한 이메일 = 한 회사 = 한 로그인\". 회원가입 마법사는 ",[32,47795,30556],{},"로 발급.",[237,47798,47800],{"id":47799},"_65-멀티-계정","6.5 멀티 계정",[18,47802,47803,47804,47806],{},"법인·개인사업자만 주계정 + 보조계정 추가 가능. 개인은 ",[32,47805,19455],{}," 탭 미노출(현재 코드 모두 노출 — 후속).",[237,47808,47810],{"id":47809},"_66-비밀번호-정책","6.6 비밀번호 정책",[18,47812,47813,47816],{},[32,47814,47815],{},"min 8자"," (현 검증). 영문·숫자·특수문자 조합 8자 이상은 표시 문구만, 검증은 없음.",[237,47818,47820],{"id":47819},"_67-본인-확인-otp","6.7 본인 확인 \u002F OTP",[101,47822,47823,47835],{},[104,47824,47825],{},[107,47826,47827,47829,47832],{},[110,47828,25595],{},[110,47830,47831],{},"단순 OTP (소유 검증)",[110,47833,47834],{},"본인 확인 (NICE)",[126,47836,47837,47848],{},[107,47838,47839,47841,47846],{},[131,47840,7128],{},[131,47842,36626,47843,47845],{},[32,47844,45642],{}," (signup·reset·change)",[131,47847,322],{},[107,47849,47850,47853,47861],{},[131,47851,47852],{},"휴대폰",[131,47854,36626,47855,47858,47859,4343],{},[32,47856,47857],{},"\u002Fauth\u002Fphone-code\u002F*"," (signup·reset·change·",[21,47860,38570],{},[131,47862,36626,47863,47865],{},[32,47864,45645],{}," (signup의 Step 4 메인)",[18,47867,47868,47869,47872,47873,47876],{},"자체 SMS OTP는 단순 휴대폰 보유 확인 — 비밀번호 재설정·",[21,47870,47871],{},"계약서 전자서명 직전 본인 확인","(§15) 등에 활용. ",[21,47874,47875],{},"회원가입 본인 확인은 NICE M(휴대폰)으로 일원화"," (NICE 자격증명 발급 후 real 모드 전환 — 현재 mock. §16 참조).",[237,47878,47880],{"id":47879},"_68-외부-자격증명-운영-상태-16","6.8 외부 자격증명 운영 상태 (§16)",[101,47882,47883,47896],{},[104,47884,47885],{},[107,47886,47887,47890,47893],{},[110,47888,47889],{},"서비스",[110,47891,47892],{},"현재",[110,47894,47895],{},"보류 사유",[126,47897,47898,47914],{},[107,47899,47900,47905,47911],{},[131,47901,47902],{},[21,47903,47904],{},"NICE 본인확인",[131,47906,47907,47908,47910],{},"mock (",[32,47909,38976],{},"). CLIENT_ID\u002FSECRET\u002FRETURN_URL 3 secret은 등록 후 보관",[131,47912,47913],{},"콘솔 IP 화이트리스트(에러 1007). 사용자 콘솔 작업 대기",[107,47915,47916,47920,47925],{},[131,47917,47918],{},[21,47919,38885],{},[131,47921,47907,47922,47924],{},[32,47923,25204],{},"). AppKey만 수령",[131,47926,47927],{},"User Access Key + Secret Access Key 미수령 + Bearer 토큰 인증으로 어댑터 재작성 필요",[18,47929,47930,47931,4703],{},"키 수령·정책 해결 시 mock 제거 + 어댑터 교체 + e2e — 상세는 ",[26,47932,47934],{"href":47933},".\u002Fhistory\u002Fhistory.20260602#16-%EC%9A%B4%EC%98%81-%EB%85%B8%ED%8A%B8--nice--nhn-notification-hub-%EC%9E%90%EA%B2%A9%EC%A6%9D%EB%AA%85-%EC%8B%9C%EB%8F%84%EC%99%80-%EB%B3%B4%EB%A5%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C-%EC%9A%B4%EC%98%81-%EB%B3%80%EA%B2%BD","history.20260602.md §16",[237,47936,47938],{"id":47937},"_69-nice-본인확인-ci-중복-가입-차단","6.9 NICE 본인확인 + CI 중복 가입 차단",[18,47940,47941,47942,47944,47945,47947],{},"NICE 응답의 ",[32,47943,31949],{},"는 사이트 간 동일인 식별자. ",[32,47946,31919],{},"로 한 사람이 두 회사에 가입 불가. signup 시 사전 검사 — 중복이면 409 \"이미 가입된 사용자입니다. 비밀번호 재설정으로 진행해 주세요.\"",[73,47949],{},[76,47951,47953],{"id":47952},"_7-보안-모델","7. 보안 모델",[101,47955,47956,47966],{},[104,47957,47958],{},[107,47959,47960,47962,47964],{},[110,47961,6889],{},[110,47963,47892],{},[110,47965,7718],{},[126,47967,47968,47983,47995,48013,48025,48037,48053,48068,48081,48093],{},[107,47969,47970,47975,47980],{},[131,47971,47972],{},[21,47973,47974],{},"비밀번호 저장",[131,47976,47977,47978,4343],{},"PBKDF2-SHA256 (",[32,47979,22938],{},[131,47981,47982],{},"OK",[107,47984,47985,47990,47993],{},[131,47986,47987],{},[21,47988,47989],{},"JWT",[131,47991,47992],{},"HS256, 만료 7일, sub=userId, cid=companyId, role",[131,47994,47982],{},[107,47996,47997,48002,48010],{},[131,47998,47999],{},[21,48000,48001],{},"JWT 저장",[131,48003,48004,48006,48007],{},[32,48005,28722],{}," 쿠키, SameSite=Lax, secure-in-prod, HttpOnly ",[21,48008,48009],{},"아님",[131,48011,48012],{},"백엔드 Set-Cookie 미사용 — 후속 강화",[107,48014,48015,48020,48023],{},[131,48016,48017],{},[21,48018,48019],{},"OTP 코드 저장",[131,48021,48022],{},"SHA-256(target|purpose|code) 해시만 (평문 금지)",[131,48024,47982],{},[107,48026,48027,48032,48035],{},[131,48028,48029],{},[21,48030,48031],{},"NICE 본인 확인",[131,48033,48034],{},"AES-256-GCM + PBKDF2 + HMAC 무결성 검증 (Web Crypto)",[131,48036,47982],{},[107,48038,48039,48044,48050],{},[131,48040,48041],{},[21,48042,48043],{},"승인 게이트",[131,48045,48046,48047,48049],{},"DB ",[32,48048,20267],{}," + 백엔드 미들웨어 + 프런트 미들웨어",[131,48051,48052],{},"OK (운영자단 승인 UI 미)",[107,48054,48055,48060,48066],{},[131,48056,48057],{},[21,48058,48059],{},"세션 revoke",[131,48061,48062,48063,48065],{},"클라이언트 쿠키 삭제만 (",[32,48064,47444],{}," 미구현)",[131,48067,3931],{},[107,48069,48070,48075,48078],{},[131,48071,48072],{},[21,48073,48074],{},"CORS 화이트리스트",[131,48076,48077],{},"백엔드 미설정 → 모두 허용",[131,48079,48080],{},"후속",[107,48082,48083,48087,48090],{},[131,48084,48085],{},[21,48086,30019],{},[131,48088,48089],{},"발송·검증·로그인 모두 없음",[131,48091,48092],{},"후속 (Cloudflare KV·Durable Objects 검토)",[107,48094,48095,48099,48104],{},[131,48096,48097],{},[21,48098,46654],{},[131,48100,48101,48102,4343],{},"mutating 액션 미적재 (",[32,48103,20390],{},[131,48105,48080],{},[73,48107],{},[76,48109,48111],{"id":48110},"_8-현-구현-상태-한눈에","8. 현 구현 상태 한눈에",[101,48113,48114,48128],{},[104,48115,48116],{},[107,48117,48118,48120,48123,48126],{},[110,48119,6889],{},[110,48121,48122],{},"백엔드",[110,48124,48125],{},"프런트",[110,48127,7718],{},[126,48129,48130,48142,48154,48165,48176,48188,48200,48212,48224,48238,48253,48270,48284,48301,48315,48329,48340,48350,48362,48376,48391,48404,48417,48432,48445,48457],{},[107,48131,48132,48135,48137,48139],{},[131,48133,48134],{},"회원가입 (signup)",[131,48136,3869],{},[131,48138,3869],{},[131,48140,48141],{},"companyType 전달, Step 5 유형 분기",[107,48143,48144,48147,48149,48151],{},[131,48145,48146],{},"이메일 OTP",[131,48148,3869],{},[131,48150,3869],{},[131,48152,48153],{},"NHN 자격증명 발급 후 실 메일 발송",[107,48155,48156,48158,48160,48162],{},[131,48157,31436],{},[131,48159,3869],{},[131,48161,3869],{},[131,48163,48164],{},"signup Step 4는 NICE로 대체",[107,48166,48167,48169,48171,48173],{},[131,48168,47904],{},[131,48170,3869],{},[131,48172,3869],{},[131,48174,48175],{},"mock 모드 — NICE 자격증명 발급 후 real",[107,48177,48178,48181,48183,48185],{},[131,48179,48180],{},"로그인 (이메일 + 비번)",[131,48182,3869],{},[131,48184,3869],{},[131,48186,48187],{},"login-by-email 단일 매치",[107,48189,48190,48193,48195,48197],{},[131,48191,48192],{},"\u002Fme 조회",[131,48194,3869],{},[131,48196,3869],{},[131,48198,48199],{},"풀 컬럼(승인 정보 포함)",[107,48201,48202,48205,48207,48209],{},[131,48203,48204],{},"사용자 정보 변경 (PATCH \u002Fme)",[131,48206,3869],{},[131,48208,3869],{},[131,48210,48211],{},"name·phone — 승인 게이트",[107,48213,48214,48217,48219,48221],{},[131,48215,48216],{},"회사 정보 변경 (PATCH \u002Fme\u002Fcompany)",[131,48218,3869],{},[131,48220,3869],{},[131,48222,48223],{},"companyPhone·billingEmail·adReceive — owner\u002Fadmin + 승인 게이트",[107,48225,48226,48231,48233,48235],{},[131,48227,48228],{},[21,48229,48230],{},"승인 게이트 (DB + 18 라우트 + 프런트)",[131,48232,3869],{},[131,48234,3869],{},[131,48236,48237],{},"운영자단 승인 UI 미 — DB 직접 UPDATE로 임시",[107,48239,48240,48246,48248,48250],{},[131,48241,48242,48245],{},[21,48243,48244],{},"reviewing 자동 전이"," (biz 첨부 시)",[131,48247,3869],{},[131,48249,3869],{},[131,48251,48252],{},"§12 + §13 lazy 회복",[107,48254,48255,48260,48262,48264],{},[131,48256,48257],{},[21,48258,48259],{},"계약·서류 R2 업로드 + DB",[131,48261,3869],{},[131,48263,3869],{},[131,48265,48266,48267,48269],{},"§11 — ",[32,48268,45813],{}," 5 라우트 + FILES R2 bucket",[107,48271,48272,48277,48279,48281],{},[131,48273,48274],{},[21,48275,48276],{},"이용계약 자동 'initial' 생성",[131,48278,3869],{},[131,48280,322],{},[131,48282,48283],{},"signup auto-create (§11) + lazy backfill (§13)",[107,48285,48286,48291,48293,48295],{},[131,48287,48288],{},[21,48289,48290],{},"계약 서명 다이얼로그 휴대폰 본인인증",[131,48292,3869],{},[131,48294,3869],{},[131,48296,48297,48298,48300],{},"§15 — ",[32,48299,38570],{}," purpose + 공인인증서 탭 제거",[107,48302,48303,48308,48310,48312],{},[131,48304,48305],{},[21,48306,48307],{},"사업자등록증 행 상태 배지 + 반려 시 삭제",[131,48309,3869],{},[131,48311,3869],{},[131,48313,48314],{},"§14",[107,48316,48317,48320,48323,48326],{},[131,48318,48319],{},"로그아웃",[131,48321,48322],{},"클라이언트 쿠키 삭제",[131,48324,48325],{},"⚪ GNB 미연동",[131,48327,48328],{},"30분 작업",[107,48330,48331,48333,48335,48337],{},[131,48332,17628],{},[131,48334,3931],{},[131,48336,3931],{},[131,48338,48339],{},"OTP 인프라 재활용 — 다음 작업 후보",[107,48341,48342,48344,48346,48348],{},[131,48343,19166],{},[131,48345,3931],{},[131,48347,3931],{},[131,48349],{},[107,48351,48352,48355,48357,48359],{},[131,48353,48354],{},"보안로그인 (2FA)",[131,48356,3931],{},[131,48358,3931],{},[131,48360,48361],{},"OTP 인프라 재활용",[107,48363,48364,48367,48369,48372],{},[131,48365,48366],{},"약관 동의 적재",[131,48368,3931],{},[131,48370,48371],{},"화면용 토스트",[131,48373,48374],{},[32,48375,28079],{},[107,48377,48378,48381,48383,48386],{},[131,48379,48380],{},"이메일 변경 (OTP)",[131,48382,3931],{},[131,48384,48385],{},"다이얼로그만",[131,48387,48388],{},[32,48389,48390],{},"POST \u002Fme\u002Femail-change\u002F*",[107,48392,48393,48395,48397,48400],{},[131,48394,46072],{},[131,48396,3931],{},[131,48398,48399],{},"화면 더미",[131,48401,48402],{},[32,48403,29403],{},[107,48405,48406,48408,48410,48413],{},[131,48407,33166],{},[131,48409,3931],{},[131,48411,48412],{},"\"곧 지원\" 안내",[131,48414,48415],{},[32,48416,33455],{},[107,48418,48419,48422,48424,48426],{},[131,48420,48421],{},"운영자단 승인 화면",[131,48423,3931],{},[131,48425,3931],{},[131,48427,48428,48429],{},"운영자단 화면 전체 미개발 — ",[21,48430,48431],{},"P0 1순위",[107,48433,48434,48437,48440,48442],{},[131,48435,48436],{},"실 메일 발송 (NHN)",[131,48438,48439],{},"mock",[131,48441,322],{},[131,48443,48444],{},"NHN Notification Hub 자격증명 + 어댑터 재작성 (§16)",[107,48446,48447,48450,48452,48454],{},[131,48448,48449],{},"실 SMS 발송 (NHN)",[131,48451,48439],{},[131,48453,322],{},[131,48455,48456],{},"동일",[107,48458,48459,48462,48464,48466],{},[131,48460,48461],{},"실 NICE 호출",[131,48463,48439],{},[131,48465,322],{},[131,48467,48468],{},"NICE 콘솔 IP 정책 해결 + secret 보관 중 (§16)",[73,48470],{},[76,48472,48474],{"id":48473},"_9-알려진-한계-다음-작업","9. 알려진 한계 \u002F 다음 작업",[237,48476,48478],{"id":48477},"p0-인증-폐쇄-루프","P0 — 인증 폐쇄 루프",[6674,48480,48481,48498,48503,48511,48520],{},[84,48482,48483,48486,48487,48489,48490,48493,48494,48497],{},[21,48484,48485],{},"운영자단 사업자 승인 화면"," (P0 1순위) — 현재 라이브 DB 직접 UPDATE로만 처리. ",[32,48488,41169],{}," 라우트 + UI. 승인 시 ",[32,48491,48492],{},"reviewing → approved",", 반려 시 ",[32,48495,48496],{},"reviewing → rejected + rejected_reason",". WBS 5-4-3.",[84,48499,48500,48502],{},[21,48501,3655],{}," — User Access Key 수령 시 어댑터 OAuth\u002FBearer로 교체 + e2e (§16).",[84,48504,48505,89,48508,48510],{},[21,48506,48507],{},"로그아웃 GNB 실 액션",[32,48509,29424],{}," 연결 (30분).",[84,48512,48513,48515,48516,48519],{},[21,48514,17628],{}," — OTP 인프라 재활용, ",[32,48517,48518],{},"purpose='reset_password'"," (2~3시간).",[84,48521,48522,48527,48528,48530],{},[20367,48523,48524],{},[21,48525,48526],{},"계약·서류 업로드 라우트 + R2"," — ✅ §11에서 완료. ",[32,48529,45813],{}," 5 라우트 + R2 bucket + 사용자단 연동.",[237,48532,48534],{"id":48533},"p1-정책-통합","P1 — 정책 통합",[6674,48536,48537,48546,48556,48562,48568,48574],{"start":8291},[84,48538,48539,89,48541,4339,48543,48545],{},[21,48540,48366],{},[32,48542,47509],{},[32,48544,28079],{}," 라우트 + signup 통합",[84,48547,48548,89,48551,4536,48553,48555],{},[21,48549,48550],{},"개인 유형 LNB\u002F메뉴 분기",[32,48552,19455],{},[32,48554,19458],{}," 등 사업자 전용 메뉴 숨김",[84,48557,48558,48561],{},[21,48559,48560],{},"사업자등록번호 체크섬"," — 프런트 검증",[84,48563,48564,48567],{},[21,48565,48566],{},"승인 완료 자동 알림"," — 이메일·SMS trigger",[84,48569,48570,48573],{},[21,48571,48572],{},"GNB 메뉴 항목 disabled (미승인)"," — 시각적으로 비활성화 (현재는 미들웨어 리다이렉트만)",[84,48575,48576,48578],{},[21,48577,35435],{}," — WebSocket 또는 폴링",[237,48580,48582],{"id":48581},"p2-계정-관리","P2 — 계정 관리",[6674,48584,48585,48594,48601,48610,48619],{"start":8349},[84,48586,48587,48593],{},[21,48588,48589,4339,48591],{},[32,48590,45834],{},[32,48592,45821],{}," — 비밀번호 변경",[84,48595,48596,89,48599],{},[21,48597,48598],{},"이메일 변경 OTP",[32,48600,33441],{},[84,48602,48603,48609],{},[21,48604,48605,4339,48607],{},[32,48606,45855],{},[32,48608,19452],{}," — 2FA",[84,48611,48612,48618],{},[21,48613,48614,4339,48616],{},[32,48615,29403],{},[32,48617,19455],{}," — 서비스 담당자 초대",[84,48620,48621,48626],{},[21,48622,48623,48625],{},[32,48624,33455],{}," (회원 탈퇴)"," — soft-delete + 데이터 정책",[237,48628,48630],{"id":48629},"p3-외부-의존","P3 — 외부 의존",[6674,48632,48633,48642,48648],{"start":8525},[84,48634,48635,89,48638,48641],{},[21,48636,48637],{},"NICE 통합인증 계약 + Outbound IP 협상",[26,48639,48640],{"href":45575},".\u002FNICE_AUTH.md §9·§10"," 참조",[84,48643,48644,48647],{},[21,48645,48646],{},"NHN 이메일 자격증명 등록"," — 실 메일 발송 활성화",[84,48649,48650,48653],{},[21,48651,48652],{},"NHN SMS 자격증명 등록"," — 비밀번호 재설정·휴대폰 변경 OTP 실 발송",[237,48655,48657],{"id":48656},"위생적-작업","위생적 작업",[6674,48659,48660,48682,48688],{"start":8550},[84,48661,48662,89,48667,4536,48669,4536,48671,4536,48673,4536,48675,6685,48677,4536,48679,48681],{},[21,48663,48664,48666],{},[32,48665,28258],{},"에 누락된 회원·인증 테이블 정의",[32,48668,47444],{},[32,48670,47509],{},[32,48672,28079],{},[32,48674,47474],{},[32,48676,45738],{},[32,48678,35831],{},[32,48680,35778],{},"은 §11에서 추가 완료)",[84,48683,48684,48687],{},[21,48685,48686],{},"운영 절차 다중화"," — Aurora SG 접근 다중화 (Cloudflare Tunnel·RDS Proxy 등)",[84,48689,48690,48693],{},[21,48691,48692],{},"NICE secret 회전"," (§16.1) — IP 정책 해결 시점에 CLIENT_SECRET 회전 권장 (오늘 채팅에 평문 노출)",{"title":4208,"searchDepth":4209,"depth":4209,"links":48695},[48696,48706,48713,48715,48730,48737,48749,48750,48751],{"id":45598,"depth":4212,"text":48697,"children":48698},"1. 사용자단 페이지 (malgn-noti)",[48699,48701,48703,48704,48705],{"id":45604,"depth":4209,"text":48700},"1.1 가입·로그인·재설정 (인증 게이트 — meta.auth: false)",{"id":45742,"depth":4209,"text":48702},"1.2 계정 관리 (\u002Faccount\u002F*, 인증 필요)",{"id":45983,"depth":4209,"text":45984},{"id":46095,"depth":4209,"text":46096},{"id":46225,"depth":4209,"text":46226},{"id":46274,"depth":4212,"text":46275,"children":48707},[48708,48710,48711,48712],{"id":46283,"depth":4209,"text":48709},"2.1 승인 상태 (approval_state) — 4단계 (§7·§12)",{"id":46383,"depth":4209,"text":46384},{"id":46461,"depth":4209,"text":46462},{"id":46508,"depth":4209,"text":46509},{"id":46577,"depth":4212,"text":48714},"3. 운영자단 페이지 (malgn-noti-admin, 기획 MD만 존재)",{"id":46684,"depth":4212,"text":48716,"children":48717},"4. API 엔드포인트 (malgn-noti-api)",[48718,48720,48722,48724,48725,48727,48728],{"id":46690,"depth":4209,"text":48719},"4.1 인증 (src\u002Froutes\u002Fauth.ts)",{"id":46957,"depth":4209,"text":48721},"4.2 현재 사용자 (src\u002Froutes\u002Fme.ts)",{"id":47050,"depth":4209,"text":48723},"4.3 승인 게이트 미들웨어 (src\u002Fmiddleware\u002Fapproval.ts)",{"id":47113,"depth":4209,"text":47114},{"id":47144,"depth":4209,"text":48726},"4.5 계약·서류 (src\u002Froutes\u002Fcontracts.ts, §11)",{"id":47238,"depth":4209,"text":47239},{"id":47271,"depth":4209,"text":48729},"4.7 운영자단 (미구현, \u002Fadmin\u002F*)",{"id":47324,"depth":4212,"text":47325,"children":48731},[48732,48733,48734,48735,48736],{"id":47328,"depth":4209,"text":47329},{"id":47454,"depth":4209,"text":47455},{"id":47489,"depth":4209,"text":47490},{"id":47527,"depth":4209,"text":47528},{"id":47599,"depth":4209,"text":47600},{"id":47644,"depth":4212,"text":47645,"children":48738},[48739,48741,48742,48743,48744,48745,48746,48747,48748],{"id":47662,"depth":4209,"text":48740},"6.1 회원 유형 (company_type)",{"id":47679,"depth":4209,"text":47680},{"id":47701,"depth":4209,"text":47702},{"id":47787,"depth":4209,"text":47788},{"id":47799,"depth":4209,"text":47800},{"id":47809,"depth":4209,"text":47810},{"id":47819,"depth":4209,"text":47820},{"id":47879,"depth":4209,"text":47880},{"id":47937,"depth":4209,"text":47938},{"id":47952,"depth":4212,"text":47953},{"id":48110,"depth":4212,"text":48111},{"id":48473,"depth":4212,"text":48474,"children":48752},[48753,48754,48755,48756,48757],{"id":48477,"depth":4209,"text":48478},{"id":48533,"depth":4209,"text":48534},{"id":48581,"depth":4209,"text":48582},{"id":48629,"depth":4209,"text":48630},{"id":48656,"depth":4209,"text":48657},{},"\u002Fmembership",{"title":45546,"description":4208},"MEMBERSHIP","7MnerjMJknj9nVhY0a2G8bUqYiVTNdN5XSgcW7nY8l8",{"id":48764,"title":48765,"body":48766,"description":4208,"extension":4257,"meta":51086,"navigation":4259,"path":51087,"seo":51088,"stem":51089,"__hash__":51090},"docs\u002FNICE_AUTH.md","NICE 통합인증(휴대폰 본인확인) — 적용 가이드",{"type":8,"value":48767,"toc":51038},[48768,48771,48816,48818,48822,48918,48924,48926,48930,48933,49003,49018,49020,49024,49030,49032,49036,49042,49103,49105,49109,49116,49121,49127,49132,49168,49179,49184,49279,49290,49297,49301,49307,49311,49397,49442,49446,49491,49498,49502,49538,49552,49559,49568,49574,49584,49591,49595,49600,49604,49650,49655,49701,49703,49707,49711,49717,49720,49726,49730,49736,49740,49779,49782,49784,49788,49948,49952,49973,49975,49979,49986,50032,50038,50152,50159,50193,50197,50203,50210,50217,50494,50501,50504,50575,50577,50581,50588,50595,50601,50618,50624,50707,50716,50720,50723,50729,50733,50762,50764,50768,50867,50872,50874,50878,51011,51013,51017,51024,51035],[11,48769,48765],{"id":48770},"nice-통합인증휴대폰-본인확인-적용-가이드",[15,48772,48773,48806,48811],{},[18,48774,48775,47,48778,48782,48783,48786,48787,48790,48791,47,48794,48798,48799,48802,48803,48805],{},[21,48776,48777],{},"정본 출처",[26,48779,48780],{"href":48780,"rel":48781},"https:\u002F\u002Fauth-guide.niceid.co.kr\u002F",[30]," — NICE 통합인증 API 가이드 \u002F 명세서 \u002F 응답코드\n",[21,48784,48785],{},"우리 적용 범위",": 회원가입 Step 4 \"휴대폰 본인 인증\"을 자체 SMS OTP에서 NICE ",[21,48788,48789],{},"휴대폰(M)"," 으로 교체.\n",[21,48792,48793],{},"연관",[26,48795,48797],{"href":48796},".\u002FMEMBERSHIP",".\u002FMEMBERSHIP.md"," §6.7·§6.8, ",[26,48800,48801],{"href":45565},".\u002FSIGNUP.md"," §8 #4b·#8, ",[26,48804,45586],{"href":45585}," §5·§16",[18,48807,48808,48810],{},[21,48809,45592],{},": 2026-06-02 (§9.1 자격증명 등록 시도 + 1007 IP 미해결로 mock 복귀 반영)",[18,48812,48813,48815],{},[21,48814,45592],{},": 2026-06-02",[73,48817],{},[76,48819,48821],{"id":48820},"_1-무엇이-다른가-자체-sms-otp-vs-nice-본인확인","1. 무엇이 다른가 (자체 SMS OTP vs NICE 본인확인)",[101,48823,48824,48842],{},[104,48825,48826],{},[107,48827,48828,48830,48836],{},[110,48829,6889],{},[110,48831,48832,48835],{},[21,48833,48834],{},"자체 SMS OTP"," (현재 구현)",[110,48837,48838,48841],{},[21,48839,48840],{},"NICE 휴대폰 본인확인 (M)"," (도입 예정)",[126,48843,48844,48855,48866,48877,48888,48899,48908],{},[107,48845,48846,48849,48852],{},[131,48847,48848],{},"확인하는 것",[131,48850,48851],{},"\"이 휴대폰 번호를 누가 가지고 있는가\"",[131,48853,48854],{},"\"이 사람이 진짜 본인인가 (실명·생년월일·CI)\"",[107,48856,48857,48860,48863],{},[131,48858,48859],{},"필요한 정보",[131,48861,48862],{},"휴대폰 번호",[131,48864,48865],{},"통신사 + 이름 + 주민등록번호 + 휴대폰 번호",[107,48867,48868,48871,48874],{},[131,48869,48870],{},"응답 데이터",[131,48872,48873],{},"검증 통과 여부(boolean)",[131,48875,48876],{},"이름 · 생년월일 · 성별 · CI · DI · 통신사 · 휴대폰",[107,48878,48879,48882,48885],{},[131,48880,48881],{},"외부 비용",[131,48883,48884],{},"0 (자체 SMS 발송)",[131,48886,48887],{},"NICE 건당 과금 (계약별)",[107,48889,48890,48893,48896],{},[131,48891,48892],{},"실명 확인 효력",[131,48894,48895],{},"❌ 없음",[131,48897,48898],{},"✅ 법적 본인 확인",[107,48900,48901,48904,48906],{},[131,48902,48903],{},"중복 가입 방지 (DI)",[131,48905,5788],{},[131,48907,3869],{},[107,48909,48910,48913,48915],{},[131,48911,48912],{},"통신3사 PASS 앱",[131,48914,5788],{},[131,48916,48917],{},"❌ (NICE 통합인증에서는 미지원 — 표준창 내 직접 입력)",[18,48919,28038,48920,48923],{},[21,48921,48922],{},"자체 SMS OTP는 비밀번호 재설정·이메일 변경 등 단순 검증에 유지",". 회원가입의 본인 확인은 NICE로 분리.",[73,48925],{},[76,48927,48929],{"id":48928},"_2-nice-통합인증-인증-수단-종류","2. NICE 통합인증 인증 수단 종류",[18,48931,48932],{},"NICE 통합인증 API는 4종 수단을 선택형으로 제공 (한 표준창에서 사용자가 선택).",[101,48934,48935,48948],{},[104,48936,48937],{},[107,48938,48939,48942,48945],{},[110,48940,48941],{},"코드",[110,48943,48944],{},"수단",[110,48946,48947],{},"우리 적용",[126,48949,48950,48965,48978,48990],{},[107,48951,48952,48957,48960],{},[131,48953,48954],{},[32,48955,48956],{},"M",[131,48958,48959],{},"휴대폰 본인확인",[131,48961,36626,48962],{},[21,48963,48964],{},"회원가입 Step 4 기본",[107,48966,48967,48972,48975],{},[131,48968,48969],{},[32,48970,48971],{},"F",[131,48973,48974],{},"금융인증서",[131,48976,48977],{},"선택 노출 가능",[107,48979,48980,48985,48988],{},[131,48981,48982],{},[32,48983,48984],{},"U",[131,48986,48987],{},"공동인증서",[131,48989,48977],{},[107,48991,48992,48997,49000],{},[131,48993,48994],{},[32,48995,48996],{},"I",[131,48998,48999],{},"아이핀",[131,49001,49002],{},"노출 안 함 (사용률 낮음)",[18,49004,49005,49006,49009,49010,49013,49014,49017],{},"우리 ",[32,49007,49008],{},"svc_types","는 처음에 ",[32,49011,49012],{},"[\"M\"]"," 단독으로 출시 → 사용자 피드백 보고 ",[32,49015,49016],{},"[\"M\", \"F\", \"U\"]","로 확장.",[73,49019],{},[76,49021,49023],{"id":49022},"_3-전체-시퀀스-5-단계","3. 전체 시퀀스 (5 단계)",[5251,49025,49028],{"className":49026,"code":49027,"language":5256},[5254],"[ 우리 워커 ]              [ NICE 서버 ]            [ 사용자 브라우저 ]\n   │                          │                          │\n   │ 1. POST \u002Fauth\u002Ftoken     ─►│                          │\n   │ ◄── access_token, ticket │                          │\n   │                          │                          │\n   │ 2. POST \u002Fauth\u002Furl       ─►│                          │\n   │ ◄── auth_url             │                          │\n   │                          │                          │\n   │ ──── auth_url 응답 ────────────────────────────────►│\n   │                          │                          │\n   │                          │ 3. 표준창 팝업 (사용자)  ◄┤\n   │                          │     사용자 본인 입력      │\n   │                          │ ── 인증 완료 ──────────► │\n   │                          │                          │\n   │                          │       4. return_url callback │\n   │ ◄────────────────── web_transaction_id ─────────────┤\n   │                          │                          │\n   │ 5. POST \u002Fauth\u002Fresult    ─►│                          │\n   │ ◄── enc_data, integrity  │                          │\n   │     (AES-256-GCM 복호화)  │                          │\n   │     name·birthdate·CI·DI·mobile_no·mobile_co       │\n",[32,49029,49027],{"__ignoreMap":4208},[73,49031],{},[76,49033,49035],{"id":49034},"_4-api-엔드포인트-3종","4. API 엔드포인트 3종",[18,49037,27744,49038,49041],{},[32,49039,49040],{},"https:\u002F\u002Fauth.niceid.co.kr"," 호스트 (단일).",[101,49043,49044,49056],{},[104,49045,49046],{},[107,49047,49048,49050,49052,49054],{},[110,49049,3846],{},[110,49051,22559],{},[110,49053,35660],{},[110,49055,16413],{},[126,49057,49058,49076,49090],{},[107,49059,49060,49062,49065,49070],{},[131,49061,4636],{},[131,49063,49064],{},"POST",[131,49066,49067],{},[32,49068,49069],{},"\u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Ftoken",[131,49071,49072,49073,4343],{},"Basic (",[32,49074,49075],{},"client_id:client_secret",[107,49077,49078,49080,49082,49087],{},[131,49079,4649],{},[131,49081,49064],{},[131,49083,49084],{},[32,49085,49086],{},"\u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Furl",[131,49088,49089],{},"Bearer (token 1에서 발급)",[107,49091,49092,49094,49096,49101],{},[131,49093,4662],{},[131,49095,49064],{},[131,49097,49098],{},[32,49099,49100],{},"\u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Fresult",[131,49102,49089],{},[73,49104],{},[76,49106,49108],{"id":49107},"_5-단계별-명세","5. 단계별 명세",[237,49110,49112,49113],{"id":49111},"_51-토큰-발급-post-idointcv10authtoken","5.1 토큰 발급 — ",[32,49114,49115],{},"POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Ftoken",[18,49117,49118],{},[21,49119,49120],{},"Request Headers",[5251,49122,49125],{"className":49123,"code":49124,"language":5256},[5254],"Authorization: Basic {Base64UrlEncoding(client_id:client_secret)}\nContent-Type: application\u002Fjson\nX-Intc-DevLang: Cloudflare-Workers\u002FTypeScript\n",[32,49126,49124],{"__ignoreMap":4208},[18,49128,49129],{},[21,49130,49131],{},"Request Body",[5251,49133,49135],{"className":21242,"code":49134,"language":21244,"meta":4208,"style":4208},"{\n  \"grant_type\": \"client_credentials\",\n  \"request_no\": \"A1234567890123456789\"\n}\n",[32,49136,49137,49142,49154,49164],{"__ignoreMap":4208},[7968,49138,49139],{"class":5110,"line":7970},[7968,49140,49141],{"class":7984},"{\n",[7968,49143,49144,49147,49149,49152],{"class":5110,"line":4212},[7968,49145,49146],{"class":8892},"  \"grant_type\"",[7968,49148,47],{"class":7984},[7968,49150,49151],{"class":7993},"\"client_credentials\"",[7968,49153,36189],{"class":7984},[7968,49155,49156,49159,49161],{"class":5110,"line":4209},[7968,49157,49158],{"class":8892},"  \"request_no\"",[7968,49160,47],{"class":7984},[7968,49162,49163],{"class":7993},"\"A1234567890123456789\"\n",[7968,49165,49166],{"class":5110,"line":4219},[7968,49167,8987],{"class":7984},[81,49169,49170],{},[84,49171,49172,49174,49175,49178],{},[32,49173,31981],{},": 회원사 요청 고유번호. 20~50자. ",[21,49176,49177],{},"각 인증 세션마다 새로 생성"," (uuid 또는 timestamp+nonce).",[18,49180,49181],{},[21,49182,49183],{},"Response",[5251,49185,49187],{"className":21242,"code":49186,"language":21244,"meta":4208,"style":4208},"{\n  \"result_code\": \"0000\",\n  \"result_message\": \"응답성공\",\n  \"access_token\": \"eyJhbGciOiJIUzUxMiJ9...\",\n  \"expires_in\": 1762940529000,\n  \"token_type\": \"Bearer\",\n  \"iterators\": 66,\n  \"ticket\": \"UzEyMDI1MTEx...\"\n}\n",[32,49188,49189,49193,49205,49217,49229,49241,49253,49265,49275],{"__ignoreMap":4208},[7968,49190,49191],{"class":5110,"line":7970},[7968,49192,49141],{"class":7984},[7968,49194,49195,49198,49200,49203],{"class":5110,"line":4212},[7968,49196,49197],{"class":8892},"  \"result_code\"",[7968,49199,47],{"class":7984},[7968,49201,49202],{"class":7993},"\"0000\"",[7968,49204,36189],{"class":7984},[7968,49206,49207,49210,49212,49215],{"class":5110,"line":4209},[7968,49208,49209],{"class":8892},"  \"result_message\"",[7968,49211,47],{"class":7984},[7968,49213,49214],{"class":7993},"\"응답성공\"",[7968,49216,36189],{"class":7984},[7968,49218,49219,49222,49224,49227],{"class":5110,"line":4219},[7968,49220,49221],{"class":8892},"  \"access_token\"",[7968,49223,47],{"class":7984},[7968,49225,49226],{"class":7993},"\"eyJhbGciOiJIUzUxMiJ9...\"",[7968,49228,36189],{"class":7984},[7968,49230,49231,49234,49236,49239],{"class":5110,"line":8291},[7968,49232,49233],{"class":8892},"  \"expires_in\"",[7968,49235,47],{"class":7984},[7968,49237,49238],{"class":8892},"1762940529000",[7968,49240,36189],{"class":7984},[7968,49242,49243,49246,49248,49251],{"class":5110,"line":8301},[7968,49244,49245],{"class":8892},"  \"token_type\"",[7968,49247,47],{"class":7984},[7968,49249,49250],{"class":7993},"\"Bearer\"",[7968,49252,36189],{"class":7984},[7968,49254,49255,49258,49260,49263],{"class":5110,"line":8310},[7968,49256,49257],{"class":8892},"  \"iterators\"",[7968,49259,47],{"class":7984},[7968,49261,49262],{"class":8892},"66",[7968,49264,36189],{"class":7984},[7968,49266,49267,49270,49272],{"class":5110,"line":8321},[7968,49268,49269],{"class":8892},"  \"ticket\"",[7968,49271,47],{"class":7984},[7968,49273,49274],{"class":7993},"\"UzEyMDI1MTEx...\"\n",[7968,49276,49277],{"class":5110,"line":8332},[7968,49278,8987],{"class":7984},[18,49280,49281,49282,49289],{},"⚠️ ",[21,49283,49284,4473,49286,49288],{},[32,49285,31988],{},[32,49287,31991],{},"는 복호화 단계에서 다시 사용"," — 세션 상태에 보관.",[237,49291,49293,49294],{"id":49292},"_52-인증-url-요청-post-idointcv10authurl","5.2 인증 URL 요청 — ",[32,49295,49296],{},"POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Furl",[18,49298,49299],{},[21,49300,49120],{},[5251,49302,49305],{"className":49303,"code":49304,"language":5256},[5254],"Authorization: Bearer {access_token}\nContent-Type: application\u002Fjson\n",[32,49306,49304],{"__ignoreMap":4208},[18,49308,49309],{},[21,49310,49131],{},[5251,49312,49314],{"className":21242,"code":49313,"language":21244,"meta":4208,"style":4208},"{\n  \"request_no\": \"A1234567890123456789\",\n  \"return_url\": \"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fauth\u002Fnice\u002Fcallback\",\n  \"close_url\": \"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsignup?nice=closed\",\n  \"svc_types\": [\"M\"],\n  \"method_type\": \"POST\",\n  \"exp_mods\": [\"closeButtonOn\"]\n}\n",[32,49315,49316,49320,49331,49343,49355,49369,49381,49393],{"__ignoreMap":4208},[7968,49317,49318],{"class":5110,"line":7970},[7968,49319,49141],{"class":7984},[7968,49321,49322,49324,49326,49329],{"class":5110,"line":4212},[7968,49323,49158],{"class":8892},[7968,49325,47],{"class":7984},[7968,49327,49328],{"class":7993},"\"A1234567890123456789\"",[7968,49330,36189],{"class":7984},[7968,49332,49333,49336,49338,49341],{"class":5110,"line":4209},[7968,49334,49335],{"class":8892},"  \"return_url\"",[7968,49337,47],{"class":7984},[7968,49339,49340],{"class":7993},"\"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fauth\u002Fnice\u002Fcallback\"",[7968,49342,36189],{"class":7984},[7968,49344,49345,49348,49350,49353],{"class":5110,"line":4219},[7968,49346,49347],{"class":8892},"  \"close_url\"",[7968,49349,47],{"class":7984},[7968,49351,49352],{"class":7993},"\"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsignup?nice=closed\"",[7968,49354,36189],{"class":7984},[7968,49356,49357,49360,49363,49366],{"class":5110,"line":8291},[7968,49358,49359],{"class":8892},"  \"svc_types\"",[7968,49361,49362],{"class":7984},": [",[7968,49364,49365],{"class":7993},"\"M\"",[7968,49367,49368],{"class":7984},"],\n",[7968,49370,49371,49374,49376,49379],{"class":5110,"line":8301},[7968,49372,49373],{"class":8892},"  \"method_type\"",[7968,49375,47],{"class":7984},[7968,49377,49378],{"class":7993},"\"POST\"",[7968,49380,36189],{"class":7984},[7968,49382,49383,49386,49388,49391],{"class":5110,"line":8310},[7968,49384,49385],{"class":8892},"  \"exp_mods\"",[7968,49387,49362],{"class":7984},[7968,49389,49390],{"class":7993},"\"closeButtonOn\"",[7968,49392,27327],{"class":7984},[7968,49394,49395],{"class":5110,"line":8321},[7968,49396,8987],{"class":7984},[81,49398,49399,49407,49412,49419,49432],{},[84,49400,49401,49403,49404,4703],{},[32,49402,32119],{},": NICE가 인증 완료 후 호출할 우리 콜백. ",[21,49405,49406],{},"HTTPS 필수",[84,49408,49409,49411],{},[32,49410,32122],{},": 표준창에서 사용자가 X 닫으면 이동할 URL.",[84,49413,49414,49416,49417,4703],{},[32,49415,49008],{},": 노출할 인증 수단. 1단계는 ",[32,49418,49012],{},[84,49420,49421,49424,49425,26162,49427,4778,49429,49431],{},[32,49422,49423],{},"method_type",": 콜백을 ",[32,49426,18678],{},[32,49428,49064],{},[32,49430,49064],{}," 권장(URL에 토큰 노출 방지).",[84,49433,49434,49437,49438,49441],{},[32,49435,49436],{},"exp_mods",": 옵션. ",[32,49439,49440],{},"closeButtonOn"," 등 표준창 UI 옵션.",[18,49443,49444],{},[21,49445,49183],{},[5251,49447,49449],{"className":21242,"code":49448,"language":21244,"meta":4208,"style":4208},"{\n  \"result_code\": \"0000\",\n  \"auth_url\": \"https:\u002F\u002Fauth.niceid.co.kr\u002Fido\u002Fcert\u002Frequest\u002FS1afc40674-b094-44e7-be4b-3e42011b5f81\",\n  \"transaction_id\": \"UzE0MUQyNkFDOEQ3NzYyMDIwMjUxMTEzMTAwMjM3MzM4OTQ5QUMwMkU\"\n}\n",[32,49450,49451,49455,49465,49477,49487],{"__ignoreMap":4208},[7968,49452,49453],{"class":5110,"line":7970},[7968,49454,49141],{"class":7984},[7968,49456,49457,49459,49461,49463],{"class":5110,"line":4212},[7968,49458,49197],{"class":8892},[7968,49460,47],{"class":7984},[7968,49462,49202],{"class":7993},[7968,49464,36189],{"class":7984},[7968,49466,49467,49470,49472,49475],{"class":5110,"line":4209},[7968,49468,49469],{"class":8892},"  \"auth_url\"",[7968,49471,47],{"class":7984},[7968,49473,49474],{"class":7993},"\"https:\u002F\u002Fauth.niceid.co.kr\u002Fido\u002Fcert\u002Frequest\u002FS1afc40674-b094-44e7-be4b-3e42011b5f81\"",[7968,49476,36189],{"class":7984},[7968,49478,49479,49482,49484],{"class":5110,"line":4219},[7968,49480,49481],{"class":8892},"  \"transaction_id\"",[7968,49483,47],{"class":7984},[7968,49485,49486],{"class":7993},"\"UzE0MUQyNkFDOEQ3NzYyMDIwMjUxMTEzMTAwMjM3MzM4OTQ5QUMwMkU\"\n",[7968,49488,49489],{"class":5110,"line":8291},[7968,49490,8987],{"class":7984},[18,49492,49281,49493,49289],{},[21,49494,49495,49497],{},[32,49496,31985],{},"도 복호화 단계에서 salt로 사용",[237,49499,49501],{"id":49500},"_53-표준창-팝업-프런트","5.3 표준창 팝업 (프런트)",[5251,49503,49505],{"className":7962,"code":49504,"language":7964,"meta":4208,"style":4208},"window.open(\n  auth_url,\n  'niceAuth',\n  'width=480,height=812,top=100,scrollbars=no'\n)\n",[32,49506,49507,49517,49522,49529,49534],{"__ignoreMap":4208},[7968,49508,49509,49512,49514],{"class":5110,"line":7970},[7968,49510,49511],{"class":7984},"window.",[7968,49513,9843],{"class":7980},[7968,49515,49516],{"class":7984},"(\n",[7968,49518,49519],{"class":5110,"line":4212},[7968,49520,49521],{"class":7984},"  auth_url,\n",[7968,49523,49524,49527],{"class":5110,"line":4209},[7968,49525,49526],{"class":7993},"  'niceAuth'",[7968,49528,36189],{"class":7984},[7968,49530,49531],{"class":5110,"line":4219},[7968,49532,49533],{"class":7993},"  'width=480,height=812,top=100,scrollbars=no'\n",[7968,49535,49536],{"class":5110,"line":8291},[7968,49537,9563],{"class":7984},[81,49539,49540,49546],{},[84,49541,49542,49543,4703],{},"권장 크기 ",[21,49544,49545],{},"480 × 812px",[84,49547,49548,49549,49551],{},"모바일 환경에서는 ",[32,49550,19891],{}," 또는 same-tab 이동도 가능.",[237,49553,49555,49556,49558],{"id":49554},"_54-콜백-return_url-수신","5.4 콜백 — ",[32,49557,32119],{}," 수신",[18,49560,49561,49564,49565,49567],{},[21,49562,49563],{},"Request from NICE"," (위 ",[32,49566,49423],{},"에 따라)",[5251,49569,49572],{"className":49570,"code":49571,"language":5256},[5254],"POST \u002Fauth\u002Fnice\u002Fcallback\nContent-Type: application\u002Fx-www-form-urlencoded\n\nweb_transaction_id=ZGIxOGZkYjUtMjE4NC00MDZmLTkxZjgtM2ZhNjA0OTdiZTY2\n",[32,49573,49571],{"__ignoreMap":4208},[18,49575,49576,49577,49579,49580,49583],{},"우리 콜백 핸들러는 ",[32,49578,32233],{},"만 받음. ",[21,49581,49582],{},"이 값으로 결과를 따로 조회","(다음 단계).",[237,49585,49587,49588],{"id":49586},"_55-인증-결과-요청-post-idointcv10authresult","5.5 인증 결과 요청 — ",[32,49589,49590],{},"POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Fresult",[18,49592,49593],{},[21,49594,49120],{},[5251,49596,49598],{"className":49597,"code":49304,"language":5256},[5254],[32,49599,49304],{"__ignoreMap":4208},[18,49601,49602],{},[21,49603,49131],{},[5251,49605,49607],{"className":21242,"code":49606,"language":21244,"meta":4208,"style":4208},"{\n  \"web_transaction_id\": \"ZGIxOGZkYjUtMjE4NC00MDZmLTkxZjgtM2ZhNjA0OTdiZTY2\",\n  \"transaction_id\":     \"UzE0MUQyNkFDOEQ3NzYyMDIwMjUxMTEzMTAwMjM3MzM4OTQ5QUMwMkU\",\n  \"request_no\":         \"A1234567890123456789\"\n}\n",[32,49608,49609,49613,49625,49637,49646],{"__ignoreMap":4208},[7968,49610,49611],{"class":5110,"line":7970},[7968,49612,49141],{"class":7984},[7968,49614,49615,49618,49620,49623],{"class":5110,"line":4212},[7968,49616,49617],{"class":8892},"  \"web_transaction_id\"",[7968,49619,47],{"class":7984},[7968,49621,49622],{"class":7993},"\"ZGIxOGZkYjUtMjE4NC00MDZmLTkxZjgtM2ZhNjA0OTdiZTY2\"",[7968,49624,36189],{"class":7984},[7968,49626,49627,49629,49632,49635],{"class":5110,"line":4209},[7968,49628,49481],{"class":8892},[7968,49630,49631],{"class":7984},":     ",[7968,49633,49634],{"class":7993},"\"UzE0MUQyNkFDOEQ3NzYyMDIwMjUxMTEzMTAwMjM3MzM4OTQ5QUMwMkU\"",[7968,49636,36189],{"class":7984},[7968,49638,49639,49641,49644],{"class":5110,"line":4219},[7968,49640,49158],{"class":8892},[7968,49642,49643],{"class":7984},":         ",[7968,49645,49163],{"class":7993},[7968,49647,49648],{"class":5110,"line":8291},[7968,49649,8987],{"class":7984},[18,49651,49652],{},[21,49653,49654],{},"Response (암호화됨)",[5251,49656,49658],{"className":21242,"code":49657,"language":21244,"meta":4208,"style":4208},"{\n  \"result_code\": \"0000\",\n  \"enc_data\":    \"{Base64UrlEncoded(AES-256-GCM)}\",\n  \"integrity_value\": \"neSKi1jaqTBd_uly-1-FYS-A7euPHPajR0HCODnC3HA\"\n}\n",[32,49659,49660,49664,49674,49687,49697],{"__ignoreMap":4208},[7968,49661,49662],{"class":5110,"line":7970},[7968,49663,49141],{"class":7984},[7968,49665,49666,49668,49670,49672],{"class":5110,"line":4212},[7968,49667,49197],{"class":8892},[7968,49669,47],{"class":7984},[7968,49671,49202],{"class":7993},[7968,49673,36189],{"class":7984},[7968,49675,49676,49679,49682,49685],{"class":5110,"line":4209},[7968,49677,49678],{"class":8892},"  \"enc_data\"",[7968,49680,49681],{"class":7984},":    ",[7968,49683,49684],{"class":7993},"\"{Base64UrlEncoded(AES-256-GCM)}\"",[7968,49686,36189],{"class":7984},[7968,49688,49689,49692,49694],{"class":5110,"line":4219},[7968,49690,49691],{"class":8892},"  \"integrity_value\"",[7968,49693,47],{"class":7984},[7968,49695,49696],{"class":7993},"\"neSKi1jaqTBd_uly-1-FYS-A7euPHPajR0HCODnC3HA\"\n",[7968,49698,49699],{"class":5110,"line":8291},[7968,49700,8987],{"class":7984},[73,49702],{},[76,49704,49706],{"id":49705},"_6-암복호화-aes-256-gcm-pbkdf2","6. 암복호화 — AES-256-GCM + PBKDF2",[237,49708,49710],{"id":49709},"키-유도-pbkdf2-hmac-sha256","키 유도 (PBKDF2-HMAC-SHA256)",[5251,49712,49715],{"className":49713,"code":49714,"language":5256},[5254],"input  = ticket (5.1 응답)\nsalt   = transaction_id (5.2 응답)\niter   = iterators (5.1 응답)\noutput = 64 bytes\n\n대칭키   = output[0..32]      \u002F\u002F 32 bytes\n무결성키 = output[48..80]     \u002F\u002F 32 bytes (48번째 byte부터)\n",[32,49716,49714],{"__ignoreMap":4208},[237,49718,49719],{"id":49719},"복호화",[5251,49721,49724],{"className":49722,"code":49723,"language":5256},[5254],"cipher_bytes  = Base64UrlDecode(enc_data)\niv            = cipher_bytes[0..16]                      \u002F\u002F 16 bytes\ncipher_tag    = cipher_bytes[16..]                       \u002F\u002F 나머지\ntag           = cipher_tag[-16..]                        \u002F\u002F 끝 16 bytes\ncipher_text   = cipher_tag[..-16]                        \u002F\u002F 나머지\nplaintext     = AES-256-GCM-Decrypt(cipher_text, key=대칭키, iv=iv, tag=tag)\n",[32,49725,49723],{"__ignoreMap":4208},[237,49727,49729],{"id":49728},"무결성-검증","무결성 검증",[5251,49731,49734],{"className":49732,"code":49733,"language":5256},[5254],"expected = HMAC-SHA256(enc_data, key=무결성키)\nexpected_base64url = Base64UrlEncode(expected).rstrip('=')\nassert expected_base64url == integrity_value  \u002F\u002F 일치하지 않으면 위변조\n",[32,49735,49733],{"__ignoreMap":4208},[237,49737,49739],{"id":49738},"workerstypescript-구현-메모","Workers(TypeScript) 구현 메모",[81,49741,49742,49747,49757,49770],{},[84,49743,49744],{},[32,49745,49746],{},"crypto.subtle.importKey('raw', key, {name:'AES-GCM'}, false, ['decrypt'])",[84,49748,49749,49752,49753,49756],{},[32,49750,49751],{},"crypto.subtle.decrypt({name:'AES-GCM', iv, tagLength:128}, key, cipher_tag)"," — Web Crypto의 ",[32,49754,49755],{},"decrypt","는 tag를 cipher_tag 끝에 붙인 형태로 받음 (별도 분리 불필요).",[84,49758,49759,4339,49762,49765,49766,49769],{},[32,49760,49761],{},"crypto.subtle.importKey('raw', key, {name:'HMAC', hash:'SHA-256'}, false, ['verify'])",[32,49763,49764],{},"crypto.subtle.verify"," 또는 직접 ",[32,49767,49768],{},"crypto.subtle.sign"," 후 비교.",[84,49771,49772,49773,4339,49776,4703],{},"PBKDF2: ",[32,49774,49775],{},"crypto.subtle.importKey('raw', ticket, 'PBKDF2', ...)",[32,49777,49778],{},"deriveBits({name:'PBKDF2', salt, iterations, hash:'SHA-256'}, ..., 512)",[18,49780,49781],{},"→ 별도 라이브러리 불필요. Workers 표준 Web Crypto로 모두 처리.",[73,49783],{},[76,49785,49787],{"id":49786},"_7-복호화-후-받는-데이터-휴대폰-본인확인-m-기준","7. 복호화 후 받는 데이터 (휴대폰 본인확인 M 기준)",[101,49789,49790,49806],{},[104,49791,49792],{},[107,49793,49794,49797,49800,49803],{},[110,49795,49796],{},"키",[110,49798,49799],{},"타입",[110,49801,49802],{},"설명",[110,49804,49805],{},"우리 사용",[126,49807,49808,49824,49843,49865,49886,49902,49916,49932],{},[107,49809,49810,49814,49816,49819],{},[131,49811,49812],{},[32,49813,14992],{},[131,49815,9522],{},[131,49817,49818],{},"성명",[131,49820,49821,30584],{},[32,49822,49823],{},"TB_USER.name",[107,49825,49826,49830,49832,49837],{},[131,49827,49828],{},[32,49829,31940],{},[131,49831,9522],{},[131,49833,49834],{},[32,49835,49836],{},"yyyymmdd",[131,49838,49839,49842],{},[32,49840,49841],{},"TB_USER.birthdate"," 신규 (스키마 추가)",[107,49844,49845,49849,49851,49859],{},[131,49846,49847],{},[32,49848,31943],{},[131,49850,9522],{},[131,49852,49853,49855,49856,49858],{},[32,49854,5354],{},"=여, ",[32,49857,4636],{},"=남",[131,49860,49861,49862],{},"(선택) ",[32,49863,49864],{},"TB_USER.gender",[107,49866,49867,49871,49873,49881],{},[131,49868,49869],{},[32,49870,31946],{},[131,49872,9522],{},[131,49874,49875,49877,49878,49880],{},[32,49876,5354],{},"=내국인, ",[32,49879,4636],{},"=외국인",[131,49882,49861,49883],{},[32,49884,49885],{},"TB_USER.national_info",[107,49887,49888,49892,49894,49897],{},[131,49889,49890],{},[32,49891,31949],{},[131,49893,9522],{},[131,49895,49896],{},"연계정보 (사이트 간 동일인 식별)",[131,49898,49899,49901],{},[32,49900,32079],{}," 신규 + UNIQUE — 중복 가입 방지",[107,49903,49904,49908,49910,49913],{},[131,49905,49906],{},[32,49907,32015],{},[131,49909,9522],{},[131,49911,49912],{},"중복가입 확인정보 (사이트 내 동일인 식별)",[131,49914,49915],{},"(선택) 보조",[107,49917,49918,49922,49924,49927],{},[131,49919,49920],{},[32,49921,31952],{},[131,49923,9522],{},[131,49925,49926],{},"통신사 코드",[131,49928,49929],{},[32,49930,49931],{},"TB_USER.mobile_co",[107,49933,49934,49938,49940,49943],{},[131,49935,49936],{},[32,49937,31936],{},[131,49939,9522],{},[131,49941,49942],{},"인증된 휴대폰 번호",[131,49944,49945,49947],{},[32,49946,38538],{}," 갱신 (기존 입력값 덮어쓰기)",[237,49949,49951],{"id":49950},"ci연계정보-운영-원칙","CI(연계정보) 운영 원칙",[81,49953,49954,49960,49970],{},[84,49955,49956,49959],{},[21,49957,49958],{},"CI = 한 사람당 고유 88byte 문자열",". 사이트 간(여러 서비스) 동일인 식별의 표준.",[84,49961,49962,49963,49966,49967,49969],{},"동일 CI로 이미 가입된 사용자가 있으면 ",[21,49964,49965],{},"중복 가입 차단",". (signup 시 ",[32,49968,32079],{}," UNIQUE 검사)",[84,49971,49972],{},"DI도 사용 가능하나 사이트 내(우리 서비스 한정) 식별이라 CI가 더 강력.",[73,49974],{},[76,49976,49978],{"id":49977},"_8-우리-적용-계획","8. 우리 적용 계획",[237,49980,49982,49983,49985],{"id":49981},"_81-백엔드-malgn-noti-api-신규-라우트-3종","8.1 백엔드 (",[32,49984,50],{},") — 신규 라우트 3종",[101,49987,49988,49996],{},[104,49989,49990],{},[107,49991,49992,49994],{},[110,49993,22556],{},[110,49995,46107],{},[126,49997,49998,50011,50023],{},[107,49999,50000,50004],{},[131,50001,50002],{},[32,50003,32203],{},[131,50005,50006,50007,50010],{},"5.1 + 5.2 통합 → 프런트에 ",[32,50008,50009],{},"auth_url"," 반환 + session state 발급",[107,50012,50013,50017],{},[131,50014,50015],{},[32,50016,32227],{},[131,50018,50019,50020,50022],{},"NICE의 5.4 콜백 수신 → 5.5 호출 → 복호화 → ",[32,50021,31969],{}," 적재 → 프런트로 redirect (성공\u002F실패)",[107,50024,50025,50029],{},[131,50026,50027],{},[32,50028,32244],{},[131,50030,50031],{},"프런트가 폴링으로 인증 완료 여부 확인 → 완료면 검증 결과(이름·생년월일·CI·DI·휴대폰)를 한 번에 반환",[237,50033,50035,50036],{"id":50034},"_82-신규-db-테이블-tb_nice_auth","8.2 신규 DB 테이블 — ",[32,50037,31969],{},[5251,50039,50041],{"className":31016,"code":50040,"language":31018,"meta":4208,"style":4208},"CREATE TABLE TB_NICE_AUTH (\n  id              BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n  request_no      VARCHAR(50)  NOT NULL,         -- 우리 요청 고유번호\n  transaction_id  VARCHAR(120) NOT NULL,         -- NICE 응답\n  ticket          VARCHAR(255) NOT NULL,         -- 복호화용\n  iterators       INT          NOT NULL,         -- 복호화용\n  state           VARCHAR(20)  NOT NULL DEFAULT 'pending',\n                  -- pending → completed \u002F failed \u002F expired\n  name            VARCHAR(60),\n  birthdate       VARCHAR(8),\n  gender          CHAR(1),\n  national_info   CHAR(1),\n  ci              VARCHAR(255),                  -- 88byte지만 여유\n  di              VARCHAR(255),\n  mobile_co       VARCHAR(10),\n  mobile_no       VARCHAR(20),\n  expires_at      DATETIME NOT NULL,             -- access_token 만료\n  created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  completed_at    DATETIME,\n  UNIQUE KEY uq_nice_request (request_no),\n  KEY idx_nice_state (state, created_at)\n);\n",[32,50042,50043,50048,50053,50058,50063,50068,50073,50078,50083,50088,50093,50098,50103,50108,50113,50118,50123,50128,50133,50138,50143,50148],{"__ignoreMap":4208},[7968,50044,50045],{"class":5110,"line":7970},[7968,50046,50047],{},"CREATE TABLE TB_NICE_AUTH (\n",[7968,50049,50050],{"class":5110,"line":4212},[7968,50051,50052],{},"  id              BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n",[7968,50054,50055],{"class":5110,"line":4209},[7968,50056,50057],{},"  request_no      VARCHAR(50)  NOT NULL,         -- 우리 요청 고유번호\n",[7968,50059,50060],{"class":5110,"line":4219},[7968,50061,50062],{},"  transaction_id  VARCHAR(120) NOT NULL,         -- NICE 응답\n",[7968,50064,50065],{"class":5110,"line":8291},[7968,50066,50067],{},"  ticket          VARCHAR(255) NOT NULL,         -- 복호화용\n",[7968,50069,50070],{"class":5110,"line":8301},[7968,50071,50072],{},"  iterators       INT          NOT NULL,         -- 복호화용\n",[7968,50074,50075],{"class":5110,"line":8310},[7968,50076,50077],{},"  state           VARCHAR(20)  NOT NULL DEFAULT 'pending',\n",[7968,50079,50080],{"class":5110,"line":8321},[7968,50081,50082],{},"                  -- pending → completed \u002F failed \u002F expired\n",[7968,50084,50085],{"class":5110,"line":8332},[7968,50086,50087],{},"  name            VARCHAR(60),\n",[7968,50089,50090],{"class":5110,"line":8343},[7968,50091,50092],{},"  birthdate       VARCHAR(8),\n",[7968,50094,50095],{"class":5110,"line":8349},[7968,50096,50097],{},"  gender          CHAR(1),\n",[7968,50099,50100],{"class":5110,"line":8454},[7968,50101,50102],{},"  national_info   CHAR(1),\n",[7968,50104,50105],{"class":5110,"line":8472},[7968,50106,50107],{},"  ci              VARCHAR(255),                  -- 88byte지만 여유\n",[7968,50109,50110],{"class":5110,"line":8494},[7968,50111,50112],{},"  di              VARCHAR(255),\n",[7968,50114,50115],{"class":5110,"line":8515},[7968,50116,50117],{},"  mobile_co       VARCHAR(10),\n",[7968,50119,50120],{"class":5110,"line":8525},[7968,50121,50122],{},"  mobile_no       VARCHAR(20),\n",[7968,50124,50125],{"class":5110,"line":8531},[7968,50126,50127],{},"  expires_at      DATETIME NOT NULL,             -- access_token 만료\n",[7968,50129,50130],{"class":5110,"line":8541},[7968,50131,50132],{},"  created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n",[7968,50134,50135],{"class":5110,"line":8550},[7968,50136,50137],{},"  completed_at    DATETIME,\n",[7968,50139,50140],{"class":5110,"line":8555},[7968,50141,50142],{},"  UNIQUE KEY uq_nice_request (request_no),\n",[7968,50144,50145],{"class":5110,"line":8568},[7968,50146,50147],{},"  KEY idx_nice_state (state, created_at)\n",[7968,50149,50150],{"class":5110,"line":8574},[7968,50151,8942],{},[237,50153,50155,50156,50158],{"id":50154},"_83-tb_user-스키마-확장","8.3 ",[32,50157,20302],{}," 스키마 확장",[5251,50160,50162],{"className":31016,"code":50161,"language":31018,"meta":4208,"style":4208},"ALTER TABLE TB_USER\n  ADD COLUMN birthdate  VARCHAR(8),\n  ADD COLUMN gender     CHAR(1),\n  ADD COLUMN ci         VARCHAR(255),\n  ADD COLUMN mobile_co  VARCHAR(10),\n  ADD UNIQUE KEY uq_user_ci (ci);  -- 중복 가입 차단\n",[32,50163,50164,50168,50173,50178,50183,50188],{"__ignoreMap":4208},[7968,50165,50166],{"class":5110,"line":7970},[7968,50167,31025],{},[7968,50169,50170],{"class":5110,"line":4212},[7968,50171,50172],{},"  ADD COLUMN birthdate  VARCHAR(8),\n",[7968,50174,50175],{"class":5110,"line":4209},[7968,50176,50177],{},"  ADD COLUMN gender     CHAR(1),\n",[7968,50179,50180],{"class":5110,"line":4219},[7968,50181,50182],{},"  ADD COLUMN ci         VARCHAR(255),\n",[7968,50184,50185],{"class":5110,"line":8291},[7968,50186,50187],{},"  ADD COLUMN mobile_co  VARCHAR(10),\n",[7968,50189,50190],{"class":5110,"line":8301},[7968,50191,50192],{},"  ADD UNIQUE KEY uq_user_ci (ci);  -- 중복 가입 차단\n",[237,50194,50196],{"id":50195},"_84-wrangler-secrets","8.4 wrangler secrets",[5251,50198,50201],{"className":50199,"code":50200,"language":5256},[5254],"NICE_CLIENT_ID\nNICE_CLIENT_SECRET\nNICE_RETURN_URL       (예: https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fauth\u002Fnice\u002Fcallback)\nNICE_CLOSE_URL        (예: https:\u002F\u002Fmalgn-noti.pages.dev\u002Fsignup?nice=closed)\n",[32,50202,50200],{"__ignoreMap":4208},[237,50204,50206,50207,50209],{"id":50205},"_85-프런트-signupvue-step-4-교체","8.5 프런트 — ",[32,50208,14163],{}," Step 4 교체",[18,50211,50212,50213,50216],{},"기존 흐름(통신사 select + 이름 + 주민번호 + 휴대폰 3분할 입력 + 자체 OTP 발송) → 단일 ",[21,50214,50215],{},"\"본인 인증하기\""," 버튼:",[5251,50218,50220],{"className":7962,"code":50219,"language":7964,"meta":4208,"style":4208},"async function startNiceAuth() {\n  const { authUrl, session } = await useApi()('\u002Fauth\u002Fnice\u002Finit', { method: 'POST' })\n  niceSession.value = session\n  window.open(authUrl, 'niceAuth', 'width=480,height=812,top=100')\n  \u002F\u002F 폴링 시작\n  pollUntilDone()\n}\n\nasync function pollUntilDone() {\n  for (let i = 0; i \u003C 60; i++) {  \u002F\u002F 5분(5초 × 60)\n    await sleep(5000)\n    const res = await useApi()('\u002Fauth\u002Fnice\u002Fstatus', {\n      params: { session: niceSession.value },\n    })\n    if (res.data.state === 'completed') {\n      niceResult.value = res.data  \u002F\u002F {name, birthdate, ci, mobile_no, ...}\n      verified.value = true\n      return\n    }\n    if (res.data.state === 'failed' || res.data.state === 'expired') {\n      \u002F\u002F 토스트 + 다시 시도 UI\n      return\n    }\n  }\n}\n",[32,50221,50222,50233,50267,50277,50297,50302,50309,50313,50317,50328,50359,50373,50393,50398,50402,50416,50429,50438,50443,50447,50470,50475,50479,50484,50489],{"__ignoreMap":4208},[7968,50223,50224,50226,50228,50231],{"class":5110,"line":7970},[7968,50225,9453],{"class":7973},[7968,50227,34185],{"class":7973},[7968,50229,50230],{"class":7980}," startNiceAuth",[7968,50232,43050],{"class":7984},[7968,50234,50235,50237,50239,50242,50244,50247,50249,50251,50253,50255,50258,50261,50263,50265],{"class":5110,"line":4212},[7968,50236,34235],{"class":7973},[7968,50238,27200],{"class":7984},[7968,50240,50241],{"class":8892},"authUrl",[7968,50243,4473],{"class":7984},[7968,50245,50246],{"class":8892},"session",[7968,50248,25860],{"class":7984},[7968,50250,8254],{"class":7973},[7968,50252,9280],{"class":7973},[7968,50254,9265],{"class":7980},[7968,50256,50257],{"class":7984},"()(",[7968,50259,50260],{"class":7993},"'\u002Fauth\u002Fnice\u002Finit'",[7968,50262,37294],{"class":7984},[7968,50264,34156],{"class":7993},[7968,50266,8274],{"class":7984},[7968,50268,50269,50272,50274],{"class":5110,"line":4209},[7968,50270,50271],{"class":7984},"  niceSession.value ",[7968,50273,8254],{"class":7973},[7968,50275,50276],{"class":7984}," session\n",[7968,50278,50279,50282,50284,50287,50290,50292,50295],{"class":5110,"line":4219},[7968,50280,50281],{"class":7984},"  window.",[7968,50283,9843],{"class":7980},[7968,50285,50286],{"class":7984},"(authUrl, ",[7968,50288,50289],{"class":7993},"'niceAuth'",[7968,50291,4473],{"class":7984},[7968,50293,50294],{"class":7993},"'width=480,height=812,top=100'",[7968,50296,9563],{"class":7984},[7968,50298,50299],{"class":5110,"line":8291},[7968,50300,50301],{"class":8398},"  \u002F\u002F 폴링 시작\n",[7968,50303,50304,50307],{"class":5110,"line":8301},[7968,50305,50306],{"class":7980},"  pollUntilDone",[7968,50308,9268],{"class":7984},[7968,50310,50311],{"class":5110,"line":8310},[7968,50312,8987],{"class":7984},[7968,50314,50315],{"class":5110,"line":8321},[7968,50316,8288],{"emptyLinePlaceholder":4259},[7968,50318,50319,50321,50323,50326],{"class":5110,"line":8332},[7968,50320,9453],{"class":7973},[7968,50322,34185],{"class":7973},[7968,50324,50325],{"class":7980}," pollUntilDone",[7968,50327,43050],{"class":7984},[7968,50329,50330,50332,50334,50336,50338,50340,50342,50344,50346,50349,50351,50353,50356],{"class":5110,"line":8343},[7968,50331,39950],{"class":7973},[7968,50333,6685],{"class":7984},[7968,50335,37727],{"class":7973},[7968,50337,39957],{"class":7984},[7968,50339,8254],{"class":7973},[7968,50341,33274],{"class":8892},[7968,50343,39964],{"class":7984},[7968,50345,8241],{"class":7973},[7968,50347,50348],{"class":8892}," 60",[7968,50350,39972],{"class":7984},[7968,50352,39975],{"class":7973},[7968,50354,50355],{"class":7984},") {  ",[7968,50357,50358],{"class":8398},"\u002F\u002F 5분(5초 × 60)\n",[7968,50360,50361,50363,50366,50368,50371],{"class":5110,"line":8349},[7968,50362,34477],{"class":7973},[7968,50364,50365],{"class":7980}," sleep",[7968,50367,6100],{"class":7984},[7968,50369,50370],{"class":8892},"5000",[7968,50372,9563],{"class":7984},[7968,50374,50375,50377,50380,50382,50384,50386,50388,50391],{"class":5110,"line":8454},[7968,50376,34312],{"class":7973},[7968,50378,50379],{"class":8892}," res",[7968,50381,9210],{"class":7973},[7968,50383,9280],{"class":7973},[7968,50385,9265],{"class":7980},[7968,50387,50257],{"class":7984},[7968,50389,50390],{"class":7993},"'\u002Fauth\u002Fnice\u002Fstatus'",[7968,50392,9301],{"class":7984},[7968,50394,50395],{"class":5110,"line":8472},[7968,50396,50397],{"class":7984},"      params: { session: niceSession.value },\n",[7968,50399,50400],{"class":5110,"line":8494},[7968,50401,37894],{"class":7984},[7968,50403,50404,50406,50409,50411,50414],{"class":5110,"line":8515},[7968,50405,34275],{"class":7973},[7968,50407,50408],{"class":7984}," (res.data.state ",[7968,50410,31645],{"class":7973},[7968,50412,50413],{"class":7993}," 'completed'",[7968,50415,21462],{"class":7984},[7968,50417,50418,50421,50423,50426],{"class":5110,"line":8525},[7968,50419,50420],{"class":7984},"      niceResult.value ",[7968,50422,8254],{"class":7973},[7968,50424,50425],{"class":7984}," res.data  ",[7968,50427,50428],{"class":8398},"\u002F\u002F {name, birthdate, ci, mobile_no, ...}\n",[7968,50430,50431,50434,50436],{"class":5110,"line":8531},[7968,50432,50433],{"class":7984},"      verified.value ",[7968,50435,8254],{"class":7973},[7968,50437,43069],{"class":8892},[7968,50439,50440],{"class":5110,"line":8541},[7968,50441,50442],{"class":7973},"      return\n",[7968,50444,50445],{"class":5110,"line":8550},[7968,50446,34472],{"class":7984},[7968,50448,50449,50451,50453,50455,50458,50460,50463,50465,50468],{"class":5110,"line":8555},[7968,50450,34275],{"class":7973},[7968,50452,50408],{"class":7984},[7968,50454,31645],{"class":7973},[7968,50456,50457],{"class":7993}," 'failed'",[7968,50459,36145],{"class":7973},[7968,50461,50462],{"class":7984}," res.data.state ",[7968,50464,31645],{"class":7973},[7968,50466,50467],{"class":7993}," 'expired'",[7968,50469,21462],{"class":7984},[7968,50471,50472],{"class":5110,"line":8568},[7968,50473,50474],{"class":8398},"      \u002F\u002F 토스트 + 다시 시도 UI\n",[7968,50476,50477],{"class":5110,"line":8574},[7968,50478,50442],{"class":7973},[7968,50480,50482],{"class":5110,"line":50481},23,[7968,50483,34472],{"class":7984},[7968,50485,50487],{"class":5110,"line":50486},24,[7968,50488,21497],{"class":7984},[7968,50490,50492],{"class":5110,"line":50491},25,[7968,50493,8987],{"class":7984},[237,50495,50497,50498,50500],{"id":50496},"_86-authsignup-라우트-확장","8.6 ",[32,50499,23258],{}," 라우트 확장",[18,50502,50503],{},"NICE 결과를 받아 검증 후 signup:",[5251,50505,50507],{"className":7962,"code":50506,"language":7964,"meta":4208,"style":4208},"const signupB = z.object({\n  \u002F\u002F 기존 필드 +\n  niceSession: z.string(),  \u002F\u002F \u002Fauth\u002Fnice\u002Fstatus가 반환한 session 또는 request_no\n})\n\n\u002F\u002F signup 처리 안:\n\u002F\u002F 1. TB_NICE_AUTH에서 state='completed' + 같은 session 조회\n\u002F\u002F 2. ci로 TB_USER 중복 검사 → 있으면 409 \"이미 가입된 사용자\"\n\u002F\u002F 3. signup 진행 + TB_USER.ci\u002Fbirthdate\u002Fgender\u002Fmobile_co 적재\n\u002F\u002F 4. consumed 표시 (한 번 쓰면 다시 못 씀)\n",[32,50508,50509,50524,50529,50542,50546,50550,50555,50560,50565,50570],{"__ignoreMap":4208},[7968,50510,50511,50513,50516,50518,50520,50522],{"class":5110,"line":7970},[7968,50512,9204],{"class":7973},[7968,50514,50515],{"class":8892}," signupB",[7968,50517,9210],{"class":7973},[7968,50519,9509],{"class":7984},[7968,50521,9512],{"class":7980},[7968,50523,7985],{"class":7984},[7968,50525,50526],{"class":5110,"line":4212},[7968,50527,50528],{"class":8398},"  \u002F\u002F 기존 필드 +\n",[7968,50530,50531,50534,50536,50539],{"class":5110,"line":4209},[7968,50532,50533],{"class":7984},"  niceSession: z.",[7968,50535,9522],{"class":7980},[7968,50537,50538],{"class":7984},"(),  ",[7968,50540,50541],{"class":8398},"\u002F\u002F \u002Fauth\u002Fnice\u002Fstatus가 반환한 session 또는 request_no\n",[7968,50543,50544],{"class":5110,"line":4219},[7968,50545,8007],{"class":7984},[7968,50547,50548],{"class":5110,"line":8291},[7968,50549,8288],{"emptyLinePlaceholder":4259},[7968,50551,50552],{"class":5110,"line":8301},[7968,50553,50554],{"class":8398},"\u002F\u002F signup 처리 안:\n",[7968,50556,50557],{"class":5110,"line":8310},[7968,50558,50559],{"class":8398},"\u002F\u002F 1. TB_NICE_AUTH에서 state='completed' + 같은 session 조회\n",[7968,50561,50562],{"class":5110,"line":8321},[7968,50563,50564],{"class":8398},"\u002F\u002F 2. ci로 TB_USER 중복 검사 → 있으면 409 \"이미 가입된 사용자\"\n",[7968,50566,50567],{"class":5110,"line":8332},[7968,50568,50569],{"class":8398},"\u002F\u002F 3. signup 진행 + TB_USER.ci\u002Fbirthdate\u002Fgender\u002Fmobile_co 적재\n",[7968,50571,50572],{"class":5110,"line":8343},[7968,50573,50574],{"class":8398},"\u002F\u002F 4. consumed 표시 (한 번 쓰면 다시 못 씀)\n",[73,50576],{},[76,50578,50580],{"id":50579},"_9-인프라-고려사항-중요","9. 인프라 고려사항 (중요)",[237,50582,50584,50585],{"id":50583},"_91-outbound-ip-화이트리스트-workers-환경-이슈-검증됨-62-16","9.1 Outbound IP 화이트리스트 — Workers 환경 이슈 → ",[21,50586,50587],{},"검증됨 (6\u002F2 §16)",[18,50589,50590,50591,50594],{},"NICE는 ",[21,50592,50593],{},"이용기관 서버의 Outbound IP를 사전 등록","해야 API 호출이 가능합니다 (가이드 §13).",[18,50596,50597,50600],{},[21,50598,50599],{},"문제",": Cloudflare Workers는 동적 데이터센터 IP를 사용 → 고정 IP 없음.",[18,50602,50603,50606,50607,50609,50610,50612,50613,50615,50616,39874],{},[21,50604,50605],{},"라이브 시도 결과 (2026-06-02)",": 자격증명을 정상 등록한 상태에서 ",[32,50608,32203],{}," 호출 시 NICE 응답 ",[32,50611,39829],{}," 발생. 사전 예측대로 IP 화이트리스트가 막힘. 즉시 ",[32,50614,38976],{}," 복원해 가입 흐름은 정상 동작 유지. 자격증명 3 secret(CLIENT_ID\u002FCLIENT_SECRET\u002FRETURN_URL)은 해결 시점까지 등록 상태로 보관 — ",[32,50617,32642],{},[18,50619,50620,50623],{},[21,50621,50622],{},"선택지"," (6\u002F2 우선순위 재정렬):",[101,50625,50626,50641],{},[104,50627,50628],{},[107,50629,50630,50633,50635,50638],{},[110,50631,50632],{},"안",[110,50634,49802],{},[110,50636,50637],{},"트레이드오프",[110,50639,50640],{},"사용자 의사",[126,50642,50643,50661,50676,50692],{},[107,50644,50645,50650,50653,50656],{},[131,50646,50647],{},[21,50648,50649],{},"A. NICE 콘솔 IP 검사 OFF",[131,50651,50652],{},"콘솔 → API 설정에서 IP 인증 토글 해제",[131,50654,50655],{},"가장 단순. 보안 등급은 다소 낮아짐",[131,50657,50658],{},[21,50659,50660],{},"권장 1순위",[107,50662,50663,50668,50671,50674],{},[131,50664,50665],{},[21,50666,50667],{},"B. NICE 콘솔에 Cloudflare egress IP 대역 등록",[131,50669,50670],{},"Cloudflare 공식 IP 목록을 NICE 영업담당에 송부 후 콘솔 반영",[131,50672,50673],{},"NICE 정책상 거절 가능성",[131,50675,322],{},[107,50677,50678,50683,50686,50689],{},[131,50679,50680],{},[21,50681,50682],{},"C. 자체 프록시 EC2 + Workers → EC2 → NICE",[131,50684,50685],{},"EC2 고정 IP를 NICE에 등록, Workers는 EC2로 프록시",[131,50687,50688],{},"인프라 추가. 운영 안정성 ↑. 월 ~$4 + 약간의 코드",[131,50690,50691],{},"A·B 실패 시 fallback",[107,50693,50694,50699,50702,50705],{},[131,50695,50696],{},[21,50697,50698],{},"D. Cloudflare Workers Smart Placement + dedicated egress IP",[131,50700,50701],{},"Cloudflare enterprise 등급의 고정 egress IP",[131,50703,50704],{},"비용 큼",[131,50706,322],{},[18,50708,50709,50712,50713,50715],{},[21,50710,50711],{},"현재 결정 (6\u002F2)",": 사용자 의사로 IP 정책은 ",[21,50714,39051],{},". mock 모드 유지하면서 다른 작업 진행.",[237,50717,50719],{"id":50718},"_92-nice-방화벽-호스트","9.2 NICE 방화벽 호스트",[18,50721,50722],{},"운영 시점 outbound 허용 호스트:",[5251,50724,50727],{"className":50725,"code":50726,"language":5256},[5254],"auth.niceid.co.kr        (121.162.155.181) : 443  ← 통합인증 (우리 사용)\nnice.checkplus.co.kr     (121.131.196.215) : 443  ← 휴대폰 인증 (자동 fallback)\n",[32,50728,50726],{"__ignoreMap":4208},[237,50730,50732],{"id":50731},"_93-보안-위생","9.3 보안 위생",[81,50734,50735,50747,50752,50757],{},[84,50736,50737,4361,50740,12916,50743,50746],{},[32,50738,50739],{},"client_id",[32,50741,50742],{},"client_secret",[21,50744,50745],{},"Workers secret으로만",". 코드\u002F저장소·로그·에러 응답에 절대 노출 금지.",[84,50748,50749,50751],{},[32,50750,42386],{},"은 짧은 TTL — 캐시 시 expires_in 안에서만.",[84,50753,50754,50756],{},[32,50755,31949],{},"는 PII이지만 그 자체로는 복호화 불가능한 해시 형태 → DB 저장 OK. 단 로그·외부 응답에 직접 노출 금지.",[84,50758,50759,50761],{},[32,50760,31981],{},"는 매 세션 새로 생성 + 재사용 차단.",[73,50763],{},[76,50765,50767],{"id":50766},"_10-nice측-계약등록-절차-사용자-작업-필요","10. NICE측 계약·등록 절차 (사용자 작업 필요)",[101,50769,50770,50781],{},[104,50771,50772],{},[107,50773,50774,50776,50779],{},[110,50775,112],{},[110,50777,50778],{},"작업자",[110,50780,7718],{},[126,50782,50783,50800,50811,50822,50838,50848,50858],{},[107,50784,50785,50792,50797],{},[131,50786,50787,50788,50791],{},"1. NICE평가정보 영업 담당 컨택 (",[32,50789,50790],{},"niceid_support@nice.co.kr"," \u002F 02-2122-4872~3)",[131,50793,50794],{},[21,50795,50796],{},"사용자(영업\u002F대표)",[131,50798,50799],{},"통합인증 서비스 가입 요청",[107,50801,50802,50805,50808],{},[131,50803,50804],{},"2. 사이트 등록 + 단가표 확정",[131,50806,50807],{},"사용자",[131,50809,50810],{},"휴대폰 본인확인 건당 단가 협의",[107,50812,50813,50816,50819],{},[131,50814,50815],{},"3. Outbound IP 등록 (위 §9.1)",[131,50817,50818],{},"사용자 + 김도형",[131,50820,50821],{},"Cloudflare 대역 등록 협의 결과 따라",[107,50823,50824,50832,50835],{},[131,50825,50826,50827,4420,50829,50831],{},"4. ",[32,50828,50739],{},[32,50830,50742],{}," 발급",[131,50833,50834],{},"NICE → 사용자",[131,50836,50837],{},"비공개 보관",[107,50839,50840,50843,50845],{},[131,50841,50842],{},"5. 우리 Workers에 secret 등록 + 코드 배포",[131,50844,484],{},[131,50846,50847],{},"1~2시간 작업",[107,50849,50850,50853,50855],{},[131,50851,50852],{},"6. NICE 테스트 페이지에서 e2e 검증",[131,50854,484],{},[131,50856,50857],{},"본인 휴대폰으로 1회",[107,50859,50860,50863,50865],{},[131,50861,50862],{},"7. 운영 전환 + signup.vue 교체 배포",[131,50864,484],{},[131,50866],{},[18,50868,28038,50869,4703],{},[21,50870,50871],{},"NICE 발급 완료(4단계) 후 5~7은 약 반나절 작업",[73,50873],{},[76,50875,50877],{"id":50876},"_11-알려진-한계-후속-작업","11. 알려진 한계 \u002F 후속 작업",[101,50879,50880,50890],{},[104,50881,50882],{},[107,50883,50884,50886,50888],{},[110,50885,3846],{},[110,50887,6889],{},[110,50889,7718],{},[126,50891,50892,50906,50918,50930,50945,50957,50969,50983,50998],{},[107,50893,50894,50896,50900],{},[131,50895,4636],{},[131,50897,50898],{},[21,50899,32678],{},[131,50901,50902,50903,50905],{},"NICE 휴대폰 본인확인은 외국인 등록증 지원. ",[32,50904,32681],{},"로 분기 가능. UI에 외국인 선택지 추가 필요",[107,50907,50908,50910,50915],{},[131,50909,4649],{},[131,50911,50912],{},[21,50913,50914],{},"법인 계정의 대표자 인증",[131,50916,50917],{},"법인사업자 가입 시 대표자 본인 인증으로 대체 가능. 정책 결정 후 적용",[107,50919,50920,50922,50927],{},[131,50921,4662],{},[131,50923,50924],{},[21,50925,50926],{},"PASS 앱 직접 연동",[131,50928,50929],{},"NICE 통합인증에선 미지원. PASS 직접 연동은 통신3사 별도 계약 필요 — 후순위",[107,50931,50932,50934,50940],{},[131,50933,4678],{},[131,50935,50936,50939],{},[21,50937,50938],{},"NICE 통합인증 외 수단"," (금융인증서 F, 공동인증서 U)",[131,50941,50942,50943,32257],{},"1단계는 M만, 2단계에서 ",[32,50944,49008],{},[107,50946,50947,50949,50954],{},[131,50948,4691],{},[131,50950,50951],{},[21,50952,50953],{},"CI 중복 검사 UX",[131,50955,50956],{},"이미 가입된 사용자가 다른 이메일로 재가입 시도 → 409 후 \"이미 가입된 계정이 있습니다. 비밀번호 재설정으로 진행해 주세요\" 안내",[107,50958,50959,50961,50966],{},[131,50960,4708],{},[131,50962,50963],{},[21,50964,50965],{},"자체 SMS OTP 유지 영역",[131,50967,50968],{},"비밀번호 재설정·이메일\u002F휴대폰 변경 등 단순 보유 확인은 자체 OTP 유지 (NICE 비용 절감)",[107,50970,50971,50973,50978],{},[131,50972,4724],{},[131,50974,50975],{},[21,50976,50977],{},"본인 인증 결과 보관 기간",[131,50979,50980,50982],{},[32,50981,31969],{},"는 가입 직후 30~90일 후 PII만 마스킹 처리 검토 (CI는 유지)",[107,50984,50985,50987,50992],{},[131,50986,23932],{},[131,50988,50989],{},[21,50990,50991],{},"모바일웹 UX",[131,50993,50994,50995,50997],{},"표준창이 팝업이라 모바일에서 차단 가능성 — ",[32,50996,29057],{}," 모드 검토",[107,50999,51000,51003,51008],{},[131,51001,51002],{},"9",[131,51004,51005],{},[21,51006,51007],{},"테스트 환경 분리",[131,51009,51010],{},"NICE에서 테스트 서버 별도 발급 시 우리도 sandbox\u002Fproduction 환경 분리",[73,51012],{},[76,51014,51016],{"id":51015},"_12-다음-단계-추천","12. 다음 단계 추천",[18,51018,51019,51020,51023],{},"이번주 회원·인증 트랙 P0 항목과 별개로 — NICE 연동은 ",[21,51021,51022],{},"사용자 영업 작업(계약) 선행"," → 그 다음 1일 작업으로 정리 가능.",[81,51025,51026,51029,51032],{},[84,51027,51028],{},"사용자 → NICE 영업 컨택 (이번주~다음주)",[84,51030,51031],{},"김도형 → 계약 완료 후 secret 받으면 반나절 작업으로 백엔드 3 라우트 + 프런트 Step 4 교체 + 라이브 검증",[84,51033,51034],{},"그 사이는 현재 자체 SMS OTP로 가입 흐름 유지 (mock 노출은 NICE 적용 시 영구 제거)",[8560,51036,51037],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":4208,"searchDepth":4209,"depth":4209,"links":51039},[51040,51041,51042,51043,51044,51055,51061,51064,51077,51083,51084,51085],{"id":48820,"depth":4212,"text":48821},{"id":48928,"depth":4212,"text":48929},{"id":49022,"depth":4212,"text":49023},{"id":49034,"depth":4212,"text":49035},{"id":49107,"depth":4212,"text":49108,"children":51045},[51046,51048,51050,51051,51053],{"id":49111,"depth":4209,"text":51047},"5.1 토큰 발급 — POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Ftoken",{"id":49292,"depth":4209,"text":51049},"5.2 인증 URL 요청 — POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Furl",{"id":49500,"depth":4209,"text":49501},{"id":49554,"depth":4209,"text":51052},"5.4 콜백 — return_url 수신",{"id":49586,"depth":4209,"text":51054},"5.5 인증 결과 요청 — POST \u002Fido\u002Fintc\u002Fv1.0\u002Fauth\u002Fresult",{"id":49705,"depth":4212,"text":49706,"children":51056},[51057,51058,51059,51060],{"id":49709,"depth":4209,"text":49710},{"id":49719,"depth":4209,"text":49719},{"id":49728,"depth":4209,"text":49729},{"id":49738,"depth":4209,"text":49739},{"id":49786,"depth":4212,"text":49787,"children":51062},[51063],{"id":49950,"depth":4209,"text":49951},{"id":49977,"depth":4212,"text":49978,"children":51065},[51066,51068,51070,51072,51073,51075],{"id":49981,"depth":4209,"text":51067},"8.1 백엔드 (malgn-noti-api) — 신규 라우트 3종",{"id":50034,"depth":4209,"text":51069},"8.2 신규 DB 테이블 — TB_NICE_AUTH",{"id":50154,"depth":4209,"text":51071},"8.3 TB_USER 스키마 확장",{"id":50195,"depth":4209,"text":50196},{"id":50205,"depth":4209,"text":51074},"8.5 프런트 — signup.vue Step 4 교체",{"id":50496,"depth":4209,"text":51076},"8.6 \u002Fauth\u002Fsignup 라우트 확장",{"id":50579,"depth":4212,"text":50580,"children":51078},[51079,51081,51082],{"id":50583,"depth":4209,"text":51080},"9.1 Outbound IP 화이트리스트 — Workers 환경 이슈 → 검증됨 (6\u002F2 §16)",{"id":50718,"depth":4209,"text":50719},{"id":50731,"depth":4209,"text":50732},{"id":50766,"depth":4212,"text":50767},{"id":50876,"depth":4212,"text":50877},{"id":51015,"depth":4212,"text":51016},{},"\u002Fnice_auth",{"title":48765,"description":4208},"NICE_AUTH","ffZJ7zlakF7p5VSIrzGy09nRDA7vTG7dVm-mcpFtjE8",{"id":51092,"title":51093,"body":51094,"description":52328,"extension":4257,"meta":52329,"navigation":4259,"path":52330,"seo":52331,"stem":52332,"__hash__":52333},"docs\u002FPAGES.md","페이지 목록 (malgn-noti 사용자단)",{"type":8,"value":51095,"toc":52314},[51096,51099,51111,51125,51128,51130,51134,51254,51258,51356,51360,51462,51466,51549,51553,51652,51656,51767,51771,51814,51818,51940,51944,52013,52017,52085,52089,52236,52238,52240,52281,52286],[11,51097,51093],{"id":51098},"페이지-목록-malgn-noti-사용자단",[18,51100,51101,51102,51106,51107,51110],{},"전체 65 페이지. 인증 정책은 ",[26,51103,51104],{"href":46164},[32,51105,29000],{}," 글로벌 가드 — ",[32,51108,51109],{},"definePageMeta({ auth: false })","로 명시한 페이지만 공개, 나머지는 모두 로그인 필수.",[18,51112,51113,51114,51117,51118,13735,51122,51124],{},"로그인 필요 페이지에 토큰 쿠키 없이 진입하면 ",[32,51115,51116],{},"\u002Flogin?redirect=\u003C원래경로>"," 로 리다이렉트. 토큰 검증은 ",[26,51119,51120],{"href":46190},[32,51121,29018],{},[32,51123,3987],{}," 호출로 수행.",[18,51126,51127],{},"범례: 🔒 = 로그인 필요 · 🌐 = 공개",[73,51129],{},[76,51131,51133],{"id":51132},"인증-가입-8","인증 \u002F 가입 (8)",[101,51135,51136,51146],{},[104,51137,51138],{},[107,51139,51140,51142,51144],{},[110,51141,11110],{},[110,51143,17982],{},[110,51145,16413],{},[126,51147,51148,51163,51175,51188,51201,51214,51227,51240],{},[107,51149,51150,51154,51160],{},[131,51151,51152],{},[32,51153,4361],{},[131,51155,51156,51159],{},[26,51157,19850],{"href":51158},"..\u002Fapp\u002Fpages\u002Findex.vue"," — 비로그인 공개 랜딩",[131,51161,51162],{},"🌐",[107,51164,51165,51169,51173],{},[131,51166,51167],{},[32,51168,9335],{},[131,51170,51171],{},[26,51172,14160],{"href":45658},[131,51174,51162],{},[107,51176,51177,51181,51186],{},[131,51178,51179],{},[32,51180,17881],{},[131,51182,51183,51185],{},[26,51184,45677],{"href":45676}," — OTP\u002F이메일 보안인증",[131,51187,51162],{},[107,51189,51190,51194,51199],{},[131,51191,51192],{},[32,51193,17640],{},[131,51195,51196,51198],{},[26,51197,14163],{"href":4551}," — 회원가입 5스텝",[131,51200,51162],{},[107,51202,51203,51207,51212],{},[131,51204,51205],{},[32,51206,17738],{},[131,51208,51209,51211],{},[26,51210,45695],{"href":45694}," — 재설정 요청",[131,51213,51162],{},[107,51215,51216,51220,51225],{},[131,51217,51218],{},[32,51219,18318],{},[131,51221,51222,51224],{},[26,51223,45713],{"href":45712}," — 새 비밀번호 입력",[131,51226,51162],{},[107,51228,51229,51233,51238],{},[131,51230,51231],{},[32,51232,19208],{},[131,51234,51235,51237],{},[26,51236,19674],{"href":45729}," — 초대 링크 등록",[131,51239,51162],{},[107,51241,51242,51246,51251],{},[131,51243,51244],{},[32,51245,11278],{},[131,51247,51248,51250],{},[26,51249,13373],{"href":4440}," — 로그인 후 대시보드",[131,51252,51253],{},"🔒",[76,51255,51257],{"id":51256},"발송-6","발송 (6)",[101,51259,51260,51270],{},[104,51261,51262],{},[107,51263,51264,51266,51268],{},[110,51265,11110],{},[110,51267,17982],{},[110,51269,16413],{},[126,51271,51272,51285,51299,51313,51327,51341],{},[107,51273,51274,51278,51283],{},[131,51275,51276],{},[32,51277,12271],{},[131,51279,51280],{},[26,51281,51282],{"href":4471},"send\u002Fsms.vue",[131,51284,51253],{},[107,51286,51287,51291,51297],{},[131,51288,51289],{},[32,51290,17575],{},[131,51292,51293],{},[26,51294,51296],{"href":51295},"..\u002Fapp\u002Fpages\u002Fsend\u002Frcs.vue","send\u002Frcs.vue",[131,51298,51253],{},[107,51300,51301,51305,51311],{},[131,51302,51303],{},[32,51304,12298],{},[131,51306,51307],{},[26,51308,51310],{"href":51309},"..\u002Fapp\u002Fpages\u002Fsend\u002Fkakao.vue","send\u002Fkakao.vue",[131,51312,51253],{},[107,51314,51315,51319,51325],{},[131,51316,51317],{},[32,51318,23567],{},[131,51320,51321],{},[26,51322,51324],{"href":51323},"..\u002Fapp\u002Fpages\u002Fsend\u002Femail.vue","send\u002Femail.vue",[131,51326,51253],{},[107,51328,51329,51333,51339],{},[131,51330,51331],{},[32,51332,17578],{},[131,51334,51335],{},[26,51336,51338],{"href":51337},"..\u002Fapp\u002Fpages\u002Fsend\u002Fpush.vue","send\u002Fpush.vue",[131,51340,51253],{},[107,51342,51343,51347,51354],{},[131,51344,51345],{},[32,51346,15268],{},[131,51348,51349,51353],{},[26,51350,51352],{"href":51351},"..\u002Fapp\u002Fpages\u002Fsend\u002Fflow.vue","send\u002Fflow.vue"," — 복합(플로우)",[131,51355,51253],{},[76,51357,51359],{"id":51358},"이력-통계-6","이력 \u002F 통계 (6)",[101,51361,51362,51372],{},[104,51363,51364],{},[107,51365,51366,51368,51370],{},[110,51367,11110],{},[110,51369,17982],{},[110,51371,16413],{},[126,51373,51374,51388,51403,51418,51433,51448],{},[107,51375,51376,51380,51386],{},[131,51377,51378],{},[32,51379,15817],{},[131,51381,51382],{},[26,51383,51385],{"href":51384},"..\u002Fapp\u002Fpages\u002Fhistory\u002Fsms.vue","history\u002Fsms.vue",[131,51387,51253],{},[107,51389,51390,51395,51401],{},[131,51391,51392],{},[32,51393,51394],{},"\u002Fhistory\u002Frcs",[131,51396,51397],{},[26,51398,51400],{"href":51399},"..\u002Fapp\u002Fpages\u002Fhistory\u002Frcs.vue","history\u002Frcs.vue",[131,51402,51253],{},[107,51404,51405,51410,51416],{},[131,51406,51407],{},[32,51408,51409],{},"\u002Fhistory\u002Fkakao",[131,51411,51412],{},[26,51413,51415],{"href":51414},"..\u002Fapp\u002Fpages\u002Fhistory\u002Fkakao.vue","history\u002Fkakao.vue",[131,51417,51253],{},[107,51419,51420,51425,51431],{},[131,51421,51422],{},[32,51423,51424],{},"\u002Fhistory\u002Femail",[131,51426,51427],{},[26,51428,51430],{"href":51429},"..\u002Fapp\u002Fpages\u002Fhistory\u002Femail.vue","history\u002Femail.vue",[131,51432,51253],{},[107,51434,51435,51440,51446],{},[131,51436,51437],{},[32,51438,51439],{},"\u002Fhistory\u002Fpush",[131,51441,51442],{},[26,51443,51445],{"href":51444},"..\u002Fapp\u002Fpages\u002Fhistory\u002Fpush.vue","history\u002Fpush.vue",[131,51447,51253],{},[107,51449,51450,51455,51460],{},[131,51451,51452],{},[32,51453,51454],{},"\u002Fhistory\u002Fstats",[131,51456,51457],{},[26,51458,14136],{"href":51459},"..\u002Fapp\u002Fpages\u002Fhistory\u002Fstats.vue",[131,51461,51253],{},[76,51463,51465],{"id":51464},"주소록-5","주소록 (5)",[101,51467,51468,51478],{},[104,51469,51470],{},[107,51471,51472,51474,51476],{},[110,51473,11110],{},[110,51475,17982],{},[110,51477,16413],{},[126,51479,51480,51492,51505,51520,51534],{},[107,51481,51482,51486,51490],{},[131,51483,51484],{},[32,51485,16262],{},[131,51487,51488],{},[26,51489,14142],{"href":4539},[131,51491,51253],{},[107,51493,51494,51498,51503],{},[131,51495,51496],{},[32,51497,17175],{},[131,51499,51500],{},[26,51501,16180],{"href":51502},"..\u002Fapp\u002Fpages\u002Fcontacts\u002Fgroups.vue",[131,51504,51253],{},[107,51506,51507,51511,51518],{},[131,51508,51509],{},[32,51510,17241],{},[131,51512,51513,51517],{},[26,51514,51516],{"href":51515},"..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout.vue","contacts\u002Foptout.vue"," — 휴대폰 거부",[131,51519,51253],{},[107,51521,51522,51526,51532],{},[131,51523,51524],{},[32,51525,17246],{},[131,51527,51528],{},[26,51529,51531],{"href":51530},"..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout-email.vue","contacts\u002Foptout-email.vue",[131,51533,51253],{},[107,51535,51536,51540,51547],{},[131,51537,51538],{},[32,51539,17250],{},[131,51541,51542,51546],{},[26,51543,51545],{"href":51544},"..\u002Fapp\u002Fpages\u002Fcontacts\u002Foptout-token.vue","contacts\u002Foptout-token.vue"," — PUSH 토큰",[131,51548,51253],{},[76,51550,51552],{"id":51551},"발신-정보-6","발신 정보 (6)",[101,51554,51555,51565],{},[104,51556,51557],{},[107,51558,51559,51561,51563],{},[110,51560,11110],{},[110,51562,17982],{},[110,51564,16413],{},[126,51566,51567,51580,51595,51609,51623,51638],{},[107,51568,51569,51573,51578],{},[131,51570,51571],{},[32,51572,16339],{},[131,51574,51575,51577],{},[26,51576,6412],{"href":6411}," — 발신번호",[131,51579,51253],{},[107,51581,51582,51587,51593],{},[131,51583,51584],{},[32,51585,51586],{},"\u002Fsender\u002Fbrands",[131,51588,51589,51592],{},[26,51590,16289],{"href":51591},"..\u002Fapp\u002Fpages\u002Fsender\u002Fbrands.vue"," — RCS 브랜드",[131,51594,51253],{},[107,51596,51597,51602,51607],{},[131,51598,51599],{},[32,51600,51601],{},"\u002Fsender\u002Fdomains",[131,51603,51604,51606],{},[26,51605,6470],{"href":6469}," — 이메일 도메인 + DKIM",[131,51608,51253],{},[107,51610,51611,51615,51621],{},[131,51612,51613],{},[32,51614,17023],{},[131,51616,51617,51620],{},[26,51618,16971],{"href":51619},"..\u002Fapp\u002Fpages\u002Fsender\u002Fpush-cert.vue"," — FCM\u002FAPNs",[131,51622,51253],{},[107,51624,51625,51630,51636],{},[131,51626,51627],{},[32,51628,51629],{},"\u002Fsender\u002Fprofiles",[131,51631,51632,51635],{},[26,51633,16913],{"href":51634},"..\u002Fapp\u002Fpages\u002Fsender\u002Fprofiles.vue"," — 카카오 발신 프로필",[131,51637,51253],{},[107,51639,51640,51644,51650],{},[131,51641,51642],{},[32,51643,17026],{},[131,51645,51646,51649],{},[26,51647,16989],{"href":51648},"..\u002Fapp\u002Fpages\u002Fsender\u002Foptout-080.vue"," — 080 수신거부",[131,51651,51253],{},[76,51653,51655],{"id":51654},"메시지-관리-7","메시지 관리 (7)",[101,51657,51658,51668],{},[104,51659,51660],{},[107,51661,51662,51664,51666],{},[110,51663,11110],{},[110,51665,17982],{},[110,51667,16413],{},[126,51669,51670,51684,51697,51711,51724,51738,51753],{},[107,51671,51672,51676,51682],{},[131,51673,51674],{},[32,51675,17424],{},[131,51677,51678],{},[26,51679,51681],{"href":51680},"..\u002Fapp\u002Fpages\u002Fmanage\u002Fsms.vue","manage\u002Fsms.vue",[131,51683,51253],{},[107,51685,51686,51690,51695],{},[131,51687,51688],{},[32,51689,17908],{},[131,51691,51692],{},[26,51693,17666],{"href":51694},"..\u002Fapp\u002Fpages\u002Fmanage\u002Frcs.vue",[131,51696,51253],{},[107,51698,51699,51703,51709],{},[131,51700,51701],{},[32,51702,17450],{},[131,51704,51705],{},[26,51706,51708],{"href":51707},"..\u002Fapp\u002Fpages\u002Fmanage\u002Fkakao.vue","manage\u002Fkakao.vue",[131,51710,51253],{},[107,51712,51713,51717,51722],{},[131,51714,51715],{},[32,51716,17764],{},[131,51718,51719],{},[26,51720,17663],{"href":51721},"..\u002Fapp\u002Fpages\u002Fmanage\u002Femail.vue",[131,51723,51253],{},[107,51725,51726,51730,51736],{},[131,51727,51728],{},[32,51729,18045],{},[131,51731,51732],{},[26,51733,51735],{"href":51734},"..\u002Fapp\u002Fpages\u002Fmanage\u002Fpush.vue","manage\u002Fpush.vue",[131,51737,51253],{},[107,51739,51740,51744,51751],{},[131,51741,51742],{},[32,51743,18349],{},[131,51745,51746,51750],{},[26,51747,51749],{"href":51748},"..\u002Fapp\u002Fpages\u002Fmanage\u002Flanding.vue","manage\u002Flanding.vue"," — 랜딩페이지",[131,51752,51253],{},[107,51754,51755,51759,51765],{},[131,51756,51757],{},[32,51758,18229],{},[131,51760,51761,51764],{},[26,51762,18188],{"href":51763},"..\u002Fapp\u002Fpages\u002Fmanage\u002Fsettings.vue"," — 메시지 발송 상세 설정",[131,51766,51253],{},[76,51768,51770],{"id":51769},"크레딧-결제-2","크레딧 \u002F 결제 (2)",[101,51772,51773,51783],{},[104,51774,51775],{},[107,51776,51777,51779,51781],{},[110,51778,11110],{},[110,51780,17982],{},[110,51782,16413],{},[126,51784,51785,51799],{},[107,51786,51787,51791,51797],{},[131,51788,51789],{},[32,51790,18609],{},[131,51792,51793,51796],{},[26,51794,14148],{"href":51795},"..\u002Fapp\u002Fpages\u002Fcharge\u002Findex.vue"," — 충전",[131,51798,51253],{},[107,51800,51801,51805,51812],{},[131,51802,51803],{},[32,51804,18621],{},[131,51806,51807,51811],{},[26,51808,51810],{"href":51809},"..\u002Fapp\u002Fpages\u002Fcharge\u002Fresult.vue","charge\u002Fresult.vue"," — 충전 결과",[131,51813,51253],{},[76,51815,51817],{"id":51816},"나의-페이지-계정-8","나의 페이지 \u002F 계정 (8)",[101,51819,51820,51830],{},[104,51821,51822],{},[107,51823,51824,51826,51828],{},[110,51825,11110],{},[110,51827,17982],{},[110,51829,16413],{},[126,51831,51832,51846,51858,51871,51884,51899,51913,51927],{},[107,51833,51834,51838,51844],{},[131,51835,51836],{},[32,51837,3991],{},[131,51839,51840,51843],{},[26,51841,51842],{"href":45773},"account\u002Fsettings.vue"," — 회원 정보",[131,51845,51253],{},[107,51847,51848,51852,51856],{},[131,51849,51850],{},[32,51851,45821],{},[131,51853,51854,48593],{},[26,51855,19241],{"href":45826},[131,51857,51253],{},[107,51859,51860,51864,51869],{},[131,51861,51862],{},[32,51863,19452],{},[131,51865,51866,51868],{},[26,51867,19283],{"href":45846}," — 보안 로그인",[131,51870,51253],{},[107,51872,51873,51877,51882],{},[131,51874,51875],{},[32,51876,19455],{},[131,51878,51879,51881],{},[26,51880,19310],{"href":45866}," — 멀티 계정",[131,51883,51253],{},[107,51885,51886,51890,51897],{},[131,51887,51888],{},[32,51889,19458],{},[131,51891,51892,51894,51895],{},[26,51893,19338],{"href":45797}," — 사업자등록증 제출·이용계약 전자서명·미승인 사용자 메인 진입점. 상세는 ",[26,51896,45808],{"href":45570},[131,51898,51253],{},[107,51900,51901,51905,51911],{},[131,51902,51903],{},[32,51904,20012],{},[131,51906,51907,51910],{},[26,51908,51909],{"href":45905},"account\u002Fbilling.vue"," — 결제 정보",[131,51912,51253],{},[107,51914,51915,51919,51925],{},[131,51916,51917],{},[32,51918,18635],{},[131,51920,51921,51924],{},[26,51922,51923],{"href":45885},"account\u002Fcards.vue"," — 결제 카드",[131,51926,51253],{},[107,51928,51929,51933,51938],{},[131,51930,51931],{},[32,51932,19629],{},[131,51934,51935,51937],{},[26,51936,19479],{"href":45922}," — 크레딧 내역",[131,51939,51253],{},[76,51941,51943],{"id":51942},"문의-4","문의 (4)",[101,51945,51946,51956],{},[104,51947,51948],{},[107,51949,51950,51952,51954],{},[110,51951,11110],{},[110,51953,17982],{},[110,51955,16413],{},[126,51957,51958,51972,51986,52000],{},[107,51959,51960,51964,51970],{},[131,51961,51962],{},[32,51963,18439],{},[131,51965,51966,51969],{},[26,51967,19547],{"href":51968},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiries\u002Findex.vue"," — 내 문의 목록",[131,51971,51253],{},[107,51973,51974,51978,51984],{},[131,51975,51976],{},[32,51977,19634],{},[131,51979,51980,51983],{},[26,51981,19574],{"href":51982},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiries\u002Fdetail.vue"," — 문의 상세",[131,51985,51253],{},[107,51987,51988,51992,51998],{},[131,51989,51990],{},[32,51991,16882],{},[131,51993,51994,51997],{},[26,51995,19593],{"href":51996},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiry\u002Findex.vue"," — 1:1 문의 작성",[131,51999,51253],{},[107,52001,52002,52006,52011],{},[131,52003,52004],{},[32,52005,18433],{},[131,52007,52008],{},[26,52009,19700],{"href":52010},"..\u002Fapp\u002Fpages\u002Faccount\u002Finquiry\u002Fcomplete.vue",[131,52012,51253],{},[76,52014,52016],{"id":52015},"운영-가이드-메타-4","운영 가이드 \u002F 메타 (4)",[101,52018,52019,52029],{},[104,52020,52021],{},[107,52022,52023,52025,52027],{},[110,52024,11110],{},[110,52026,17982],{},[110,52028,16413],{},[126,52030,52031,52044,52057,52070],{},[107,52032,52033,52037,52042],{},[131,52034,52035],{},[32,52036,4457],{},[131,52038,52039,52041],{},[26,52040,13370],{"href":4453}," — 디자인 가이드 18섹션",[131,52043,51253],{},[107,52045,52046,52050,52055],{},[131,52047,52048],{},[32,52049,5744],{},[131,52051,52052,52054],{},[26,52053,5741],{"href":5740}," — 운영 가이드",[131,52056,51253],{},[107,52058,52059,52063,52068],{},[131,52060,52061],{},[32,52062,17936],{},[131,52064,52065],{},[26,52066,17867],{"href":52067},"..\u002Fapp\u002Fpages\u002Fsitemap.vue",[131,52069,51253],{},[107,52071,52072,52076,52083],{},[131,52073,52074],{},[32,52075,26863],{},[131,52077,52078,52082],{},[26,52079,52081],{"href":52080},"..\u002Fapp\u002Fpages\u002Fwbs.vue","wbs.vue"," — WBS 진척률",[131,52084,51162],{},[76,52086,52088],{"id":52087},"시스템-페이지-9","시스템 페이지 (9)",[101,52090,52091,52101],{},[104,52092,52093],{},[107,52094,52095,52097,52099],{},[110,52096,11110],{},[110,52098,17982],{},[110,52100,16413],{},[126,52102,52103,52117,52133,52148,52163,52178,52193,52208,52223],{},[107,52104,52105,52109,52115],{},[131,52106,52107],{},[32,52108,11289],{},[131,52110,52111],{},[26,52112,52114],{"href":52113},"..\u002Fapp\u002Fpages\u002F404.vue","404.vue",[131,52116,51162],{},[107,52118,52119,52124,52131],{},[131,52120,52121],{},[32,52122,52123],{},"\u002Ferror",[131,52125,52126,52130],{},[26,52127,52129],{"href":52128},"..\u002Fapp\u002Fpages\u002Ferror.vue","error.vue"," — 시스템 에러",[131,52132,51162],{},[107,52134,52135,52140,52146],{},[131,52136,52137],{},[32,52138,52139],{},"\u002Ftemplete\u002Ferror\u002Fsystem",[131,52141,52142],{},[26,52143,52145],{"href":52144},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Ferror\u002Fsystem.vue","templete\u002Ferror\u002Fsystem.vue",[131,52147,51162],{},[107,52149,52150,52155,52161],{},[131,52151,52152],{},[32,52153,52154],{},"\u002Ftemplete\u002Ferror\u002Fnot-found",[131,52156,52157],{},[26,52158,52160],{"href":52159},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Ferror\u002Fnot-found.vue","templete\u002Ferror\u002Fnot-found.vue",[131,52162,51162],{},[107,52164,52165,52170,52176],{},[131,52166,52167],{},[32,52168,52169],{},"\u002Ftemplete\u002Ferror\u002Fnetwork",[131,52171,52172],{},[26,52173,52175],{"href":52174},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Ferror\u002Fnetwork.vue","templete\u002Ferror\u002Fnetwork.vue",[131,52177,51162],{},[107,52179,52180,52185,52191],{},[131,52181,52182],{},[32,52183,52184],{},"\u002Ftemplete\u002Finspection\u002Femergency",[131,52186,52187],{},[26,52188,52190],{"href":52189},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Finspection\u002Femergency.vue","templete\u002Finspection\u002Femergency.vue",[131,52192,51162],{},[107,52194,52195,52200,52206],{},[131,52196,52197],{},[32,52198,52199],{},"\u002Ftemplete\u002Finspection\u002Fscheduled",[131,52201,52202],{},[26,52203,52205],{"href":52204},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Finspection\u002Fscheduled.vue","templete\u002Finspection\u002Fscheduled.vue",[131,52207,51162],{},[107,52209,52210,52214,52221],{},[131,52211,52212],{},[32,52213,46263],{},[131,52215,52216,52220],{},[26,52217,52219],{"href":52218},"..\u002Fapp\u002Fpages\u002Ftemplete\u002Femail\u002Fverify.vue","templete\u002Femail\u002Fverify.vue"," — 이메일 인증 메일 템플릿",[131,52222,51162],{},[107,52224,52225,52229,52234],{},[131,52226,52227],{},[32,52228,46247],{},[131,52230,52231],{},[26,52232,52233],{"href":46252},"templete\u002Femail\u002Freset-password.vue",[131,52235,51162],{},[73,52237],{},[76,52239,124],{"id":124},[101,52241,52242,52252],{},[104,52243,52244],{},[107,52245,52246,52249],{},[110,52247,52248],{},"구분",[110,52250,52251],{},"페이지 수",[126,52253,52254,52266,52273],{},[107,52255,52256,52261],{},[131,52257,52258],{},[21,52259,52260],{},"총",[131,52262,52263],{},[21,52264,52265],{},"65",[107,52267,52268,52271],{},[131,52269,52270],{},"🌐 공개 (auth: false)",[131,52272,40765],{},[107,52274,52275,52278],{},[131,52276,52277],{},"🔒 로그인 필요 (기본 가드)",[131,52279,52280],{},"48",[18,52282,52283],{},[21,52284,52285],{},"공개 페이지 정책 — 인증 가드 통과 규칙:",[81,52287,52288,52299,52307],{},[84,52289,52290,52291,4473,52293,4473,52295,4473,52297,8728],{},"가입·로그인·재설정 흐름 7종 (",[32,52292,9335],{},[32,52294,17640],{},[32,52296,17738],{},[32,52298,19208],{},[84,52300,52301,52302,52304,52305],{},"비로그인 랜딩 ",[32,52303,4361],{}," + WBS ",[32,52306,26863],{},[84,52308,52309,52310,4536,52312],{},"시스템\u002F에러\u002F점검 템플릿 8종 + ",[32,52311,11289],{},[32,52313,52123],{},{"title":4208,"searchDepth":4209,"depth":4209,"links":52315},[52316,52317,52318,52319,52320,52321,52322,52323,52324,52325,52326,52327],{"id":51132,"depth":4212,"text":51133},{"id":51256,"depth":4212,"text":51257},{"id":51358,"depth":4212,"text":51359},{"id":51464,"depth":4212,"text":51465},{"id":51551,"depth":4212,"text":51552},{"id":51654,"depth":4212,"text":51655},{"id":51769,"depth":4212,"text":51770},{"id":51816,"depth":4212,"text":51817},{"id":51942,"depth":4212,"text":51943},{"id":52015,"depth":4212,"text":52016},{"id":52087,"depth":4212,"text":52088},{"id":124,"depth":4212,"text":124},"전체 65 페이지. 인증 정책은 app\u002Fmiddleware\u002Fauth.global.ts 글로벌 가드 — definePageMeta({ auth: false })로 명시한 페이지만 공개, 나머지는 모두 로그인 필수.",{},"\u002Fpages",{"title":51093,"description":52328},"PAGES","AtNMUAF5OJdvZUvlc-fNWHAz6GEeLSqhm3YyJzLz4lo",{"id":52335,"title":52336,"body":52337,"description":4208,"extension":4257,"meta":54630,"navigation":4259,"path":54631,"seo":54632,"stem":54633,"__hash__":54634},"docs\u002Fpages\u002FCONTRACT.md","\u002Faccount\u002Fcontract — 계약 관리 기획·로직 정본",{"type":8,"value":52338,"toc":54569},[52339,52345,52383,52385,52389,52491,52493,52497,52500,52504,52510,52519,52523,52529,52537,52541,52547,52565,52569,52576,52593,52599,52601,52605,52609,52622,52719,52723,52729,52737,52842,52845,52868,52874,52921,52927,52970,52990,52998,53002,53007,53012,53085,53098,53102,53108,53112,53118,53127,53141,53157,53163,53175,53177,53181,53313,53315,53319,53326,53386,53394,53396,53400,53406,53412,53418,53424,53427,53451,53453,53457,53461,53479,53483,53502,53506,53582,53586,53606,53610,53624,53628,53649,53653,53686,53690,53701,53703,53707,53711,53778,53782,53865,53874,53878,53884,53936,53943,53947,53995,53998,54000,54004,54010,54094,54100,54106,54160,54178,54185,54192,54194,54198,54431,54433,54435,54439,54451,54455,54485,54489,54519,54523,54567],[11,52340,52342,52344],{"id":52341},"accountcontract-계약-관리-기획로직-정본",[32,52343,19458],{}," — 계약 관리 기획·로직 정본",[15,52346,52347,52361,52378],{},[18,52348,52349,52351,52352,4361,52354,52356,52357,52360],{},[21,52350,23],{},": 사업자등록증 등록·심사·이용계약 전자체결·가입 서류 첨부를 한 화면에서 관리.\n회원가입한 사업자(",[32,52353,33543],{},[32,52355,33560],{},")의 ",[21,52358,52359],{},"미승인 상태 메인 진입점"," + 승인 후 계약 갱신·서류 관리 화면.",[18,52362,52363,47,52365,52368,52369,52372,52373,52377],{},[21,52364,48793],{},[26,52366,48801],{"href":52367},".\u002FSIGNUP"," §3·§4 \u002F ",[26,52370,52371],{"href":30307},"..\u002FMEMBERSHIP.md"," §1.2·§2·§8 \u002F\n",[26,52374,52376],{"href":52375},"..\u002Fhistory\u002Fhistory.20260602","..\u002Fhistory\u002Fhistory.20260602.md"," §7·§10·§11·§12·§13·§14·§15",[18,52379,52380,52382],{},[21,52381,45592],{},": 2026-06-02 (§15 반영)",[73,52384],{},[76,52386,52388],{"id":52387},"_1-페이지-개요","1. 페이지 개요",[101,52390,52391,52399],{},[104,52392,52393],{},[107,52394,52395,52397],{},[110,52396,6889],{},[110,52398,4800],{},[126,52400,52401,52409,52421,52433,52462,52475],{},[107,52402,52403,52405],{},[131,52404,22556],{},[131,52406,52407],{},[32,52408,19458],{},[107,52410,52411,52413],{},[131,52412,28918],{},[131,52414,52415],{},[26,52416,52418],{"href":52417},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fcontract.vue",[32,52419,52420],{},"app\u002Fpages\u002Faccount\u002Fcontract.vue",[107,52422,52423,52426],{},[131,52424,52425],{},"메인 컴포넌트",[131,52427,52428,52432],{},[26,52429,52430],{"href":37240},[32,52431,19343],{}," (~770 라인)",[107,52434,52435,52438],{},[131,52436,52437],{},"보조 컴포넌트",[131,52439,52440,52445,52446,52451,52452,7505,52457],{},[26,52441,52443],{"href":52442},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppContractViewDialog.vue",[32,52444,19392],{}," — 계약서 미리보기 \u002F ",[26,52447,52449],{"href":52448},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppContractSignDialog.vue",[32,52450,19398],{}," — 본인인증 + 전자서명 3-스텝 위저드 (~700 라인) \u002F ",[26,52453,52455],{"href":52454},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppUploadGuideDialog.vue",[32,52456,19408],{},[26,52458,52460],{"href":52459},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppFilePreviewDialog.vue",[32,52461,19414],{},[107,52463,52464,52467],{},[131,52465,52466],{},"공통 셸",[131,52468,52469,52474],{},[26,52470,52472],{"href":52471},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppMyPageShell.vue",[32,52473,5836],{}," — 나의 페이지 좌측 메뉴 + 본문 슬롯",[107,52476,52477,52480],{},[131,52478,52479],{},"접근 권한",[131,52481,52482,52483,7505,52485,52487,52488,52490],{},"인증된 사업자(",[32,52484,33543],{},[32,52486,33560],{},")만 — 개인(",[32,52489,33576],{},")은 메뉴 미노출(후속)",[73,52492],{},[76,52494,52496],{"id":52495},"_2-진입-경로-4가지","2. 진입 경로 — 4가지",[18,52498,52499],{},"미승인 상태의 사업자가 이 화면에 도달하는 경로 4가지가 모두 정합되어 있어야 한다.",[237,52501,52503],{"id":52502},"_21-회원가입-직후-자동","2.1 회원가입 직후 (자동)",[5251,52505,52508],{"className":52506,"code":52507,"language":5256},[5254],"\u002Fsignup Step 5 → \"계약 관리로 이동\" 버튼\n  → if isBusiness: navigateTo('\u002Faccount\u002Fcontract')\n     else: navigateTo('\u002Fhome')\n",[32,52509,52507],{"__ignoreMap":4208},[18,52511,52512,52513],{},"코드: ",[26,52514,52516,52517],{"href":52515},"..\u002F..\u002Fapp\u002Fpages\u002Fsignup.vue#L456","signup.vue ",[32,52518,29092],{},[237,52520,52522],{"id":52521},"_22-로그인-직후-자동","2.2 로그인 직후 (자동)",[5251,52524,52527],{"className":52525,"code":52526,"language":5256},[5254],"\u002Flogin → loginByEmail() → fetchMe()\n  → if approvalState !== 'approved': navigateTo('\u002Faccount\u002Fcontract')\n     else: navigateTo(redirect ?? '\u002Fhome')\n",[32,52528,52526],{"__ignoreMap":4208},[18,52530,52512,52531],{},[26,52532,52534,52535],{"href":52533},"..\u002F..\u002Fapp\u002Fpages\u002Flogin\u002Findex.vue#L44","login\u002Findex.vue ",[32,52536,29050],{},[237,52538,52540],{"id":52539},"_23-미들웨어-리다이렉트-다른-차단-페이지-시도","2.3 미들웨어 리다이렉트 (다른 차단 페이지 시도)",[5251,52542,52545],{"className":52543,"code":52544,"language":5256},[5254],"\u002Fhome·\u002Fsend\u002F*·\u002Fcontacts·… 진입 시도\n  → middleware\u002Fapproval.global.ts\n  → if approvalState !== 'approved' && path ∉ ALLOWED_PREFIXES:\n       return navigateTo('\u002Faccount\u002Fcontract')\n",[32,52546,52544],{"__ignoreMap":4208},[18,52548,52549,52550,4420,52552,4420,52554,4420,52556,4420,52558,4420,52560,52562,52563],{},"허용 경로: ",[32,52551,5699],{},[32,52553,5744],{},[32,52555,4457],{},[32,52557,26863],{},[32,52559,18473],{},[32,52561,29216],{},"\n코드: ",[26,52564,34894],{"href":35024},[237,52566,52568],{"id":52567},"_24-글로벌-띠-cta-수동","2.4 글로벌 띠 CTA (수동)",[18,52570,52571,52575],{},[26,52572,52573],{"href":34923},[32,52574,34887],{}," (모든 페이지 layout 최상단) — CTA 클릭 시:",[81,52577,52578,52583,52588],{},[84,52579,52580,52582],{},[32,52581,27043],{}," → \"사업자등록증 등록\"",[84,52584,52585,52587],{},[32,52586,36757],{}," → \"진행 상태 보기\"",[84,52589,52590,52592],{},[32,52591,33601],{}," → \"다시 제출하기\"",[18,52594,52595,52596,52598],{},"→ 모두 ",[32,52597,19458],{},"로 이동.",[73,52600],{},[76,52602,52604],{"id":52603},"_3-화면-구성-3-영역","3. 화면 구성 — 3 영역",[237,52606,52608],{"id":52607},"_31-패널-상단-상태-카드-12-신규","3.1 패널 상단 상태 카드 (§12 신규)",[18,52610,52611,52612,4361,52614,4361,52616,52618,52619,52621],{},"미승인 상태(",[32,52613,27043],{},[32,52615,36757],{},[32,52617,33601],{},")일 때만 노출. 회사 ",[32,52620,20267],{},"에 따라 톤·아이콘·메시지 분기:",[101,52623,52624,52639],{},[104,52625,52626],{},[107,52627,52628,52630,52633,52635,52637],{},[110,52629,31998],{},[110,52631,52632],{},"톤",[110,52634,7790],{},[110,52636,27332],{},[110,52638,7185],{},[126,52640,52641,52660,52679,52704],{},[107,52642,52643,52647,52649,52654,52657],{},[131,52644,52645],{},[32,52646,27043],{},[131,52648,19367],{},[131,52650,52651],{},[32,52652,52653],{},"i-lucide-clock",[131,52655,52656],{},"\"사업자등록증을 등록해 주세요\"",[131,52658,52659],{},"\"가입서류 첨부 영역에서 사업자등록증(PDF, 최대 10MB)을 업로드하시면 심사가 시작됩니다.\"",[107,52661,52662,52666,52668,52673,52676],{},[131,52663,52664],{},[32,52665,36757],{},[131,52667,19359],{},[131,52669,52670],{},[32,52671,52672],{},"i-lucide-loader-circle",[131,52674,52675],{},"\"사업자등록증 심사 중입니다\"",[131,52677,52678],{},"\"영업일 기준 1~2일 내에 심사 결과를 안내드립니다. 추가 서류 첨부가 필요하면 가입서류 영역에서 진행할 수 있습니다.\"",[107,52680,52681,52685,52687,52692,52695],{},[131,52682,52683],{},[32,52684,33601],{},[131,52686,5975],{},[131,52688,52689],{},[32,52690,52691],{},"i-lucide-circle-x",[131,52693,52694],{},"\"사업자등록증 심사가 반려되었습니다\"",[131,52696,52697,52698,52703],{},"\"반려 사유: ",[6818,52699,52700],{},[52701,52702],"binding",{"value":33706}," · 사업자등록증을 새로 첨부하면 심사가 다시 시작됩니다.\"",[107,52705,52706,52710,52712,52714,52717],{},[131,52707,52708],{},[32,52709,33595],{},[131,52711,322],{},[131,52713,322],{},[131,52715,52716],{},"카드 자체 비표시",[131,52718,322],{},[237,52720,52722],{"id":52721},"_32-이용계약-체결","3.2 이용계약 체결",[18,52724,52725,52726,52728],{},"전자계약 방식의 이용계약서 카드 리스트. 데이터 소스: ",[32,52727,36268],{}," (§11).",[18,52730,52731,6685,52734,8864],{},[21,52732,52733],{},"상태 4종",[32,52735,52736],{},"TB_CONTRACT.contract_state",[101,52738,52739,52757],{},[104,52740,52741],{},[107,52742,52743,52745,52748,52750,52755],{},[110,52744,31998],{},[110,52746,52747],{},"라벨",[110,52749,7790],{},[110,52751,52752],{},[32,52753,52754],{},"canSign",[110,52756,4629],{},[126,52758,52759,52778,52804,52823],{},[107,52760,52761,52765,52768,52773,52775],{},[131,52762,52763],{},[32,52764,36466],{},[131,52766,52767],{},"최초계약",[131,52769,52770],{},[32,52771,52772],{},"square-pen",[131,52774,3869],{},[131,52776,52777],{},"가입 직후 자동 생성된 1건 (signup auto-create §11 + lazy backfill §13)",[107,52779,52780,52784,52787,52792,52794],{},[131,52781,52782],{},[32,52783,27035],{},[131,52785,52786],{},"체결완료",[131,52788,52789],{},[32,52790,52791],{},"circle-check",[131,52793,5788],{},[131,52795,52796,52797,52799,52800,52803],{},"정상 체결 — 서명자·체결일(",[32,52798,36079],{},")·만료일(",[32,52801,52802],{},"expires_at = signed_at + 2y",") 표시",[107,52805,52806,52810,52813,52818,52820],{},[131,52807,52808],{},[32,52809,36473],{},[131,52811,52812],{},"계약갱신",[131,52814,52815],{},[32,52816,52817],{},"circle-alert",[131,52819,3869],{},[131,52821,52822],{},"운영자가 신규 계약서 배포, 갱신 필요 (현재 운영자단 미구현)",[107,52824,52825,52829,52832,52837,52839],{},[131,52826,52827],{},[32,52828,19371],{},[131,52830,52831],{},"만료",[131,52833,52834],{},[32,52835,52836],{},"archive",[131,52838,5788],{},[131,52840,52841],{},"갱신 계약 체결 시 백엔드가 같은 회사의 다른 done 계약을 자동 expired로 전이",[18,52843,52844],{},"각 카드 액션:",[81,52846,52847,52856],{},[84,52848,52849,52852,52853,52855],{},[21,52850,52851],{},"계약서 확인"," (모든 상태) → ",[32,52854,19392],{}," 모달 (요약본 — 약관 정본 하드코딩)",[84,52857,52858,6685,52861,52864,52865,52867],{},[21,52859,52860],{},"계약체결하기",[32,52862,52863],{},"canSign=true","일 때만) → ",[32,52866,19398],{}," (본인인증 + 3-스텝 위저드, §15)",[252,52869,52871,52872,4343],{"id":52870},"전자서명-위저드-appcontractsigndialog","전자서명 위저드 (",[26,52873,19398],{"href":52448},[101,52875,52876,52887],{},[104,52877,52878],{},[107,52879,52880,52882,52884],{},[110,52881,30322],{},[110,52883,52747],{},[110,52885,52886],{},"내용",[126,52888,52889,52899,52909],{},[107,52890,52891,52893,52896],{},[131,52892,4636],{},[131,52894,52895],{},"제1장 · 총칙 및 서류",[131,52897,52898],{},"제1조~제8조 — 목적·정의·계약 성립·서류 등 (끝까지 스크롤해야 다음 단계)",[107,52900,52901,52903,52906],{},[131,52902,4649],{},[131,52904,52905],{},"제2장 · 이용요금 및 결제",[131,52907,52908],{},"단가표·청구주기·연체·환불 등 (끝까지 스크롤)",[107,52910,52911,52913,52916],{},[131,52912,4662],{},[131,52914,52915],{},"제3장 · 전자서명",[131,52917,52918,52920],{},[21,52919,38534],{}," (§15) → 통과 시 정보 테이블 + 캔버스 노출",[18,52922,52923,52926],{},[21,52924,52925],{},"STEP 3 본인인증 흐름"," (§15):",[6674,52928,52929,52935,52940,52949,52955,52961],{},[84,52930,52931,52932,52934],{},"Dialog open watcher에서 ",[32,52933,29025],{}," 호출 → 휴대폰 최신화",[84,52936,52937,52938,4343],{},"회원 휴대폰을 마스킹 표시(",[32,52939,38602],{},[84,52941,52942,52943,52945,52946,52948],{},"\"인증번호 받기\" → ",[32,52944,31450],{}," (purpose=",[32,52947,38570],{},", 백엔드 §15.1)",[84,52950,52951,52952,52954],{},"6자리 입력 → ",[32,52953,31453],{}," → 통과 시 카드 success 톤 + 캔버스 셋업",[84,52956,52957,52958,52960],{},"서명자명(기본값 ",[32,52959,38668],{},") + 캔버스 ink → \"서명 완료\"",[84,52962,52963,52964,52966,52967,52969],{},"부모에 ",[32,52965,32209],{}," emit → ",[32,52968,36278],{}," (§11)",[18,52971,52972,52973,4339,52975,4339,52977,4339,52979,52981,52982,52984,52985,52987,52988,4703],{},"체결 완료 → 백엔드가 ",[32,52974,36634],{},[32,52976,36287],{},[32,52978,36290],{},[32,52980,36293],{}," UPDATE. ",[32,52983,36473],{},"였다면 같은 회사의 다른 ",[32,52986,27035],{},"은 자동 ",[32,52989,19371],{},[15,52991,52992],{},[18,52993,52994,52997],{},[21,52995,52996],{},"삭제됨 (§15)",": 공인인증서 탭 — STEP 3은 본인인증 → 전자서명 단일 흐름.",[237,52999,53001],{"id":53000},"_33-가입서류-첨부","3.3 가입서류 첨부",[18,53003,53004,53005,52728],{},"PDF만 첨부 가능, 최대 10MB. 데이터 소스: ",[32,53006,36301],{},[18,53008,53009,9148],{},[21,53010,53011],{},"서류 3종",[101,53013,53014,53029],{},[104,53015,53016],{},[107,53017,53018,53020,53022,53025],{},[110,53019,52747],{},[110,53021,9801],{},[110,53023,53024],{},"활성화 조건",[110,53026,48046,53027,8218],{},[32,53028,14992],{},[126,53030,53031,53048,53067],{},[107,53032,53033,53038,53042,53044],{},[131,53034,53035],{},[21,53036,53037],{},"사업자등록증",[131,53039,53040],{},[32,53041,7171],{},[131,53043,33058],{},[131,53045,53046],{},[32,53047,47575],{},[107,53049,53050,53055,53060,53063],{},[131,53051,53052],{},[21,53053,53054],{},"대부업등록증",[131,53056,53057],{},[32,53058,53059],{},"해당업체",[131,53061,53062],{},"체크박스 \"대부업 해당\" 활성 시 (첨부 있으면 자동 활성)",[131,53064,53065],{},[32,53066,47578],{},[107,53068,53069,53074,53078,53081],{},[131,53070,53071],{},[21,53072,53073],{},"지급이행보증보험증권",[131,53075,53076],{},[32,53077,53059],{},[131,53079,53080],{},"체크박스 \"후불 정산 해당\" 활성 시 (첨부 있으면 자동 활성)",[131,53082,53083],{},[32,53084,47581],{},[18,53086,53087,5069,53089,53091,53092,53097],{},[32,53088,35778],{},[32,53090,17261],{}," 컬럼이 없어 ",[21,53093,53094,53096],{},[32,53095,14992],{}," 접두사로 종류 구분"," (§11 결정).",[252,53099,53101],{"id":53100},"파일-첨부-흐름","파일 첨부 흐름",[5251,53103,53106],{"className":53104,"code":53105,"language":5256},[5254],"[사업자등록증 업로드] 클릭\n  → AppUploadGuideDialog (\"PDF · 10MB · …\")\n  → 확인 → input[type=file] 트리거\n  → pickFile(): MIME=application\u002Fpdf, size ≤ 10MB 검증\n  → activeContractId가 없으면 토스트 \"활성 계약을 찾을 수 없습니다\"\n  → FormData 멀티파트 POST \u002Fcontracts\u002Ffiles\n       form: contractId \u002F kind ∈ {biz, loan, insurance} \u002F file\n  → loadFiles() 재호출 → 화면 갱신\n  → kind=biz면 auth.fetchMe() 호출 → 글로벌 띠·페이지 배너가 즉시 \"심사 중\"으로 전환 (§12)\n  → 토스트 \"사업자등록증이 제출되었습니다. 심사가 진행됩니다.\"\n",[32,53107,53105],{"__ignoreMap":4208},[252,53109,53111],{"id":53110},"파일-행-표시-14","파일 행 표시 (§14)",[5251,53113,53116],{"className":53114,"code":53115,"language":5256},[5254],"[아이콘] [이름·메타]      [심사 상태 배지]   [확인]   [(반려 시) 삭제]\n",[32,53117,53115],{"__ignoreMap":4208},[18,53119,53120,53123,53124,53126],{},[21,53121,53122],{},"심사 상태 배지"," (사업자등록증만, ",[32,53125,27043],{},"은 파일 없음이라 미표시):",[81,53128,53129,53133,53137],{},[84,53130,53131,38393],{},[32,53132,36757],{},[84,53134,53135,38398],{},[32,53136,33595],{},[84,53138,53139,38403],{},[32,53140,33601],{},[18,53142,53143,12916,53146,53148,53149,53151,53152,53154,53155,38371],{},[21,53144,53145],{},"삭제 버튼",[32,53147,33601],{}," 상태에서만 노출 → ",[32,53150,36341],{},".\n삭제 후에도 회사는 ",[32,53153,33601],{}," 유지(운영자 결정 보존). 새 파일 첨부 시 백엔드(§12)가 자동 ",[32,53156,36757],{},[252,53158,53160,53161,4343],{"id":53159},"미리보기-appfilepreviewdialog","미리보기 (",[32,53162,19414],{},[18,53164,53165,53166,33723,53169,53172,53173,4703],{},"iframe은 Authorization 헤더를 못 싣기 때문에 ",[32,53167,53168],{},"useApi\u003CBlob>('\u002Fcontracts\u002Ffiles\u002F:id\u002Fdownload', { responseType: 'blob' })",[32,53170,53171],{},"URL.createObjectURL()"," → iframe src. 모달 닫힐 때 ",[32,53174,36555],{},[73,53176],{},[76,53178,53180],{"id":53179},"_4-사용자-액션-매트릭스","4. 사용자 액션 매트릭스",[101,53182,53183,53194],{},[104,53184,53185],{},[107,53186,53187,53190,53192],{},[110,53188,53189],{},"액션",[110,53191,27964],{},[110,53193,22362],{},[126,53195,53196,53210,53226,53241,53254,53269,53284,53298],{},[107,53197,53198,53200,53205],{},[131,53199,52851],{},[131,53201,53202],{},[32,53203,53204],{},"viewContract(c)",[131,53206,53207,53209],{},[32,53208,19392],{}," 모달 — 요약본",[107,53211,53212,53214,53219],{},[131,53213,52860],{},[131,53215,53216],{},[32,53217,53218],{},"signContract(c)",[131,53220,53221,53223,53224],{},[32,53222,19398],{}," (본인인증 + 3-스텝) → 완료 시 ",[32,53225,36278],{},[107,53227,53228,53231,53236],{},[131,53229,53230],{},"서류 업로드 클릭",[131,53232,53233],{},[32,53234,53235],{},"requestUpload(target)",[131,53237,53238,53240],{},[32,53239,19408],{}," → 파일 선택",[107,53242,53243,53246,53251],{},[131,53244,53245],{},"파일 선택",[131,53247,53248],{},[32,53249,53250],{},"pickFile(target, e)",[131,53252,53253],{},"MIME·크기 검증 → FormData POST → 목록 갱신 + (biz일 때) fetchMe",[107,53255,53256,53259,53264],{},[131,53257,53258],{},"서류 확인",[131,53260,53261],{},[32,53262,53263],{},"openPreview(label, f)",[131,53265,53266,53267],{},"인증 fetch → blob → object URL → ",[32,53268,19414],{},[107,53270,53271,53274,53279],{},[131,53272,53273],{},"서류 삭제",[131,53275,53276],{},[32,53277,53278],{},"removeFile(f.id)",[131,53280,53281,53283],{},[32,53282,36341],{}," (rejected 상태에서만 버튼 노출)",[107,53285,53286,53289,53295],{},[131,53287,53288],{},"대부업\u002F후불 해당 토글",[131,53290,53291,4536,53293],{},[32,53292,36496],{},[32,53294,36499],{},[131,53296,53297],{},"업로드 인터페이스 활성\u002F비활성",[107,53299,53300,53302,53307],{},[131,53301,18582],{},[131,53303,53304],{},[32,53305,53306],{},"save()",[131,53308,53309,53310,53312],{},"현재는 토스트만 — 회사 전화번호 등은 별도 ",[32,53311,3991],{},"에서",[73,53314],{},[76,53316,53318],{"id":53317},"_5-회원-유형별-서류-요구사항","5. 회원 유형별 서류 요구사항",[18,53320,53321,53325],{},[26,53322,53324],{"href":53323},".\u002FSIGNUP#2-%ED%95%B5%EC%8B%AC-%EC%B0%A8%EC%9D%B4-%ED%95%9C%EB%88%88%EC%97%90","SIGNUP.md §2"," 정책 표 기반:",[101,53327,53328,53341],{},[104,53329,53330],{},[107,53331,53332,53335,53338],{},[110,53333,53334],{},"가입 유형",[110,53336,53337],{},"카드 충전 시",[110,53339,53340],{},"후불 정산 시",[126,53342,53343,53358,53371],{},[107,53344,53345,53352,53355],{},[131,53346,53347,6685,53350,4343],{},[21,53348,53349],{},"법인사업자",[32,53351,33543],{},[131,53353,53354],{},"사업자등록증 + (대부업등록증)",[131,53356,53357],{},"위 + 지급이행보증보험증권 + 통장사본",[107,53359,53360,53367,53369],{},[131,53361,53362,6685,53365,4343],{},[21,53363,53364],{},"개인사업자",[32,53366,33560],{},[131,53368,53354],{},[131,53370,53357],{},[107,53372,53373,53380,53383],{},[131,53374,53375,6685,53378,4343],{},[21,53376,53377],{},"개인",[32,53379,33576],{},[131,53381,53382],{},"(가입신청서만 — 본 화면 미진입)",[131,53384,53385],{},"❌ 후불 미지원",[15,53387,53388],{},[18,53389,49281,53390,53393],{},[21,53391,53392],{},"회원 유형에 따른 분기는 후속"," — 현재는 모든 사업자에게 동일 폼 노출. 대부업·후불 옵션 자동 분기는 P1.",[73,53395],{},[76,53397,53399],{"id":53398},"_6-상태-모델","6. 상태 모델",[237,53401,53403,53404,4343],{"id":53402},"_61-이용계약-tb_contractcontract_state","6.1 이용계약 (",[32,53405,52736],{},[5251,53407,53410],{"className":53408,"code":53409,"language":5256},[5254],"            ┌──────────────┐\n            │  initial     │  ← signup auto-create (§11) 또는 lazy backfill (§13)\n            └──────┬───────┘\n          POST \u002Fcontracts\u002F:id\u002Fsign\n                   ▼\n            ┌──────────────┐    운영자가 신규 약관 배포(미구현)\n            │  done        │ ─────────────────────────┐\n            └──────────────┘                          ▼\n                                              ┌──────────────┐\n                                              │  renew       │\n                                              └──────┬───────┘\n                                       POST \u002Fcontracts\u002F:id\u002Fsign\n                                                     ▼\n                                              ┌──────────────┐\n        같은 회사의 다른 done ──→ 자동       │  done        │\n        expired (sign 핸들러에서 일괄)        └──────────────┘\n                                                     │\n                                              만료 cron(미구현)\n                                                     ▼\n                                              ┌──────────────┐\n                                              │  expired     │\n                                              └──────────────┘\n",[32,53411,53409],{"__ignoreMap":4208},[237,53413,53415,53416,46287],{"id":53414},"_62-회사-승인-상태-tb_companyapproval_state-4단계-712","6.2 회사 승인 상태 (",[32,53417,33498],{},[5251,53419,53422],{"className":53420,"code":53421,"language":5256},[5254],"  ┌──────────┐  biz 첨부(§12)   ┌────────────┐  운영자 승인     ┌──────────┐\n  │ pending  │ ───────────────► │ reviewing  │ ───────────────► │ approved │\n  └──────────┘                  └────────────┘                  └──────────┘\n                                       │                              ▲\n                                       │ 운영자 반려                  │\n                                       ▼                              │\n                                  ┌──────────┐  biz 재첨부(§12)        │\n                                  │ rejected │ ──────────────────────►│ reviewing\n                                  └──────────┘                        (자동)\n",[32,53423,53421],{"__ignoreMap":4208},[18,53425,53426],{},"코드:",[81,53428,53429,53436,53443],{},[84,53430,53431,53432,53435],{},"첨부 시 자동 전이: ",[26,53433,53434],{"href":36247},"contracts.ts POST \u002Ffiles"," §12",[84,53437,53438,53439,53442],{},"자동 회복: ",[26,53440,53441],{"href":36247},"GET \u002Ffiles lazy backfill"," §13",[84,53444,53445,53446,51,53448,53450],{},"미들웨어 차단: ",[26,53447,37491],{"href":34129},[32,53449,36936],{},"이면 403",[73,53452],{},[76,53454,53456],{"id":53455},"_7-정책-결정-사항","7. 정책 결정 사항",[237,53458,53460],{"id":53459},"_71-미승인-사용자의-메인-진입점","7.1 미승인 사용자의 메인 진입점",[81,53462,53463,53470],{},[84,53464,53465,53466,53469],{},"회원가입한 사업자는 ",[21,53467,53468],{},"반드시"," 본 화면에서 사업자등록증을 등록해야 운영자 심사 → 승인 → 서비스 이용.",[84,53471,52611,53472,4361,53474,4361,53476,53478],{},[32,53473,27043],{},[32,53475,36757],{},[32,53477,33601],{},")에서는 GNB·홈·발송·주소록 등 어떤 페이지에 가도 미들웨어가 본 화면으로 리다이렉트.",[237,53480,53482],{"id":53481},"_72-카드-충전-vs-후불-정산","7.2 카드 충전 vs 후불 정산",[81,53484,53485,53490,53499],{},[84,53486,53487,53489],{},[21,53488,47688],{},": 사업자등록증 + (대부업등록증) — 등록 후 운영자 승인 → 즉시 카드 등록·충전·발송 가능.",[84,53491,53492,53495,53496,53498],{},[21,53493,53494],{},"후불 정산",": 위 + ",[21,53497,53073],{}," + 통장사본 — 운영자가 추가 검수 → 신용 한도 부여.",[84,53500,53501],{},"현재 화면은 두 경로의 서류를 모두 표시하고 사용자가 \"후불 정산 해당\" 체크로 선택.",[237,53503,53505],{"id":53504},"_73-상태별-안내-메시지-71214","7.3 상태별 안내 메시지 (§7·§12·§14)",[101,53507,53508,53523],{},[104,53509,53510],{},[107,53511,53512,53514,53517,53520],{},[110,53513,31998],{},[110,53515,53516],{},"메시지 (글로벌 띠)",[110,53518,53519],{},"메시지 (이 화면 상단 카드)",[110,53521,53522],{},"메시지 (파일 행 배지)",[126,53524,53525,53539,53553,53567],{},[107,53526,53527,53531,53534,53536],{},[131,53528,53529],{},[32,53530,27043],{},[131,53532,53533],{},"사업자등록증을 등록해 주세요",[131,53535,53533],{},[131,53537,53538],{},"(파일 없음 — 미표시)",[107,53540,53541,53545,53548,53550],{},[131,53542,53543],{},[32,53544,36757],{},[131,53546,53547],{},"사업자등록증 심사 중입니다",[131,53549,53547],{},[131,53551,53552],{},"\"심사 중\" (info)",[107,53554,53555,53559,53562,53564],{},[131,53556,53557],{},[32,53558,33595],{},[131,53560,53561],{},"(배너 미노출)",[131,53563,53561],{},[131,53565,53566],{},"\"승인\" (success)",[107,53568,53569,53573,53576,53579],{},[131,53570,53571],{},[32,53572,33601],{},[131,53574,53575],{},"사업자등록증 심사 반려 + 사유",[131,53577,53578],{},"사업자등록증 심사가 반려되었습니다",[131,53580,53581],{},"\"반려\" (danger) + 삭제 버튼",[237,53583,53585],{"id":53584},"_74-이용계약-자동-생성-11","7.4 이용계약 자동 생성 (§11)",[18,53587,53588,53589,53591,53592,35565,53595,53597,53598,4473,53600,4473,53602,53605],{},"가입 시점에 백엔드 ",[32,53590,22968],{}," 핸들러가 NICE 세션 consume 직후 ",[32,53593,53594],{},"companyType ∈ {corp, sole}",[32,53596,35831],{}," 1건 자동 INSERT(",[32,53599,35853],{},[32,53601,35856],{},[32,53603,53604],{},"contract_state='initial'","). signup 이전에 가입한 사업자는 §13의 lazy backfill로 GET 시점에 자동 생성.",[237,53607,53609],{"id":53608},"_75-갱신-계약-체결-시-기존-계약-자동-만료-11","7.5 갱신 계약 체결 시 기존 계약 자동 만료 (§11)",[18,53611,53612,53614,53615,52984,53618,53620,53621,53623],{},[32,53613,36278],{}," 핸들러가 ",[32,53616,53617],{},"contract_state='renew'",[32,53619,27035],{}," 계약을 한 번에 ",[32,53622,19371],{},"로 일괄 UPDATE — 이중 유효 계약 방지.",[237,53625,53627],{"id":53626},"_76-사업자등록증-첨부-시-회사-상태-자동-전이-12","7.6 사업자등록증 첨부 시 회사 상태 자동 전이 (§12)",[18,53629,53630,53632,53633,36775,53635,26162,53637,35565,53639,36782,53641,53643,53644,4361,53646,53648],{},[32,53631,36314],{}," 핸들러에서 ",[32,53634,36774],{},[32,53636,27043],{},[32,53638,33601],{},[32,53640,36757],{},[32,53642,36785],{},"은 그대로 둠(운영자가 새 심사에서 결정). ",[32,53645,36757],{},[32,53647,33595],{},"는 변동 없음.",[237,53650,53652],{"id":53651},"_77-파일-제약","7.7 파일 제약",[81,53654,53655,53663,53669,53678],{},[84,53656,53657,47,53660,53662],{},[21,53658,53659],{},"MIME",[32,53661,36330],{}," 만 허용 (백엔드에서 재검증)",[84,53664,53665,53668],{},[21,53666,53667],{},"최대 크기",": 10MB (Cloudflare Workers 요청 크기 제한 내)",[84,53670,53671,53674,53675,53677],{},[21,53672,53673],{},"권한",": 본 회사의 계약·파일만 접근. ",[32,53676,22750],{}," 매칭 안 되면 404.",[84,53679,53680,47,53683,53685],{},[21,53681,53682],{},"R2 키 패턴",[32,53684,36363],{}," — 회사·계약별 prefix 분리.",[237,53687,53689],{"id":53688},"_78-미승인-사용자의-본-화면-예외-11","7.8 미승인 사용자의 본 화면 예외 (§11)",[18,53691,53692,53694,53695,45107,53697,53700],{},[32,53693,35862],{}," 라우트는 ",[32,53696,34718],{},[21,53698,53699],{},"적용하지 않음"," — 미승인 사용자가 사업자등록증을 제출하는 화면이 본 화면이므로 의도된 예외. 다른 도메인 라우트는 §8에서 모두 차단.",[73,53702],{},[76,53704,53706],{"id":53705},"_8-api-엔드포인트-구현됨-11","8. API 엔드포인트 — 구현됨 (§11)",[237,53708,53710],{"id":53709},"_81-계약","8.1 계약",[101,53712,53713,53725],{},[104,53714,53715],{},[107,53716,53717,53719,53721,53723],{},[110,53718,22559],{},[110,53720,35660],{},[110,53722,46107],{},[110,53724,7718],{},[126,53726,53727,53746],{},[107,53728,53729,53733,53737,53743],{},[131,53730,53731],{},[32,53732,18678],{},[131,53734,53735],{},[32,53736,35862],{},[131,53738,53739,53740,53742],{},"본 회사 계약 목록 (",[32,53741,25687],{}," + id 오름차순)",[131,53744,53745],{},"§13 lazy auto-create 포함",[107,53747,53748,53752,53757,53776],{},[131,53749,53750],{},[32,53751,49064],{},[131,53753,53754],{},[32,53755,53756],{},"\u002Fcontracts\u002F:id\u002Fsign",[131,53758,53759,53760,6078,53762,6078,53764,6078,53766,5439,53768,53770,53771,53773,53774],{},"전자서명 완료 → ",[32,53761,27035],{},[32,53763,36074],{},[32,53765,36079],{},[32,53767,36293],{},[32,53769,36473],{},"였다면 기존 ",[32,53772,27035],{}," 일괄 ",[32,53775,19371],{},[131,53777,43729],{},[237,53779,53781],{"id":53780},"_82-가입-서류-r2-db","8.2 가입 서류 (R2 + DB)",[101,53783,53784,53796],{},[104,53785,53786],{},[107,53787,53788,53790,53792,53794],{},[110,53789,22559],{},[110,53791,35660],{},[110,53793,46107],{},[110,53795,7718],{},[126,53797,53798,53814,53830,53848],{},[107,53799,53800,53804,53808,53811],{},[131,53801,53802],{},[32,53803,18678],{},[131,53805,53806],{},[32,53807,36383],{},[131,53809,53810],{},"본 회사 파일 목록 (contract JOIN으로 회사 단위 좁힘)",[131,53812,53813],{},"§13 자동 회복 포함",[107,53815,53816,53820,53824,53827],{},[131,53817,53818],{},[32,53819,49064],{},[131,53821,53822],{},[32,53823,36383],{},[131,53825,53826],{},"멀티파트 업로드 → R2 put + DB insert",[131,53828,53829],{},"§11 — PDF·10MB·접두사 + §12 auto reviewing",[107,53831,53832,53836,53841,53845],{},[131,53833,53834],{},[32,53835,18678],{},[131,53837,53838],{},[32,53839,53840],{},"\u002Fcontracts\u002Ffiles\u002F:id\u002Fdownload",[131,53842,36327,53843,23902],{},[32,53844,36330],{},[131,53846,53847],{},"inline disposition",[107,53849,53850,53854,53859,53862],{},[131,53851,53852],{},[32,53853,20406],{},[131,53855,53856],{},[32,53857,53858],{},"\u002Fcontracts\u002Ffiles\u002F:id",[131,53860,53861],{},"R2 delete(swallow) + DB delete",[131,53863,53864],{},"§14 — 반려 상태에서만 호출",[18,53866,32884,53867,7505,53869,53871,53872],{},[32,53868,36397],{},[32,53870,36400],{}," 스키마 + 위 5 path. ",[26,53873,25376],{"href":29675},[237,53875,53877],{"id":53876},"_83-휴대폰-본인인증-15","8.3 휴대폰 본인인증 (§15)",[18,53879,53880,53881,53883],{},"기존 phone-code 인프라 재사용 + ",[32,53882,38570],{}," purpose 추가:",[101,53885,53886,53896],{},[104,53887,53888],{},[107,53889,53890,53892,53894],{},[110,53891,22559],{},[110,53893,35660],{},[110,53895,46107],{},[126,53897,53898,53917],{},[107,53899,53900,53904,53908],{},[131,53901,53902],{},[32,53903,49064],{},[131,53905,53906],{},[32,53907,29991],{},[131,53909,53910,53913,53914,53916],{},[32,53911,53912],{},"{phone, purpose: 'contract_sign'}"," → SMS 발송 (mock 모드면 ",[32,53915,29476],{}," 응답에 노출)",[107,53918,53919,53923,53928],{},[131,53920,53921],{},[32,53922,49064],{},[131,53924,53925],{},[32,53926,53927],{},"\u002Fauth\u002Fphone-code\u002Fverify",[131,53929,53930,53933,53934],{},[32,53931,53932],{},"{phone, purpose: 'contract_sign', code}"," → 200 ",[32,53935,38766],{},[18,53937,53938,53939,53942],{},"TTL 10분, 5회 시도 제한, 재발송 시 직전 코드 무효화, 소비 후 재사용 차단. SHA-256(",[32,53940,53941],{},"phone|purpose|code",") 해시 저장.",[237,53944,53946],{"id":53945},"_84-운영자-검수-운영자단-미구현","8.4 운영자 검수 (운영자단 — 미구현)",[101,53948,53949,53957],{},[104,53950,53951],{},[107,53952,53953,53955],{},[110,53954,35660],{},[110,53956,46107],{},[126,53958,53959,53969,53980],{},[107,53960,53961,53966],{},[131,53962,53963],{},[32,53964,53965],{},"GET \u002Fadmin\u002Fcompanies\u002F{id}\u002Fcontracts",[131,53967,53968],{},"운영자가 회사별 계약·서류 조회",[107,53970,53971,53975],{},[131,53972,53973],{},[32,53974,47303],{},[131,53976,53977,53978],{},"승인 → ",[32,53979,33582],{},[107,53981,53982,53987],{},[131,53983,53984],{},[32,53985,53986],{},"POST \u002Fadmin\u002Fcompanies\u002F{id}\u002Freject {reason}",[131,53988,53989,53990,4339,53993,23555],{},"반려 → ",[32,53991,53992],{},"approval_state='rejected'",[32,53994,36785],{},[18,53996,53997],{},"현재는 라이브 DB UPDATE만 가능.",[73,53999],{},[76,54001,54003],{"id":54002},"_9-db-테이블-라이브-schemats-정의-11","9. DB 테이블 (라이브 + schema.ts 정의 §11)",[237,54005,54007,54008],{"id":54006},"_91-tb_contract","9.1 ",[32,54009,35831],{},[5251,54011,54013],{"className":31016,"code":54012,"language":31018,"meta":4208,"style":4208},"TB_CONTRACT (\n  id              BIGINT UNSIGNED PK AUTO_INCREMENT,\n  company_id      BIGINT UNSIGNED NOT NULL,    -- FK → TB_COMPANY\n  title           VARCHAR(160)   NOT NULL,\n  version         VARCHAR(20)    NOT NULL,     -- '신규' \u002F 'v2.0' 등\n  contract_state  VARCHAR(20)    NOT NULL DEFAULT 'initial',  -- initial\u002Fdone\u002Frenew\u002Fexpired\n  status          INT            NOT NULL DEFAULT 1,\n  signer_user_id  BIGINT UNSIGNED,             -- FK → TB_USER (서명자)\n  signed_at       DATETIME,\n  expires_at      DATETIME,                    -- 보통 signed_at + 2y\n  created_at      DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  updated_at      DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  INDEX idx_contract_company (company_id, contract_state),\n  CONSTRAINT fk_contract_company FOREIGN KEY (company_id) REFERENCES TB_COMPANY(id),\n  CONSTRAINT fk_contract_signer  FOREIGN KEY (signer_user_id) REFERENCES TB_USER(id)\n)\n",[32,54014,54015,54020,54025,54030,54035,54040,54045,54050,54055,54060,54065,54070,54075,54080,54085,54090],{"__ignoreMap":4208},[7968,54016,54017],{"class":5110,"line":7970},[7968,54018,54019],{},"TB_CONTRACT (\n",[7968,54021,54022],{"class":5110,"line":4212},[7968,54023,54024],{},"  id              BIGINT UNSIGNED PK AUTO_INCREMENT,\n",[7968,54026,54027],{"class":5110,"line":4209},[7968,54028,54029],{},"  company_id      BIGINT UNSIGNED NOT NULL,    -- FK → TB_COMPANY\n",[7968,54031,54032],{"class":5110,"line":4219},[7968,54033,54034],{},"  title           VARCHAR(160)   NOT NULL,\n",[7968,54036,54037],{"class":5110,"line":8291},[7968,54038,54039],{},"  version         VARCHAR(20)    NOT NULL,     -- '신규' \u002F 'v2.0' 등\n",[7968,54041,54042],{"class":5110,"line":8301},[7968,54043,54044],{},"  contract_state  VARCHAR(20)    NOT NULL DEFAULT 'initial',  -- initial\u002Fdone\u002Frenew\u002Fexpired\n",[7968,54046,54047],{"class":5110,"line":8310},[7968,54048,54049],{},"  status          INT            NOT NULL DEFAULT 1,\n",[7968,54051,54052],{"class":5110,"line":8321},[7968,54053,54054],{},"  signer_user_id  BIGINT UNSIGNED,             -- FK → TB_USER (서명자)\n",[7968,54056,54057],{"class":5110,"line":8332},[7968,54058,54059],{},"  signed_at       DATETIME,\n",[7968,54061,54062],{"class":5110,"line":8343},[7968,54063,54064],{},"  expires_at      DATETIME,                    -- 보통 signed_at + 2y\n",[7968,54066,54067],{"class":5110,"line":8349},[7968,54068,54069],{},"  created_at      DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,\n",[7968,54071,54072],{"class":5110,"line":8454},[7968,54073,54074],{},"  updated_at      DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,\n",[7968,54076,54077],{"class":5110,"line":8472},[7968,54078,54079],{},"  INDEX idx_contract_company (company_id, contract_state),\n",[7968,54081,54082],{"class":5110,"line":8494},[7968,54083,54084],{},"  CONSTRAINT fk_contract_company FOREIGN KEY (company_id) REFERENCES TB_COMPANY(id),\n",[7968,54086,54087],{"class":5110,"line":8515},[7968,54088,54089],{},"  CONSTRAINT fk_contract_signer  FOREIGN KEY (signer_user_id) REFERENCES TB_USER(id)\n",[7968,54091,54092],{"class":5110,"line":8525},[7968,54093,9563],{},[18,54095,54096,54097,54099],{},"schema.ts 정의: ",[26,54098,21654],{"href":29606}," §11.",[237,54101,54103,54104],{"id":54102},"_92-tb_contract_file","9.2 ",[32,54105,35778],{},[5251,54107,54109],{"className":31016,"code":54108,"language":31018,"meta":4208,"style":4208},"TB_CONTRACT_FILE (\n  id            BIGINT UNSIGNED PK AUTO_INCREMENT,\n  contract_id   BIGINT UNSIGNED NOT NULL,      -- FK → TB_CONTRACT\n  name          VARCHAR(255)    NOT NULL,      -- 한국어 접두사 포함 ('사업자등록증_...')\n  size_bytes    BIGINT UNSIGNED NOT NULL,\n  r2_key        VARCHAR(255)    NOT NULL,      -- contracts\u002F\u003Cco>\u002F\u003Ccontract>\u002F\u003Cts>_\u003Cname>\n  uploaded_at   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  INDEX idx_contractfile_contract (contract_id),\n  CONSTRAINT fk_contractfile_contract FOREIGN KEY (contract_id) REFERENCES TB_CONTRACT(id)\n)\n",[32,54110,54111,54116,54121,54126,54131,54136,54141,54146,54151,54156],{"__ignoreMap":4208},[7968,54112,54113],{"class":5110,"line":7970},[7968,54114,54115],{},"TB_CONTRACT_FILE (\n",[7968,54117,54118],{"class":5110,"line":4212},[7968,54119,54120],{},"  id            BIGINT UNSIGNED PK AUTO_INCREMENT,\n",[7968,54122,54123],{"class":5110,"line":4209},[7968,54124,54125],{},"  contract_id   BIGINT UNSIGNED NOT NULL,      -- FK → TB_CONTRACT\n",[7968,54127,54128],{"class":5110,"line":4219},[7968,54129,54130],{},"  name          VARCHAR(255)    NOT NULL,      -- 한국어 접두사 포함 ('사업자등록증_...')\n",[7968,54132,54133],{"class":5110,"line":8291},[7968,54134,54135],{},"  size_bytes    BIGINT UNSIGNED NOT NULL,\n",[7968,54137,54138],{"class":5110,"line":8301},[7968,54139,54140],{},"  r2_key        VARCHAR(255)    NOT NULL,      -- contracts\u002F\u003Cco>\u002F\u003Ccontract>\u002F\u003Cts>_\u003Cname>\n",[7968,54142,54143],{"class":5110,"line":8310},[7968,54144,54145],{},"  uploaded_at   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,\n",[7968,54147,54148],{"class":5110,"line":8321},[7968,54149,54150],{},"  INDEX idx_contractfile_contract (contract_id),\n",[7968,54152,54153],{"class":5110,"line":8332},[7968,54154,54155],{},"  CONSTRAINT fk_contractfile_contract FOREIGN KEY (contract_id) REFERENCES TB_CONTRACT(id)\n",[7968,54157,54158],{"class":5110,"line":8343},[7968,54159,9563],{},[18,54161,54162,54167,54168,54170,54171,4536,54173,4536,54175,54177],{},[21,54163,54164,54166],{},[32,54165,17261],{}," 컬럼 없음"," (§11 결정) — 파일 종류는 ",[32,54169,14992],{}," 접두사로 구분.\nR2 메타데이터에는 ",[32,54172,17261],{},[32,54174,22750],{},[32,54176,35992],{},"를 customMetadata로 함께 저장.",[237,54179,54181,54182],{"id":54180},"_93-미구현-tb_contract_template","9.3 (미구현) ",[32,54183,54184],{},"TB_CONTRACT_TEMPLATE",[18,54186,54187,54188,54191],{},"운영자가 배포하는 약관 정본 + chapter·article JSON. 신규 배포 시 회사별 ",[32,54189,54190],{},"TB_CONTRACT.contract_state='renew'","로 자동 마이그레이션. P2.",[73,54193],{},[76,54195,54197],{"id":54196},"_10-현재-구현-상태","10. 현재 구현 상태",[101,54199,54200,54210],{},[104,54201,54202],{},[107,54203,54204,54206,54208],{},[110,54205,7712],{},[110,54207,270],{},[110,54209,7718],{},[126,54211,54212,54224,54237,54249,54264,54274,54284,54294,54304,54314,54324,54334,54344,54354,54366,54378,54390,54404,54416],{},[107,54213,54214,54217,54219],{},[131,54215,54216],{},"이용계약 카드 리스트",[131,54218,3869],{},[131,54220,54221,54223],{},[32,54222,36268],{}," 실 API (§11)",[107,54225,54226,54231,54234],{},[131,54227,54228,54229,4343],{},"계약서 확인 모달 (",[32,54230,19392],{},[131,54232,54233],{},"🟢 UI",[131,54235,54236],{},"약관 정본 하드코딩 (백엔드 템플릿 없음)",[107,54238,54239,54242,54244],{},[131,54240,54241],{},"전자서명 위저드 (3-스텝)",[131,54243,3869],{},[131,54245,54246,54247,52969],{},"본인인증(§15) + ",[32,54248,36278],{},[107,54250,54251,54253,54255],{},[131,54252,38534],{},[131,54254,3869],{},[131,54256,54257,54260,54261,54263],{},[32,54258,54259],{},"phone-code"," purpose=",[32,54262,38570],{}," (§15)",[107,54265,54266,54269,54271],{},[131,54267,54268],{},"갱신 → 기존 만료 자동 전이",[131,54270,3869],{},[131,54272,54273],{},"백엔드 sign 핸들러에서 일괄 UPDATE (§11)",[107,54275,54276,54279,54281],{},[131,54277,54278],{},"가입서류 첨부 (PDF\u002F10MB 검증)",[131,54280,3869],{},[131,54282,54283],{},"백엔드 R2 + DB 적재 (§11)",[107,54285,54286,54289,54291],{},[131,54287,54288],{},"사업자등록증 첨부 시 reviewing 자동 전이",[131,54290,3869],{},[131,54292,54293],{},"백엔드 `pending",[107,54295,54296,54299,54301],{},[131,54297,54298],{},"§11 이전 가입자 \u002F §12 이전 첨부자 lazy 회복",[131,54300,3869],{},[131,54302,54303],{},"GET 시점 자동 보정 (§13)",[107,54305,54306,54309,54311],{},[131,54307,54308],{},"파일 행 심사 상태 배지",[131,54310,3869],{},[131,54312,54313],{},"reviewing\u002Fapproved\u002Frejected 3분기 (§14)",[107,54315,54316,54319,54321],{},[131,54317,54318],{},"반려 시 삭제 버튼 + DELETE",[131,54320,3869],{},[131,54322,54323],{},"rejected 상태에서만 노출 (§14)",[107,54325,54326,54329,54331],{},[131,54327,54328],{},"대부업·후불 체크박스 토글",[131,54330,54233],{},[131,54332,54333],{},"첨부 있으면 자동 활성, 정책 분기는 없음",[107,54335,54336,54339,54341],{},[131,54337,54338],{},"첨부 서류 미리보기",[131,54340,3869],{},[131,54342,54343],{},"인증 fetch → blob → object URL (§11)",[107,54345,54346,54349,54351],{},[131,54347,54348],{},"패널 상단 상태 카드 (pending\u002Freviewing\u002Frejected)",[131,54350,3869],{},[131,54352,54353],{},"3분기 톤·메시지 (§12)",[107,54355,54356,54358,54360],{},[131,54357,18582],{},[131,54359,54233],{},[131,54361,54362,54363,54365],{},"토스트만 (회사 전화번호 등은 ",[32,54364,3991],{}," 사용)",[107,54367,54368,54373,54375],{},[131,54369,54370],{},[21,54371,54372],{},"회원 유형별 서류 분기",[131,54374,3931],{},[131,54376,54377],{},"현재 모든 사업자에게 동일 폼",[107,54379,54380,54385,54387],{},[131,54381,54382],{},[21,54383,54384],{},"운영자 승인 트리거",[131,54386,3931],{},[131,54388,54389],{},"운영자단 미개발 — DB 직접 UPDATE만 가능",[107,54391,54392,54397,54399],{},[131,54393,54394],{},[21,54395,54396],{},"약관 템플릿 정본 관리",[131,54398,3931],{},[131,54400,54401,54403],{},[32,54402,19392],{}," 약관 본문 하드코딩",[107,54405,54406,54411,54413],{},[131,54407,54408],{},[21,54409,54410],{},"체결 서명 PDF 보존",[131,54412,3931],{},[131,54414,54415],{},"캔버스 ink 이미지가 백엔드에 저장 안 됨",[107,54417,54418,54423,54425],{},[131,54419,54420],{},[21,54421,54422],{},"계약 갱신 cron",[131,54424,3931],{},[131,54426,54427,54428,54430],{},"만료 1개월 전 ",[32,54429,36473],{}," 자동 생성 미구현",[73,54432],{},[76,54434,50877],{"id":50876},[237,54436,54438],{"id":54437},"p0-가입-후-폐쇄-루프-완성","P0 — 가입 후 폐쇄 루프 완성",[6674,54440,54441,54446],{},[84,54442,54443,54445],{},[21,54444,48485],{}," (§7.7 \u002F §11.10 \u002F §12.6) — 현재 라이브 DB UPDATE로만 승인\u002F반려. 운영자가 본 페이지의 업로드 파일을 보고 승인\u002F반려(사유 입력) 처리 가능해야 함. WBS 5-4-3.",[84,54447,54448,54450],{},[21,54449,3655],{}," (§16) — 승인\u002F반려 결정 시 사용자에게 알림 메일·SMS 자동 발송. User Access Key 수령 대기.",[237,54452,54454],{"id":54453},"p1-ux-정합화","P1 — UX 정합화",[6674,54456,54457,54466,54477],{"start":4209},[84,54458,54459,89,54462,54465],{},[21,54460,54461],{},"회원 유형별 폼 분기",[32,54463,54464],{},"auth.tenant.companyType","에 따라 대부업·후불 옵션 노출 여부 결정.",[84,54467,54468,89,54471,54473,54474,54476],{},[21,54469,54470],{},"개인 가입자의 메뉴 숨김",[32,54472,19458],{}," 항목 자체를 LNB·",[32,54475,5836],{},"에서 미노출.",[84,54478,54479,54484],{},[21,54480,54481,54483],{},[32,54482,36757],{}," 상태에서 잘못 올린 biz 파일 정정"," (§14.4) — 현재는 삭제 버튼이 안 보임. 정책상 \"심사 중 변경 불가\"가 안전하나 사용자가 답답할 수 있음. 운영자단 심사 화면 도입 시 같은 곳에서 정정 가능하도록.",[237,54486,54488],{"id":54487},"p2-약관계약-정본-관리","P2 — 약관·계약 정본 관리",[6674,54490,54491,54501,54510],{"start":8301},[84,54492,54493,54497,54498,54500],{},[21,54494,54495],{},[32,54496,54184],{}," — 운영자가 약관 본문 정본 등록·버전 관리·일괄 ",[32,54499,36473],{}," 배포.",[84,54502,54503,54506,54507,54509],{},[21,54504,54505],{},"전자서명 인증 강화 옵션"," — 현재 휴대폰 SMS OTP. 법적 강도를 더 높이려면 NICE 본인확인(",[32,54508,45645],{},") 재호출 또는 공인인증서 연계.",[84,54511,54512,54515,54516,54518],{},[21,54513,54514],{},"계약서 PDF 생성·보존"," — 체결 완료 시 캔버스 ink(PNG) + 서명자·시간·IP·UA 메타가 들어간 PDF 자동 생성 → R2 저장(",[32,54517,38851],{}," 컬럼 추가) → 사용자가 다운로드.",[237,54520,54522],{"id":54521},"p3-위생적-작업","P3 — 위생적 작업",[6674,54524,54525,54533,54545,54553,54562],{"start":8332},[84,54526,54527,54529,54530,54532],{},[21,54528,54422],{}," (§11.10) — 만료 1개월 전 자동 ",[32,54531,36473],{}," 계약 row 생성. Workers Cron Trigger.",[84,54534,54535,54541,54542,54544],{},[21,54536,54537,54538,54540],{},"반려 후 재첨부 시 ",[32,54539,36785],{}," 처리"," (§12.6) — 현재는 그대로 둔 채 ",[32,54543,36757],{}," 전이. 정책상 더 명확히 하려면 재첨부 시 NULL 정리.",[84,54546,54547,89,54550,54552],{},[21,54548,54549],{},"갱신 알림",[32,54551,36473],{}," 상태가 되면 사용자에게 이메일·SMS·인앱 알림.",[84,54554,54555,89,54558,54561],{},[21,54556,54557],{},"체결마감일 임박 경고",[32,54559,54560],{},"renew.metas[].danger=true"," 외에 GNB 띠로 일주일 전부터 강조.",[84,54563,54564,54566],{},[21,54565,36727],{}," (§11.10) — 운영자 부담 경감. NHN OCR API 또는 외부 서비스.",[8560,54568,12401],{},{"title":4208,"searchDepth":4209,"depth":4209,"links":54570},[54571,54572,54578,54591,54592,54593,54599,54609,54615,54623,54624],{"id":52387,"depth":4212,"text":52388},{"id":52495,"depth":4212,"text":52496,"children":54573},[54574,54575,54576,54577],{"id":52502,"depth":4209,"text":52503},{"id":52521,"depth":4209,"text":52522},{"id":52539,"depth":4209,"text":52540},{"id":52567,"depth":4209,"text":52568},{"id":52603,"depth":4212,"text":52604,"children":54579},[54580,54581,54585],{"id":52607,"depth":4209,"text":52608},{"id":52721,"depth":4209,"text":52722,"children":54582},[54583],{"id":52870,"depth":4219,"text":54584},"전자서명 위저드 (AppContractSignDialog)",{"id":53000,"depth":4209,"text":53001,"children":54586},[54587,54588,54589],{"id":53100,"depth":4219,"text":53101},{"id":53110,"depth":4219,"text":53111},{"id":53159,"depth":4219,"text":54590},"미리보기 (AppFilePreviewDialog)",{"id":53179,"depth":4212,"text":53180},{"id":53317,"depth":4212,"text":53318},{"id":53398,"depth":4212,"text":53399,"children":54594},[54595,54597],{"id":53402,"depth":4209,"text":54596},"6.1 이용계약 (TB_CONTRACT.contract_state)",{"id":53414,"depth":4209,"text":54598},"6.2 회사 승인 상태 (TB_COMPANY.approval_state) — 4단계 (§7·§12)",{"id":53455,"depth":4212,"text":53456,"children":54600},[54601,54602,54603,54604,54605,54606,54607,54608],{"id":53459,"depth":4209,"text":53460},{"id":53481,"depth":4209,"text":53482},{"id":53504,"depth":4209,"text":53505},{"id":53584,"depth":4209,"text":53585},{"id":53608,"depth":4209,"text":53609},{"id":53626,"depth":4209,"text":53627},{"id":53651,"depth":4209,"text":53652},{"id":53688,"depth":4209,"text":53689},{"id":53705,"depth":4212,"text":53706,"children":54610},[54611,54612,54613,54614],{"id":53709,"depth":4209,"text":53710},{"id":53780,"depth":4209,"text":53781},{"id":53876,"depth":4209,"text":53877},{"id":53945,"depth":4209,"text":53946},{"id":54002,"depth":4212,"text":54003,"children":54616},[54617,54619,54621],{"id":54006,"depth":4209,"text":54618},"9.1 TB_CONTRACT",{"id":54102,"depth":4209,"text":54620},"9.2 TB_CONTRACT_FILE",{"id":54180,"depth":4209,"text":54622},"9.3 (미구현) TB_CONTRACT_TEMPLATE",{"id":54196,"depth":4212,"text":54197},{"id":50876,"depth":4212,"text":50877,"children":54625},[54626,54627,54628,54629],{"id":54437,"depth":4209,"text":54438},{"id":54453,"depth":4209,"text":54454},{"id":54487,"depth":4209,"text":54488},{"id":54521,"depth":4209,"text":54522},{},"\u002Fpages\u002Fcontract",{"title":52336,"description":4208},"pages\u002FCONTRACT","nNaei9_64ZqRCqK9CGsM0HlMo7H92B5HJnLdrSpVrc0",{"id":54636,"title":54637,"body":54638,"description":4208,"extension":4257,"meta":55779,"navigation":4259,"path":55780,"seo":55781,"stem":55782,"__hash__":55783},"docs\u002Fpages\u002FSIGNUP.md","회원가입 절차 — 유형별 정리",{"type":8,"value":54639,"toc":55753},[54640,54643,54695,54697,54701,54707,54714,54716,54720,54872,54874,54881,54885,54908,54912,54935,54939,54961,54965,54975,54979,55038,55050,55054,55099,55101,55107,55117,55120,55132,55143,55145,55151,55154,55172,55176,55203,55207,55210,55264,55271,55275,55294,55296,55300,55368,55374,55376,55380,55538,55554,55556,55560,55740,55751],[11,54641,54637],{"id":54642},"회원가입-절차-유형별-정리",[15,54644,54645,54650,54690],{},[18,54646,54647,9148],{},[21,54648,54649],{},"정본 소스",[81,54651,54652,54655,54662,54675,54682],{},[84,54653,54654],{},"시안 정책 표(사용자 제공 이미지) — 신청 서류·결제 방식·승인 흐름의 기획 정본",[84,54656,54657,54661],{},[26,54658,54659],{"href":18106},[32,54660,18107],{}," — 5단계 마법사 실제 구현 (사용자단)",[84,54663,54664,54669,54670,4536,54672,54674],{},[26,54665,54666],{"href":29639},[32,54667,54668],{},"..\u002Fmalgn-noti-api\u002Fsrc\u002Froutes\u002Fauth.ts"," — 백엔드 ",[32,54671,23258],{},[32,54673,23261],{}," 라우트",[84,54676,54677,54681],{},[26,54678,54679],{"href":27064},[32,54680,47658],{}," §2-4(회원·결제·계약 정책), §2-6-1(계약관리 정책)",[84,54683,54684,54689],{},[26,54685,54687],{"href":54686},"..\u002Fhistory\u002Fhistory.20260601",[32,54688,45581],{}," §4 — 사용자단 인증 백엔드 연동 (배포 #49)",[18,54691,54692,54694],{},[21,54693,45592],{},": 2026-06-01",[73,54696],{},[76,54698,54700],{"id":54699},"_1-5단계-공통-골격-모든-유형-동일","1. 5단계 공통 골격 (모든 유형 동일)",[5251,54702,54705],{"className":54703,"code":54704,"language":5256},[5254],"Step 1  회원 가입 안내 + 유형 선택  →  Step 2  정보 확인\nStep 3  아이디 등록 + 약관 동의      →  Step 4  휴대폰 본인 인증\nStep 5  가입 완료 (실 API 호출 + 자동 로그인 → \u002Fhome)\n",[32,54706,54704],{"__ignoreMap":4208},[18,54708,54709,54710,54713],{},"분기는 ",[21,54711,54712],{},"Step 1 유형 선택"," 직후부터 발생 — Step 2 입력 필드 \u002F Step 1·5의 안내 문구 \u002F 가입 후 화면(계약관리·멀티 계정) 노출 여부가 달라집니다.",[73,54715],{},[76,54717,54719],{"id":54718},"_2-핵심-차이-한눈에","2. 핵심 차이 한눈에",[101,54721,54722,54740],{},[104,54723,54724],{},[107,54725,54726,54728,54732,54736],{},[110,54727,6889],{},[110,54729,47726,54730],{},[32,54731,33543],{},[110,54733,47744,54734],{},[32,54735,33560],{},[110,54737,33916,54738],{},[32,54739,33576],{},[126,54741,54742,54757,54775,54791,54806,54823,54838,54856],{},[107,54743,54744,54749,54752,54754],{},[131,54745,54746],{},[21,54747,54748],{},"Step 2 필수 입력",[131,54750,54751],{},"사업자 등록번호 + 회사명 + 대표자명",[131,54753,54751],{},[131,54755,54756],{},"이름 + 주소",[107,54758,54759,54767,54770,54772],{},[131,54760,54761],{},[21,54762,54763,54764],{},"API ",[32,54765,54766],{},"companyName",[131,54768,54769],{},"회사명 그대로",[131,54771,54769],{},[131,54773,54774],{},"본인 이름 (개인은 회사 개념 없음)",[107,54776,54777,54783,54786,54788],{},[131,54778,54779],{},[21,54780,54763,54781],{},[32,54782,14992],{},[131,54784,54785],{},"대표자명",[131,54787,54785],{},[131,54789,54790],{},"본인 이름",[107,54792,54793,54798,54801,54803],{},[131,54794,54795],{},[21,54796,54797],{},"카드 충전 가입 서류",[131,54799,54800],{},"사업자 등록증 + 대부업등록증(해당) + 가입신청서",[131,54802,48456],{},[131,54804,54805],{},"가입신청서만",[107,54807,54808,54813,54818,54821],{},[131,54809,54810],{},[21,54811,54812],{},"후불 정산 가입 서류",[131,54814,54815,54816],{},"위 + ",[21,54817,53073],{},[131,54819,54820],{},"위 + 지급이행보증보험증권",[131,54822,53385],{},[107,54824,54825,54830,54833,54835],{},[131,54826,54827],{},[21,54828,54829],{},"가입 후 승인 (정책)",[131,54831,54832],{},"카드: 즉시 \u002F 후불: BackOffice 승인 + 통장사본",[131,54834,48456],{},[131,54836,54837],{},"❌ 승인 절차 없음 — 즉시 사용",[107,54839,54840,54845,54850,54853],{},[131,54841,54842],{},[21,54843,54844],{},"멀티 계정 (주·보조)",[131,54846,36626,54847,54849],{},[32,54848,19455],{}," 탭 노출",[131,54851,54852],{},"✅ 노출",[131,54854,54855],{},"❌ 탭 미노출",[107,54857,54858,54863,54868,54870],{},[131,54859,54860],{},[21,54861,54862],{},"계약관리",[131,54864,36626,54865,54867],{},[32,54866,19458],{}," 노출 (계약서 + 지급이행보증보험 첨부 UI)",[131,54869,54852],{},[131,54871,48895],{},[73,54873],{},[76,54875,54877,54878,54880],{"id":54876},"_3-법인사업자corp-절차","3. 법인사업자(",[32,54879,33543],{},") 절차",[237,54882,54884],{"id":54883},"step-1-안내유형-선택","Step 1 — 안내·유형 선택",[81,54886,54887,54897],{},[84,54888,54889,54890,54892,54893,54896],{},"카드 ",[32,54891,53349],{},"(아이콘 ",[32,54894,54895],{},"i-lucide-building-2",") 클릭 → 다음",[84,54898,54899,54900,51,54903],{},"안내 서류표: ",[21,54901,54902],{},"사업자 등록증 + 대부업등록증(해당업체) + 가입신청서(계약서)",[7968,54904,54905,54906],{},"+ 후불 시 ",[21,54907,53073],{},[237,54909,54911],{"id":54910},"step-2-정보-확인","Step 2 — 정보 확인",[81,54913,54914,54920,54925,54929],{},[84,54915,54916,54919],{},[21,54917,54918],{},"사업자 등록번호"," 3-2-5 자리 (3개 입력란)",[84,54921,54922],{},[21,54923,54924],{},"회사명",[84,54926,54927],{},[21,54928,54785],{},[84,54930,54931,54934],{},[32,54932,54933],{},"canProceed",": 사업자번호 형식 통과 + 회사명·대표자명 비어있지 않음",[237,54936,54938],{"id":54937},"step-3-아이디-등록약관","Step 3 — 아이디 등록·약관",[81,54940,54941,54949,54955],{},[84,54942,54943,54945,54946],{},[21,54944,7128],{},"(=로그인 ID) + OTP 6자리 인증 → ",[32,54947,54948],{},"idVerified=true",[84,54950,54951,54954],{},[21,54952,54953],{},"비밀번호"," 8자 이상 + 확인 일치",[84,54956,54957,54960],{},[21,54958,54959],{},"약관 동의"," — 필수 항목 전부 체크",[237,54962,54964],{"id":54963},"step-4-휴대폰-본인-인증","Step 4 — 휴대폰 본인 인증",[81,54966,54967,54970],{},[84,54968,54969],{},"010\u002F011\u002F016\u002F017\u002F018\u002F019 선택 + 3~4자리 + 4자리",[84,54971,54972,54973],{},"인증번호 6자리 → ",[32,54974,32369],{},[237,54976,54978],{"id":54977},"step-5-가입-완료-클릭-시-실-api-호출","Step 5 — 가입 완료 클릭 시 (실 API 호출)",[5251,54980,54984],{"className":54981,"code":54982,"language":54983,"meta":4208,"style":4208},"language-http shiki shiki-themes github-light github-dark","POST \u002Fauth\u002Fsignup\nContent-Type: application\u002Fjson\n\n{\n  \"companyName\": \"\u003C회사명>\",\n  \"loginid\":     \"\u003C이메일>\",\n  \"password\":    \"\u003C비밀번호>\",\n  \"email\":       \"\u003C이메일>\",\n  \"name\":        \"\u003C대표자명>\",\n  \"phone\":       \"010-XXXX-XXXX\"\n}\n","http",[32,54985,54986,54991,54996,55000,55004,55009,55014,55019,55024,55029,55034],{"__ignoreMap":4208},[7968,54987,54988],{"class":5110,"line":7970},[7968,54989,54990],{},"POST \u002Fauth\u002Fsignup\n",[7968,54992,54993],{"class":5110,"line":4212},[7968,54994,54995],{},"Content-Type: application\u002Fjson\n",[7968,54997,54998],{"class":5110,"line":4209},[7968,54999,8288],{"emptyLinePlaceholder":4259},[7968,55001,55002],{"class":5110,"line":4219},[7968,55003,49141],{},[7968,55005,55006],{"class":5110,"line":8291},[7968,55007,55008],{},"  \"companyName\": \"\u003C회사명>\",\n",[7968,55010,55011],{"class":5110,"line":8301},[7968,55012,55013],{},"  \"loginid\":     \"\u003C이메일>\",\n",[7968,55015,55016],{"class":5110,"line":8310},[7968,55017,55018],{},"  \"password\":    \"\u003C비밀번호>\",\n",[7968,55020,55021],{"class":5110,"line":8321},[7968,55022,55023],{},"  \"email\":       \"\u003C이메일>\",\n",[7968,55025,55026],{"class":5110,"line":8332},[7968,55027,55028],{},"  \"name\":        \"\u003C대표자명>\",\n",[7968,55030,55031],{"class":5110,"line":8343},[7968,55032,55033],{},"  \"phone\":       \"010-XXXX-XXXX\"\n",[7968,55035,55036],{"class":5110,"line":8349},[7968,55037,8987],{},[18,55039,28038,55040,55042,55043,55046,55047,55049],{},[32,55041,23296],{}," + JWT → 자동 로그인 → 발급 ",[21,55044,55045],{},"고객사 ID"," 노출 → ",[32,55048,11278],{}," 이동",[237,55051,55053],{"id":55052},"가입-후-화면","가입 후 화면",[81,55055,55056,55065,55076,55085],{},[84,55057,55058,6685,55060,55064],{},[21,55059,54862],{},[26,55061,55062],{"href":52417},[32,55063,19458],{},") — 카드 충전: 카드 등록 후 즉시 \u002F 후불: 계약서 전자 서명 + 사업자 등록증·지급이행보증보험증권 업로드 → BackOffice 승인 대기 → 통장사본 제출 (운영자단 미구현)",[84,55066,55067,6685,55070,55075],{},[21,55068,55069],{},"멀티 계정",[26,55071,55073],{"href":55072},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fmulti.vue",[32,55074,19455],{},") — 주계정\u002F보조계정 추가, 서비스 담당자 초대 메일 발송 흐름",[84,55077,55078,6685,55080,4343],{},[21,55079,46062],{},[26,55081,55083],{"href":55082},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fcards.vue",[32,55084,18635],{},[84,55086,55087,6685,55090,4420,55094,4343],{},[21,55088,55089],{},"크레딧 충전·내역",[26,55091,55092],{"href":18608},[32,55093,18609],{},[26,55095,55097],{"href":55096},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fcredit.vue",[32,55098,19629],{},[73,55100],{},[76,55102,55104,55105,54880],{"id":55103},"_4-개인사업자sole-절차","4. 개인사업자(",[32,55106,33560],{},[18,55108,55109,55112,55113,55116],{},[21,55110,55111],{},"법인사업자와 100% 동일."," 코드상 ",[32,55114,55115],{},"isBusiness = userType === 'corp' || 'sole'","로 한 갈래로 묶임.",[18,55118,55119],{},"차이점이 있다면:",[81,55121,55122,55129],{},[84,55123,55124,55125,55128],{},"Step 1 카드의 라벨·아이콘(",[32,55126,55127],{},"i-lucide-store"," 매장 아이콘)",[84,55130,55131],{},"시안 정책 메모상 향후 신용 한도·후불 정책에서 차등이 생길 수 있음 — 현 구현은 동일 처리",[18,55133,55134,55135,4361,55137,4361,55139,55142],{},"→ Step 2~5의 입력·검증·API 호출·가입 후 화면(",[32,55136,54862],{},[32,55138,55069],{},[32,55140,55141],{},"계약서 + 지급이행보증보험",") 모두 법인과 같음.",[73,55144],{},[76,55146,55148,55149,54880],{"id":55147},"_5-개인personal-절차","5. 개인(",[32,55150,33576],{},[237,55152,54884],{"id":55153},"step-1-안내유형-선택-1",[81,55155,55156,55163],{},[84,55157,54889,55158,54892,55160,54896],{},[32,55159,53377],{},[32,55161,55162],{},"i-lucide-user",[84,55164,54899,55165,55168,55169,4343],{},[21,55166,55167],{},"가입신청서(계약서)"," 1건만 (카드 충전 한정 — ",[21,55170,55171],{},"후불 미지원",[237,55173,55175],{"id":55174},"step-2-정보-확인-사업자와-다름","Step 2 — 정보 확인 (사업자와 다름)",[81,55177,55178,55183,55188,55198],{},[84,55179,55180],{},[21,55181,55182],{},"이름",[84,55184,55185],{},[21,55186,55187],{},"주소",[84,55189,55190,55191,6685,55194,55197],{},"사업자 등록번호·회사명·대표자명 필드 모두 ",[21,55192,55193],{},"표시 안 함",[32,55195,55196],{},"v-else"," 분기)",[84,55199,55200,55202],{},[32,55201,54933],{},": 이름·주소 비어있지 않음",[237,55204,55206],{"id":55205},"step-34-사업자와-동일-이메일otp약관휴대폰-본인-인증","Step 3·4 — 사업자와 동일 (이메일·OTP·약관·휴대폰 본인 인증)",[237,55208,54978],{"id":55209},"step-5-가입-완료-클릭-시-실-api-호출-1",[5251,55211,55213],{"className":54981,"code":55212,"language":54983,"meta":4208,"style":4208},"POST \u002Fauth\u002Fsignup\nContent-Type: application\u002Fjson\n\n{\n  \"companyName\": \"\u003C본인 이름>\",         \u002F\u002F ← 회사 개념이 없어 이름으로 대체\n  \"loginid\":     \"\u003C이메일>\",\n  \"password\":    \"\u003C비밀번호>\",\n  \"email\":       \"\u003C이메일>\",\n  \"name\":        \"\u003C본인 이름>\",\n  \"phone\":       \"010-XXXX-XXXX\"\n}\n",[32,55214,55215,55219,55223,55227,55231,55239,55243,55247,55251,55256,55260],{"__ignoreMap":4208},[7968,55216,55217],{"class":5110,"line":7970},[7968,55218,54990],{},[7968,55220,55221],{"class":5110,"line":4212},[7968,55222,54995],{},[7968,55224,55225],{"class":5110,"line":4209},[7968,55226,8288],{"emptyLinePlaceholder":4259},[7968,55228,55229],{"class":5110,"line":4219},[7968,55230,49141],{},[7968,55232,55233,55236],{"class":5110,"line":8291},[7968,55234,55235],{},"  \"companyName\": \"\u003C본인 이름>\",",[7968,55237,55238],{},"         \u002F\u002F ← 회사 개념이 없어 이름으로 대체\n",[7968,55240,55241],{"class":5110,"line":8301},[7968,55242,55013],{},[7968,55244,55245],{"class":5110,"line":8310},[7968,55246,55018],{},[7968,55248,55249],{"class":5110,"line":8321},[7968,55250,55023],{},[7968,55252,55253],{"class":5110,"line":8332},[7968,55254,55255],{},"  \"name\":        \"\u003C본인 이름>\",\n",[7968,55257,55258],{"class":5110,"line":8343},[7968,55259,55033],{},[7968,55261,55262],{"class":5110,"line":8349},[7968,55263,8987],{},[18,55265,28038,55266,55268,55269],{},[32,55267,23296],{}," + JWT → 자동 로그인 → 발급 고객사 ID 노출 → ",[32,55270,11278],{},[237,55272,55274],{"id":55273},"가입-후-화면-사업자와-다름","가입 후 화면 (사업자와 다름)",[81,55276,55277,55283,55289],{},[84,55278,55279,55282],{},[21,55280,55281],{},"계약관리 없음"," — 시안 정책: \"개인은 계약관리 없음 → 바로 사용 로그인 사용 가능\"",[84,55284,55285,55288],{},[21,55286,55287],{},"멀티 계정 없음"," — 시안 정책: \"개인 선택 시는 멀티계정 추가 탭 보이지 않게\"",[84,55290,55291,55293],{},[21,55292,46062],{}," 등록만으로 즉시 충전·발송 가능",[73,55295],{},[76,55297,55299],{"id":55298},"_6-가입-직후-backoffice-승인-워크플로우-정책-vs-현재-구현","6. 가입 직후 BackOffice 승인 워크플로우 (정책 vs 현재 구현)",[101,55301,55302,55318],{},[104,55303,55304],{},[107,55305,55306,55308,55311,55316],{},[110,55307,112],{},[110,55309,55310],{},"시안 정책",[110,55312,55313,55314,4343],{},"현재 백엔드(",[32,55315,23258],{},[110,55317,7718],{},[126,55319,55320,55336,55355],{},[107,55321,55322,55325,55328,55333],{},[131,55323,55324],{},"법인\u002F개인사업자 카드 충전",[131,55326,55327],{},"즉시 사용 가능",[131,55329,36626,55330,55332],{},[32,55331,33494],{}," 즉시 부여",[131,55334,55335],{},"정합",[107,55337,55338,55341,55344,55350],{},[131,55339,55340],{},"법인\u002F개인사업자 후불",[131,55342,55343],{},"온라인 계약 + BackOffice 승인 + 통장사본 후 사용",[131,55345,55346,55347],{},"⚠️ 동일하게 즉시 ",[32,55348,55349],{},"joined",[131,55351,55352],{},[21,55353,55354],{},"정책 미구현 — 후속",[107,55356,55357,55359,55361,55366],{},[131,55358,53377],{},[131,55360,55327],{},[131,55362,55363,55364],{},"✅ 즉시 ",[32,55365,55349],{},[131,55367,55335],{},[18,55369,28038,55370,55373],{},[21,55371,55372],{},"즉, 현재는 모든 유형이 동일하게 가입 즉시 로그인 가능",". 후불 승인 게이트는 백엔드 + 운영자단 양쪽 후속 작업.",[73,55375],{},[76,55377,55379],{"id":55378},"_7-화면-노출-차등-사용자단-라우트별","7. 화면 노출 차등 — 사용자단 라우트별",[101,55381,55382,55395],{},[104,55383,55384],{},[107,55385,55386,55388,55391,55393],{},[110,55387,22556],{},[110,55389,55390],{},"법인",[110,55392,53364],{},[110,55394,53377],{},[126,55396,55397,55413,55428,55444,55460,55476,55491,55506,55522],{},[107,55398,55399,55407,55409,55411],{},[131,55400,55401,55406],{},[26,55402,55404],{"href":55403},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fsettings.vue",[32,55405,3991],{}," (회원 정보 변경)",[131,55408,3869],{},[131,55410,3869],{},[131,55412,3869],{},[107,55414,55415,55422,55424,55426],{},[131,55416,55417,55421],{},[26,55418,55419],{"href":55082},[32,55420,18635],{}," (결제 카드 관리)",[131,55423,3869],{},[131,55425,3869],{},[131,55427,3869],{},[107,55429,55430,55438,55440,55442],{},[131,55431,55432,55437],{},[26,55433,55435],{"href":55434},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fpassword.vue",[32,55436,45821],{}," (비밀번호 변경)",[131,55439,3869],{},[131,55441,3869],{},[131,55443,3869],{},[107,55445,55446,55454,55456,55458],{},[131,55447,55448,55453],{},[26,55449,55451],{"href":55450},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fsecurity.vue",[32,55452,19452],{}," (보안 로그인)",[131,55455,3869],{},[131,55457,3869],{},[131,55459,3869],{},[107,55461,55462,55469,55471,55473],{},[131,55463,55464,55468],{},[26,55465,55466],{"href":55072},[32,55467,19455],{}," (멀티 계정 추가)",[131,55470,3869],{},[131,55472,3869],{},[131,55474,55475],{},"❌ 정책상 미노출",[107,55477,55478,55485,55487,55489],{},[131,55479,55480,55484],{},[26,55481,55482],{"href":52417},[32,55483,19458],{}," (계약 관리)",[131,55486,3869],{},[131,55488,3869],{},[131,55490,5788],{},[107,55492,55493,55500,55502,55504],{},[131,55494,55495,55499],{},[26,55496,55497],{"href":55096},[32,55498,19629],{}," (크레딧 내역)",[131,55501,3869],{},[131,55503,3869],{},[131,55505,3869],{},[107,55507,55508,55516,55518,55520],{},[131,55509,55510,55515],{},[26,55511,55513],{"href":55512},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Fbilling.vue",[32,55514,20012],{}," (결제 이력 등)",[131,55517,3869],{},[131,55519,3869],{},[131,55521,3869],{},[107,55523,55524,55532,55534,55536],{},[131,55525,55526,55531],{},[26,55527,55529],{"href":55528},"..\u002F..\u002Fapp\u002Fpages\u002Faccount\u002Finquiries.vue",[32,55530,18439],{}," (나의 문의)",[131,55533,3869],{},[131,55535,3869],{},[131,55537,3869],{},[15,55539,55540],{},[18,55541,49281,55542,47,55545,4536,55547,55549,55550,55553],{},[21,55543,55544],{},"현재 구현",[32,55546,19455],{},[32,55548,19458],{},"는 모든 유형에서 노출됩니다. 사용자 유형을 store\u002F쿠키에 저장하지 않고 있어 분기 로직이 아직 없음 → 후속 작업으로 ",[32,55551,55552],{},"auth.user.companyType"," 같은 필드 도입 + GNB·LNB·계정 메뉴 조건부 렌더링 필요.",[73,55555],{},[76,55557,55559],{"id":55558},"_8-알려진-한계-후속-작업","8. 알려진 한계 \u002F 후속 작업",[101,55561,55562,55573],{},[104,55563,55564],{},[107,55565,55566,55568,55571],{},[110,55567,3846],{},[110,55569,55570],{},"한계",[110,55572,270],{},[126,55574,55575,55596,55616,55637,55659,55678,55694,55714,55727],{},[107,55576,55577,55579,55587],{},[131,55578,4636],{},[131,55580,55581],{},[20367,55582,55583,55586],{},[32,55584,55585],{},"userType","이 백엔드로 전달 안 됨",[131,55588,36626,55589,89,55592,55595],{},[21,55590,55591],{},"완료(6\u002F2 §7)",[32,55593,55594],{},"TB_COMPANY.company_type"," 추가, signup Zod 확장, 프런트 전달",[107,55597,55598,55600,55605],{},[131,55599,4649],{},[131,55601,55602],{},[20367,55603,55604],{},"승인 게이트 미구현",[131,55606,36626,55607,89,55610,55612,55613,55615],{},[21,55608,55609],{},"완료(6\u002F2 §7~§10·§12·§13)",[32,55611,33498],{}," 4단계(pending\u002Freviewing\u002Fapproved\u002Frejected) + signup 자동 분기 + 18 라우트 ",[32,55614,34718],{}," + 프런트 글로벌 띠·라우트 가드 + biz 첨부 시 자동 reviewing 전이 + lazy backfill",[107,55617,55618,55620,55625],{},[131,55619,4662],{},[131,55621,55622],{},[21,55623,55624],{},"개인의 멀티 계정·계약관리 메뉴 노출 차단",[131,55626,55627,55628,89,55631,55633,55634,55636],{},"🟢 ",[21,55629,55630],{},"부분",[32,55632,54464],{},"은 노출됨. LNB 분기·",[32,55635,5836],{}," 메뉴 숨김은 후속",[107,55638,55639,55642,55647],{},[131,55640,55641],{},"4a",[131,55643,55644],{},[20367,55645,55646],{},"이메일 OTP 미연동",[131,55648,36626,55649,89,55652,4536,55654,4339,55656,55658],{},[21,55650,55651],{},"완료(6\u002F1 §5)",[32,55653,29660],{},[32,55655,29440],{},[32,55657,29386],{}," + SHA-256 + TTL 10분·5회 제한·소비 후 재사용 차단. NHN_MOCK 시 mockCode 응답에 노출",[107,55660,55661,55664,55669],{},[131,55662,55663],{},"4b",[131,55665,55666],{},[20367,55667,55668],{},"휴대폰 OTP·본인확인 미연동",[131,55670,36626,55671,55674,55675,55677],{},[21,55672,55673],{},"완료(6\u002F2 §4·§5·§15)"," — 자체 SMS OTP 4 purpose(signup·reset·change·",[21,55676,38570],{},") + NICE 통합인증 인프라(현재 mock). 본인확인은 NICE M(휴대폰)으로 일원화",[107,55679,55680,55682,55691],{},[131,55681,4691],{},[131,55683,55684,55687,55688,55690],{},[21,55685,55686],{},"약관 동의 미적재"," — Step 3 체크박스는 화면용. ",[32,55689,28079],{}," 무적재",[131,55692,55693],{},"signup 시 동의 항목 적재 또는 별도 라우트",[107,55695,55696,55698,55703],{},[131,55697,4708],{},[131,55699,55700],{},[20367,55701,55702],{},"서류 업로드 미구현",[131,55704,36626,55705,89,55708,55710,55711,55713],{},[21,55706,55707],{},"완료(6\u002F2 §11~§14)",[32,55709,45813],{}," 5 라우트 + R2 bucket ",[32,55712,35819],{},". signup auto-create로 가입 직후 'initial' 이용계약 1건 자동 생성. 사업자등록증 첨부 시 자동 reviewing 전이",[107,55715,55716,55718,55724],{},[131,55717,4724],{},[131,55719,55720,55723],{},[21,55721,55722],{},"사업자등록번호 검증"," — 형식(3-2-5) 외 체크섬·국세청 조회 미적용",[131,55725,55726],{},"체크섬 lib 또는 외부 API (P1)",[107,55728,55729,55731,55737],{},[131,55730,23932],{},[131,55732,55733,55736],{},[21,55734,55735],{},"NICE\u002FNHN 자격증명 real 모드 미"," (6\u002F2 §16)",[131,55738,55739],{},"NICE: 콘솔 IP 정책 해결 대기 \u002F NHN Notification Hub: User Access Key + 어댑터 재작성 대기",[18,55741,55742,55743,55746,55747,55750],{},"오늘(6\u002F2) §7~§15 작업으로 ",[21,55744,55745],{},"§1·§2·§4b·§6 완료",". 남은 핵심은 ",[21,55748,55749],{},"§5 약관 적재","와 외부 자격증명(§8).",[8560,55752,12401],{},{"title":4208,"searchDepth":4209,"depth":4209,"links":55754},[55755,55756,55757,55766,55768,55776,55777,55778],{"id":54699,"depth":4212,"text":54700},{"id":54718,"depth":4212,"text":54719},{"id":54876,"depth":4212,"text":55758,"children":55759},"3. 법인사업자(corp) 절차",[55760,55761,55762,55763,55764,55765],{"id":54883,"depth":4209,"text":54884},{"id":54910,"depth":4209,"text":54911},{"id":54937,"depth":4209,"text":54938},{"id":54963,"depth":4209,"text":54964},{"id":54977,"depth":4209,"text":54978},{"id":55052,"depth":4209,"text":55053},{"id":55103,"depth":4212,"text":55767},"4. 개인사업자(sole) 절차",{"id":55147,"depth":4212,"text":55769,"children":55770},"5. 개인(personal) 절차",[55771,55772,55773,55774,55775],{"id":55153,"depth":4209,"text":54884},{"id":55174,"depth":4209,"text":55175},{"id":55205,"depth":4209,"text":55206},{"id":55209,"depth":4209,"text":54978},{"id":55273,"depth":4209,"text":55274},{"id":55298,"depth":4212,"text":55299},{"id":55378,"depth":4212,"text":55379},{"id":55558,"depth":4212,"text":55559},{},"\u002Fpages\u002Fsignup",{"title":54637,"description":4208},"pages\u002FSIGNUP","KR4GLIJ18h_xKGKm1DZLtvRXMj8cJuPtvi4VlaOI-KI",{"id":55785,"title":55786,"body":55787,"description":57326,"extension":4257,"meta":57327,"navigation":4259,"path":57328,"seo":57329,"stem":44645,"__hash__":57330},"docs\u002FSTACK.md","기술 스택 — 맑은 메시징",{"type":8,"value":55788,"toc":57303},[55789,55792,55802,55813,55815,55819,55825,55827,55833,55836,56049,56060,56062,56068,56075,56078,56095,56097,56103,56106,56289,56297,56299,56303,56423,56428,56430,56434,56518,56520,56524,56618,56620,56624,56702,56709,56711,56715,56719,56768,56772,56828,56832,56878,56882,56939,56943,56988,56990,56994,56997,57121,57126,57128,57132,57233,57235,57239],[11,55790,55786],{"id":55791},"기술-스택-맑은-메시징",[18,55793,55794,55795,7505,55797,7505,55799,55801],{},"3개 레포(",[32,55796,7664],{},[32,55798,7672],{},[32,55800,50],{},")와 외부 의존을 한 번에 정리한 문서입니다.",[15,55803,55804],{},[18,55805,55806,55807,55809,55810,55812],{},"큰 그림은 ",[26,55808,7682],{"href":7681},", 프론트엔드 코딩 컨벤션은 ",[26,55811,4296],{"href":4295},"를 참조.",[73,55814],{},[76,55816,55818],{"id":55817},"_1-전체-토폴로지","1. 전체 토폴로지",[5251,55820,55823],{"className":55821,"code":55822,"language":5256},[5254],"사용자(고객사)             내부 운영자\n       │                       │\n       ▼                       ▼\n┌─────────────────┐    ┌──────────────────────┐\n│ malgn-noti      │    │ malgn-noti-admin     │       Cloudflare Pages\n│ (Nuxt 3, SSR)   │    │ (Nuxt 3, SSR)        │       (.pages.dev)\n└────────┬────────┘    └──────────┬───────────┘\n         │                        │\n         │     공개 API + 어드민 API│\n         └───────────┬────────────┘\n                     ▼\n            ┌─────────────────────┐\n            │ malgn-noti-api      │       Cloudflare Workers\n            │ (Hono, TS)          │       (.workers.dev)\n            └─────────┬───────────┘\n                      │\n       ┌──────────────┼──────────────────┐\n       ▼              ▼                  ▼\n┌─────────────┐ ┌─────────────┐  ┌────────────────────┐\n│ Hyperdrive  │ │ R2 \u002F KV \u002F   │  │ NHN Notification    │\n│ (MySQL)     │ │ Queues      │  │ Hub                 │\n└──────┬──────┘ └─────────────┘  └─────────────────────┘\n       ▼\n┌─────────────────┐\n│ AWS Aurora MySQL│\n└─────────────────┘\n",[32,55824,55822],{"__ignoreMap":4208},[73,55826],{},[76,55828,55830,55831],{"id":55829},"_2-사용자단-malgn-noti","2. 사용자단 — ",[32,55832,7664],{},[18,55834,55835],{},"고객(테넌트) 사용자가 사용하는 콘솔.",[101,55837,55838,55851],{},[104,55839,55840],{},[107,55841,55842,55844,55846,55849],{},[110,55843,6889],{},[110,55845,7715],{},[110,55847,55848],{},"버전",[110,55850,46107],{},[126,55852,55853,55870,55884,55901,55916,55934,55948,55961,55985,56001,56015,56034],{},[107,55854,55855,55857,55862,55867],{},[131,55856,7725],{},[131,55858,55859],{},[21,55860,55861],{},"Nuxt",[131,55863,55864,55865,4343],{},"3.21.5 (",[32,55866,7731],{},[131,55868,55869],{},"SSR + 라우팅 + 자동 import",[107,55871,55872,55874,55879,55882],{},[131,55873,7743],{},[131,55875,55876],{},[21,55877,55878],{},"TypeScript",[131,55880,55881],{},"5.9 (strict)",[131,55883,28614],{},[107,55885,55886,55889,55894,55896],{},[131,55887,55888],{},"뷰 라이브러리",[131,55890,55891],{},[21,55892,55893],{},"Vue",[131,55895,4159],{},[131,55897,55898,55899,4343],{},"UI 렌더 (",[32,55900,12915],{},[107,55902,55903,55906,55910,55913],{},[131,55904,55905],{},"UI 컴포넌트",[131,55907,55908],{},[21,55909,7762],{},[131,55911,55912],{},"3.3.7",[131,55914,55915],{},"폼\u002F모달\u002F테이블\u002F메뉴 — Reka UI + Tailwind",[107,55917,55918,55920,55925,55928],{},[131,55919,7770],{},[131,55921,55922],{},[21,55923,55924],{},"Tailwind CSS",[131,55926,55927],{},"v4 (Nuxt UI 통합)",[131,55929,55930,55931,55933],{},"유틸리티 + ",[32,55932,4342],{},"로 테마 토큰",[107,55935,55936,55939,55943,55945],{},[131,55937,55938],{},"상태 관리",[131,55940,55941],{},[21,55942,10549],{},[131,55944,4012],{},[131,55946,55947],{},"인증\u002F사용자\u002F크레딧",[107,55949,55950,55952,55956,55959],{},[131,55951,7844],{},[131,55953,55954],{},[21,55955,7847],{},[131,55957,55958],{},"3.25",[131,55960,7850],{},[107,55962,55963,55965,55969,55977],{},[131,55964,7790],{},[131,55966,55967],{},[21,55968,10542],{},[131,55970,55971,4473,55973,4473,55975],{},[32,55972,7796],{},[32,55974,7800],{},[32,55976,7803],{},[131,55978,55979,55980,55982,55983],{},"채널 카드는 ",[32,55981,7803],{},", 일반은 ",[32,55984,7796],{},[107,55986,55987,55990,55995,55998],{},[131,55988,55989],{},"차트",[131,55991,55992],{},[21,55993,55994],{},"Chart.js",[131,55996,55997],{},"4.x (예정)",[131,55999,56000],{},"통계 페이지",[107,56002,56003,56005,56009,56012],{},[131,56004,7814],{},[131,56006,56007],{},[21,56008,7817],{},[131,56010,56011],{},"Google Fonts \u002F CDN",[131,56013,56014],{},"UI=Inter, 숫자\u002FID=JetBrains Mono, 한국어 fallback=Pretendard (2026-05-18 디자인 피벗)",[107,56016,56017,56020,56025,56031],{},[131,56018,56019],{},"린트",[131,56021,56022],{},[21,56023,56024],{},"ESLint",[131,56026,56027,56028],{},"9.x + ",[32,56029,56030],{},"@nuxt\u002Feslint",[131,56032,56033],{},"Nuxt 권장 룰",[107,56035,56036,56039,56043,56046],{},[131,56037,56038],{},"패키지 매니저",[131,56040,56041],{},[21,56042,10206],{},[131,56044,56045],{},"10.25.0",[131,56047,56048],{},"빠른 설치 + 워크스페이스 가능",[18,56050,56051,56052,6685,56054,56056,56057],{},"배포 대상: ",[21,56053,7858],{},[32,56055,11189],{},"). production: ",[26,56058,10317],{"href":10317,"rel":56059},[30],[73,56061],{},[76,56063,56065,56066],{"id":56064},"_3-운영자단-malgn-noti-admin","3. 운영자단 — ",[32,56067,7672],{},[18,56069,56070,56071,56074],{},"사용자단과 ",[21,56072,56073],{},"완전히 동일한 스택",". 컴포넌트\u002F도메인 타입 공유 전제.",[18,56076,56077],{},"차이점만:",[81,56079,56080,56086,56089,56092],{},[84,56081,56082,56083],{},"배포 위치: ",[26,56084,11230],{"href":11230,"rel":56085},[30],[84,56087,56088],{},"접근 제어: 사내망\u002FCloudflare Access (예정)",[84,56090,56091],{},"인증: 어드민 전용 JWT + 2FA 강제 (예정)",[84,56093,56094],{},"추가 라이브러리(예정): 차트, CSV 내보내기 헬퍼",[73,56096],{},[76,56098,56100,56101],{"id":56099},"_4-백엔드-malgn-noti-api","4. 백엔드 — ",[32,56102,50],{},[18,56104,56105],{},"NHN\u002FPG\u002FAI를 직접 호출하는 유일한 컴포넌트.",[101,56107,56108,56120],{},[104,56109,56110],{},[107,56111,56112,56114,56116,56118],{},[110,56113,6889],{},[110,56115,7715],{},[110,56117,55848],{},[110,56119,46107],{},[126,56121,56122,56138,56154,56169,56184,56197,56215,56229,56242,56258,56276],{},[107,56123,56124,56127,56132,56135],{},[131,56125,56126],{},"런타임",[131,56128,56129],{},[21,56130,56131],{},"Cloudflare Workers",[131,56133,56134],{},"(V8 isolates)",[131,56136,56137],{},"edge 런타임",[107,56139,56140,56143,56147,56149],{},[131,56141,56142],{},"호환 모드",[131,56144,56145],{},[32,56146,11256],{},[131,56148,322],{},[131,56150,56151,56153],{},[32,56152,10764],{}," 등 Node API",[107,56155,56156,56159,56164,56166],{},[131,56157,56158],{},"호환 날짜",[131,56160,56161],{},[32,56162,56163],{},"2025-01-01",[131,56165,322],{},[131,56167,56168],{},"Workers 동작 기준",[107,56170,56171,56173,56178,56181],{},[131,56172,7725],{},[131,56174,56175],{},[21,56176,56177],{},"Hono",[131,56179,56180],{},"4.12",[131,56182,56183],{},"라우터\u002F미들웨어",[107,56185,56186,56188,56192,56195],{},[131,56187,7743],{},[131,56189,56190],{},[21,56191,55878],{},[131,56193,56194],{},"5.x (strict)",[131,56196,28614],{},[107,56198,56199,56202,56209,56212],{},[131,56200,56201],{},"ORM",[131,56203,56204,6685,56207,4343],{},[21,56205,56206],{},"Drizzle ORM",[32,56208,20112],{},[131,56210,56211],{},"예정",[131,56213,56214],{},"타입 안전 쿼리 + 마이그레이션",[107,56216,56217,56220,56224,56226],{},[131,56218,56219],{},"DB 드라이버",[131,56221,56222],{},[21,56223,10764],{},[131,56225,56211],{},[131,56227,56228],{},"MySQL wire 프로토콜",[107,56230,56231,56233,56237,56239],{},[131,56232,7844],{},[131,56234,56235],{},[21,56236,7847],{},[131,56238,56211],{},[131,56240,56241],{},"요청\u002F응답 스키마",[107,56243,56244,56247,56252,56255],{},[131,56245,56246],{},"타입 정의",[131,56248,56249],{},[21,56250,56251],{},"@cloudflare\u002Fworkers-types",[131,56253,56254],{},"4.20260511",[131,56256,56257],{},"Workers 환경 타입",[107,56259,56260,56263,56268,56271],{},[131,56261,56262],{},"빌드\u002F배포",[131,56264,56265],{},[21,56266,56267],{},"Wrangler",[131,56269,56270],{},"4.90.0",[131,56272,56273,56275],{},[32,56274,11261],{},", secrets, hyperdrive",[107,56277,56278,56281,56284,56286],{},[131,56279,56280],{},"OpenAPI (검토)",[131,56282,56283],{},"hono-openapi \u002F zod-openapi",[131,56285,322],{},[131,56287,56288],{},"자동 스펙",[18,56290,13321,56291,56293,56294],{},[21,56292,56131],{},". 엔드포인트: ",[26,56295,11267],{"href":11267,"rel":56296},[30],[73,56298],{},[76,56300,56302],{"id":56301},"_5-데이터-스토리지-큐","5. 데이터 \u002F 스토리지 \u002F 큐",[101,56304,56305,56318],{},[104,56306,56307],{},[107,56308,56309,56312,56314,56316],{},[110,56310,56311],{},"종류",[110,56313,7715],{},[110,56315,4328],{},[110,56317,4803],{},[126,56319,56320,56335,56350,56366,56381,56396,56411],{},[107,56321,56322,56325,56329,56332],{},[131,56323,56324],{},"트랜잭션 DB",[131,56326,56327],{},[21,56328,10750],{},[131,56330,56331],{},"AWS",[131,56333,56334],{},"테넌트, 사용자, 발송 메타, 크레딧 원장, 캠페인, 템플릿",[107,56336,56337,56339,56344,56347],{},[131,56338,10756],{},[131,56340,56341],{},[21,56342,56343],{},"Cloudflare Hyperdrive (MySQL)",[131,56345,56346],{},"Cloudflare → AWS",[131,56348,56349],{},"풀링 + 캐시 + 자격증명 보관",[107,56351,56352,56355,56360,56363],{},[131,56353,56354],{},"객체 스토리지",[131,56356,56357],{},[21,56358,56359],{},"Cloudflare R2",[131,56361,56362],{},"Cloudflare",[131,56364,56365],{},"MMS 첨부, Excel 업로드, Export 결과",[107,56367,56368,56371,56376,56378],{},[131,56369,56370],{},"KV",[131,56372,56373],{},[21,56374,56375],{},"Cloudflare KV",[131,56377,56362],{},[131,56379,56380],{},"세션, 짧은 캐시",[107,56382,56383,56386,56391,56393],{},[131,56384,56385],{},"큐",[131,56387,56388],{},[21,56389,56390],{},"Cloudflare Queues",[131,56392,56362],{},[131,56394,56395],{},"발송 큐 \u002F 재시도 큐 \u002F Export 잡 \u002F 캠페인 fan-out",[107,56397,56398,56401,56406,56408],{},[131,56399,56400],{},"Cron",[131,56402,56403],{},[21,56404,56405],{},"Workers Crons",[131,56407,56362],{},[131,56409,56410],{},"캠페인 스케줄러, 정리 잡, FCM\u002FAPNs 만료 알림",[107,56412,56413,56416,56418,56420],{},[131,56414,56415],{},"Durable Objects (검토)",[131,56417,322],{},[131,56419,56362],{},[131,56421,56422],{},"테넌트별 rate limit, 캠페인 실행 단위",[18,56424,56425,56427],{},[21,56426,10770],{},": 퍼블릭 엔드포인트 + SG로 Hyperdrive egress IP 화이트리스트(개발 단계). TLS 강제, 앱 전용 최소권한 계정. 운영 단계에서 RDS Proxy \u002F Cloudflare Tunnel 검토.",[73,56429],{},[76,56431,56433],{"id":56432},"_6-외부-서비스","6. 외부 서비스",[101,56435,56436,56449],{},[104,56437,56438],{},[107,56439,56440,56442,56444,56447],{},[110,56441,47889],{},[110,56443,4803],{},[110,56445,56446],{},"통합 방식",[110,56448,270],{},[126,56450,56451,56467,56484,56500],{},[107,56452,56453,56458,56461,56464],{},[131,56454,56455],{},[21,56456,56457],{},"NHN Cloud Notification Hub",[131,56459,56460],{},"SMS\u002FRCS\u002F알림톡\u002F이메일\u002FPush 실 발송",[131,56462,56463],{},"api 서버의 채널 어댑터에서 HTTP 호출",[131,56465,56466],{},"통합 예정",[107,56468,56469,56475,56478,56481],{},[131,56470,56471,56474],{},[21,56472,56473],{},"결제 PG"," (토스\u002F포트원\u002F나이스 중 미정)",[131,56476,56477],{},"크레딧 충전 결제",[131,56479,56480],{},"api 서버 어댑터 + 웹훅",[131,56482,56483],{},"미정",[107,56485,56486,56492,56495,56498],{},[131,56487,56488,56491],{},[21,56489,56490],{},"LLM 제공자"," (Anthropic\u002FOpenAI 중 미정)",[131,56493,56494],{},"AI 템플릿 생성",[131,56496,56497],{},"api 서버 게이트웨이 (비용 통제)",[131,56499,56483],{},[107,56501,56502,56506,56509,56515],{},[131,56503,56504],{},[21,56505,56011],{},[131,56507,56508],{},"Inter · JetBrains Mono · Pretendard · Instrument Serif 로드",[131,56510,56511,56512],{},"프론트 ",[32,56513,56514],{},"\u003Clink>",[131,56516,56517],{},"적용",[73,56519],{},[76,56521,56523],{"id":56522},"_7-개발운영-도구","7. 개발·운영 도구",[101,56525,56526,56537],{},[104,56527,56528],{},[107,56529,56530,56533,56535],{},[110,56531,56532],{},"도구",[110,56534,55848],{},[110,56536,4803],{},[126,56538,56539,56550,56558,56567,56579,56589,56599,56609],{},[107,56540,56541,56544,56547],{},[131,56542,56543],{},"Node.js",[131,56545,56546],{},"24.11.1",[131,56548,56549],{},"로컬 런타임",[107,56551,56552,56554,56556],{},[131,56553,10206],{},[131,56555,56045],{},[131,56557,56038],{},[107,56559,56560,56562,56564],{},[131,56561,56267],{},[131,56563,56270],{},[131,56565,56566],{},"Cloudflare CLI (deploy, secret, hyperdrive, tail)",[107,56568,56569,56572,56574],{},[131,56570,56571],{},"Git \u002F GitHub",[131,56573,322],{},[131,56575,56576,56577,4343],{},"버전 관리, 원격 저장소(",[32,56578,10676],{},[107,56580,56581,56583,56586],{},[131,56582,56024],{},[131,56584,56585],{},"9.x",[131,56587,56588],{},"정적 분석",[107,56590,56591,56594,56596],{},[131,56592,56593],{},"Vitest",[131,56595,56211],{},[131,56597,56598],{},"단위\u002F통합 테스트",[107,56600,56601,56604,56606],{},[131,56602,56603],{},"Miniflare",[131,56605,56211],{},[131,56607,56608],{},"Workers 로컬 시뮬레이션",[107,56610,56611,56613,56615],{},[131,56612,44286],{},[131,56614,56211],{},[131,56616,56617],{},"마이그레이션 생성\u002F적용",[73,56619],{},[76,56621,56623],{"id":56622},"_8-배포-호스팅","8. 배포 \u002F 호스팅",[101,56625,56626,56640],{},[104,56627,56628],{},[107,56629,56630,56633,56636,56638],{},[110,56631,56632],{},"프로젝트",[110,56634,56635],{},"호스팅",[110,56637,39383],{},[110,56639,11110],{},[126,56641,56642,56658,56673,56688],{},[107,56643,56644,56646,56648,56653],{},[131,56645,7664],{},[131,56647,7858],{},[131,56649,56650,56652],{},[32,56651,13896],{}," (CLI)",[131,56654,56655],{},[26,56656,10317],{"href":10317,"rel":56657},[30],[107,56659,56660,56662,56664,56668],{},[131,56661,7672],{},[131,56663,7858],{},[131,56665,56666,56652],{},[32,56667,13896],{},[131,56669,56670],{},[26,56671,11230],{"href":11230,"rel":56672},[30],[107,56674,56675,56677,56679,56683],{},[131,56676,50],{},[131,56678,56131],{},[131,56680,56681,56652],{},[32,56682,11261],{},[131,56684,56685],{},[26,56686,11267],{"href":11267,"rel":56687},[30],[107,56689,56690,56693,56696,56699],{},[131,56691,56692],{},"AWS Aurora",[131,56694,56695],{},"AWS RDS",[131,56697,56698],{},"(수동\u002FIaC 예정)",[131,56700,56701],{},"(VPC 내부 + 퍼블릭 엔드포인트)",[18,56703,56704,56705,56708],{},"배포는 현 단계에서 ",[21,56706,56707],{},"wrangler CLI 직접 배포",". GitHub Actions 자동화는 추후 옵션.",[73,56710],{},[76,56712,56714],{"id":56713},"_9-핵심-선택-이유-대안-비교","9. 핵심 선택 이유 \u002F 대안 비교",[237,56716,56718],{"id":56717},"nuxt-3-vs-다른-프레임워크","Nuxt 3 vs. 다른 프레임워크",[101,56720,56721,56734],{},[104,56722,56723],{},[107,56724,56725,56728,56731],{},[110,56726,56727],{},"후보",[110,56729,56730],{},"선택 여부",[110,56732,56733],{},"메모",[126,56735,56736,56748,56758],{},[107,56737,56738,56743,56745],{},[131,56739,56740],{},[21,56741,56742],{},"Nuxt 3",[131,56744,3869],{},[131,56746,56747],{},"Vue 생태계 + Nuxt UI 공식 통합 + SSR\u002FSSG\u002FCSR 자유",[107,56749,56750,56753,56755],{},[131,56751,56752],{},"Next.js",[131,56754,5788],{},[131,56756,56757],{},"React 기반. 사용자 선호 Vue.",[107,56759,56760,56763,56765],{},[131,56761,56762],{},"Vite + Vue Router",[131,56764,5788],{},[131,56766,56767],{},"SSR\u002Fauto-import 직접 구축 부담",[237,56769,56771],{"id":56770},"nuxt-ui-vs-다른-ui-라이브러리","Nuxt UI vs. 다른 UI 라이브러리",[101,56773,56774,56784],{},[104,56775,56776],{},[107,56777,56778,56780,56782],{},[110,56779,56727],{},[110,56781,56730],{},[110,56783,56733],{},[126,56785,56786,56797,56808,56818],{},[107,56787,56788,56792,56794],{},[131,56789,56790],{},[21,56791,7762],{},[131,56793,3869],{},[131,56795,56796],{},"Nuxt 공식 + Tailwind v4 통합 + 263 페이지에 필요한 컴포넌트 풀세트",[107,56798,56799,56802,56805],{},[131,56800,56801],{},"Nuxt UI Pro",[131,56803,56804],{},"❌ (검토 보류)",[131,56806,56807],{},"Dashboard 레이아웃 등 유료. MVP 후 재검토.",[107,56809,56810,56813,56815],{},[131,56811,56812],{},"Naive UI \u002F Element Plus",[131,56814,5788],{},[131,56816,56817],{},"자체 디자인 시스템, Tailwind와 분리 — 통합 비용",[107,56819,56820,56823,56825],{},[131,56821,56822],{},"shadcn-vue",[131,56824,5788],{},[131,56826,56827],{},"컴포넌트 일일 복사. 빠른 263 페이지엔 부담.",[237,56829,56831],{"id":56830},"cloudflare-vs-aws-only","Cloudflare vs. AWS-only",[101,56833,56834,56844],{},[104,56835,56836],{},[107,56837,56838,56840,56842],{},[110,56839,56727],{},[110,56841,56730],{},[110,56843,56733],{},[126,56845,56846,56858,56868],{},[107,56847,56848,56853,56855],{},[131,56849,56850],{},[21,56851,56852],{},"Cloudflare(Workers\u002FPages\u002FR2\u002FKV\u002FQueues) + AWS(Aurora)",[131,56854,3869],{},[131,56856,56857],{},"edge 글로벌 + 비용 효율, Aurora만 AWS 의존",[107,56859,56860,56863,56865],{},[131,56861,56862],{},"AWS-only (Lambda + RDS + S3 + SQS)",[131,56864,5788],{},[131,56866,56867],{},"글로벌 분산\u002F콜드스타트\u002F비용 면에서 Cloudflare가 우세",[107,56869,56870,56873,56875],{},[131,56871,56872],{},"Vercel + PlanetScale",[131,56874,5788],{},[131,56876,56877],{},"PlanetScale 무료 티어 종료, MySQL 호환성 트레이드오프",[237,56879,56881],{"id":56880},"aurora-mysql-via-hyperdrive","Aurora MySQL via Hyperdrive",[101,56883,56884,56894],{},[104,56885,56886],{},[107,56887,56888,56890,56892],{},[110,56889,56727],{},[110,56891,56730],{},[110,56893,56733],{},[126,56895,56896,56908,56918,56928],{},[107,56897,56898,56903,56905],{},[131,56899,56900],{},[21,56901,56902],{},"Hyperdrive (MySQL) + mysql2",[131,56904,3869],{},[131,56906,56907],{},"TCP 풀링 + 캐시, 자격증명 분리, 코드 단순",[107,56909,56910,56913,56915],{},[131,56911,56912],{},"RDS Data API (HTTP)",[131,56914,5788],{},[131,56916,56917],{},"Serverless v2만, MySQL Data API 제약",[107,56919,56920,56923,56925],{},[131,56921,56922],{},"Cloudflare D1 (SQLite)",[131,56924,5788],{},[131,56926,56927],{},"대량 이력\u002F원장에 부담, 멀티 리전 제약",[107,56929,56930,56933,56936],{},[131,56931,56932],{},"자체 RDS Proxy",[131,56934,56935],{},"(이전)",[131,56937,56938],{},"Hyperdrive와 이중 풀링 충돌 가능. 운영 단계 재검토.",[237,56940,56942],{"id":56941},"hono-vs-itty-router-직접-작성","Hono vs. Itty Router \u002F 직접 작성",[101,56944,56945,56955],{},[104,56946,56947],{},[107,56948,56949,56951,56953],{},[110,56950,56727],{},[110,56952,56730],{},[110,56954,56733],{},[126,56956,56957,56968,56978],{},[107,56958,56959,56963,56965],{},[131,56960,56961],{},[21,56962,56177],{},[131,56964,3869],{},[131,56966,56967],{},"Workers에 최적화, 미들웨어 풍부, TS 친화",[107,56969,56970,56973,56975],{},[131,56971,56972],{},"Itty Router",[131,56974,5788],{},[131,56976,56977],{},"더 가볍지만 미들웨어 빈약",[107,56979,56980,56983,56985],{},[131,56981,56982],{},"직접 작성",[131,56984,5788],{},[131,56986,56987],{},"라우팅·검증·CORS·에러 처리 다시 만들 이유 없음",[73,56989],{},[76,56991,56993],{"id":56992},"_10-버전-호환-표","10. 버전 호환 표",[18,56995,56996],{},"각 도구의 메이저 버전이 맞물려 동작합니다.",[101,56998,56999,57008],{},[104,57000,57001],{},[107,57002,57003,57006],{},[110,57004,57005],{},"묶음",[110,57007,55848],{},[126,57009,57010,57019,57028,57038,57047,57056,57067,57076,57085,57094,57103,57112],{},[107,57011,57012,57014],{},[131,57013,56543],{},[131,57015,57016],{},[21,57017,57018],{},"24.x",[107,57020,57021,57023],{},[131,57022,10206],{},[131,57024,57025],{},[21,57026,57027],{},"10.x",[107,57029,57030,57032],{},[131,57031,55861],{},[131,57033,57034,57037],{},[21,57035,57036],{},"3.21.x"," (compat v4 모드)",[107,57039,57040,57042],{},[131,57041,55893],{},[131,57043,57044],{},[21,57045,57046],{},"3.5.x",[107,57048,57049,57051],{},[131,57050,55878],{},[131,57052,57053],{},[21,57054,57055],{},"5.9.x",[107,57057,57058,57061],{},[131,57059,57060],{},"Nuxt UI",[131,57062,57063,57066],{},[21,57064,57065],{},"3.3.x"," (Reka UI + Tailwind v4)",[107,57068,57069,57071],{},[131,57070,55924],{},[131,57072,57073],{},[21,57074,57075],{},"v4",[107,57077,57078,57080],{},[131,57079,10549],{},[131,57081,57082],{},[21,57083,57084],{},"2.3.x",[107,57086,57087,57089],{},[131,57088,56177],{},[131,57090,57091],{},[21,57092,57093],{},"4.12.x",[107,57095,57096,57098],{},[131,57097,56267],{},[131,57099,57100],{},[21,57101,57102],{},"4.90.x",[107,57104,57105,57108],{},[131,57106,57107],{},"Cloudflare Workers compatibility_date",[131,57109,57110],{},[21,57111,56163],{},[107,57113,57114,57117],{},[131,57115,57116],{},"Cloudflare Workers compatibility_flags",[131,57118,57119],{},[32,57120,11256],{},[15,57122,57123],{},[18,57124,57125],{},"의존성 업데이트 시 Nuxt ↔ Nuxt UI ↔ Tailwind는 같이 올려야 합니다 (서로 강한 결합).",[73,57127],{},[76,57129,57131],{"id":57130},"_11-미정-결정-대기","11. 미정 \u002F 결정 대기",[101,57133,57134,57145],{},[104,57135,57136],{},[107,57137,57138,57140,57142],{},[110,57139,6889],{},[110,57141,50622],{},[110,57143,57144],{},"결정 시점",[126,57146,57147,57157,57168,57179,57190,57200,57211,57222],{},[107,57148,57149,57151,57154],{},[131,57150,56473],{},[131,57152,57153],{},"토스 \u002F 포트원 \u002F 나이스",[131,57155,57156],{},"api 결제 모듈 작업 시",[107,57158,57159,57162,57165],{},[131,57160,57161],{},"AI 제공자",[131,57163,57164],{},"Anthropic \u002F OpenAI \u002F 자체",[131,57166,57167],{},"AI 템플릿 모듈 작업 시",[107,57169,57170,57173,57176],{},[131,57171,57172],{},"Aurora 토폴로지",[131,57174,57175],{},"Provisioned vs. Serverless v2, Multi-AZ, R\u002FW 분리",[131,57177,57178],{},"운영 환경 준비 시",[107,57180,57181,57184,57187],{},[131,57182,57183],{},"Aurora SG 갱신 절차",[131,57185,57186],{},"IaC(Terraform\u002FSAM) vs. 수동",[131,57188,57189],{},"운영 진입 전",[107,57191,57192,57194,57197],{},[131,57193,56354],{},[131,57195,57196],{},"R2 단독 vs. S3 병행",[131,57198,57199],{},"Aurora 리전 확정 후",[107,57201,57202,57205,57208],{},[131,57203,57204],{},"다국어 지원",[131,57206,57207],{},"한국어 우선 → 영어 추가 시점",[131,57209,57210],{},"글로벌 확장 결정 시",[107,57212,57213,57216,57219],{},[131,57214,57215],{},"Nuxt UI Pro 도입",[131,57217,57218],{},"무료 → Pro 전환 여부",[131,57220,57221],{},"MVP 후 재검토",[107,57223,57224,57227,57230],{},[131,57225,57226],{},"GitHub Actions 자동 배포",[131,57228,57229],{},"도입 시점",[131,57231,57232],{},"CI 정책 결정 후",[73,57234],{},[76,57236,57238],{"id":57237},"_12-참고","12. 참고",[81,57240,57241,57248,57254,57261,57268,57273,57278,57283,57290,57296],{},[84,57242,57243],{},[26,57244,57247],{"href":57245,"rel":57246},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002F",[30],"Cloudflare Workers docs",[84,57249,57250],{},[26,57251,56343],{"href":57252,"rel":57253},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fhyperdrive\u002F",[30],[84,57255,57256],{},[26,57257,57260],{"href":57258,"rel":57259},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002F",[30],"Cloudflare Pages docs",[84,57262,57263],{},[26,57264,57267],{"href":57265,"rel":57266},"https:\u002F\u002Fhono.dev\u002F",[30],"Hono docs",[84,57269,57270],{},[26,57271,10514],{"href":10512,"rel":57272},[30],[84,57274,57275],{},[26,57276,10521],{"href":10519,"rel":57277},[30],[84,57279,57280],{},[26,57281,10528],{"href":10526,"rel":57282},[30],[84,57284,57285],{},[26,57286,57289],{"href":57287,"rel":57288},"https:\u002F\u002Form.drizzle.team\u002Fdocs\u002Fget-started-mysql",[30],"Drizzle ORM (MySQL)",[84,57291,57292],{},[26,57293,56457],{"href":57294,"rel":57295},"https:\u002F\u002Fdocs.nhncloud.com\u002Fko\u002FNotification\u002FNotification%20Hub\u002Fko\u002Foverview\u002F",[30],[84,57297,57298],{},[26,57299,57302],{"href":57300,"rel":57301},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonRDS\u002Flatest\u002FAuroraUserGuide\u002FAurora.AuroraMySQL.html",[30],"AWS Aurora MySQL",{"title":4208,"searchDepth":4209,"depth":4209,"links":57304},[57305,57306,57308,57310,57312,57313,57314,57315,57316,57323,57324,57325],{"id":55817,"depth":4212,"text":55818},{"id":55829,"depth":4212,"text":57307},"2. 사용자단 — malgn-noti",{"id":56064,"depth":4212,"text":57309},"3. 운영자단 — malgn-noti-admin",{"id":56099,"depth":4212,"text":57311},"4. 백엔드 — malgn-noti-api",{"id":56301,"depth":4212,"text":56302},{"id":56432,"depth":4212,"text":56433},{"id":56522,"depth":4212,"text":56523},{"id":56622,"depth":4212,"text":56623},{"id":56713,"depth":4212,"text":56714,"children":57317},[57318,57319,57320,57321,57322],{"id":56717,"depth":4209,"text":56718},{"id":56770,"depth":4209,"text":56771},{"id":56830,"depth":4209,"text":56831},{"id":56880,"depth":4209,"text":56881},{"id":56941,"depth":4209,"text":56942},{"id":56992,"depth":4212,"text":56993},{"id":57130,"depth":4212,"text":57131},{"id":57237,"depth":4212,"text":57238},"3개 레포(malgn-noti \u002F malgn-noti-admin \u002F malgn-noti-api)와 외부 의존을 한 번에 정리한 문서입니다.",{},"\u002Fstack",{"title":55786,"description":57326},"4xbTNG_PL-PbBEyIwP5R9LcmwMixYrftHqP_ArPjGOs",{"id":57332,"title":57333,"body":57334,"description":4208,"extension":4257,"meta":61641,"navigation":4259,"path":26863,"seo":61642,"stem":27349,"__hash__":61643},"docs\u002FWBS.md","WBS (Work Breakdown Structure)",{"type":8,"value":57335,"toc":61608},[57336,57339,57356,57358,57362,57469,57477,57482,57486,57489,57579,57584,57586,57589,57637,57642,57644,57647,57651,57729,57733,57822,57826,57901,57905,57961,57965,58079,58081,58084,58088,58177,58181,58225,58229,58260,58264,58389,58393,58500,58504,58576,58578,58582,58586,58657,58661,58837,58841,58962,58964,58967,59018,59020,59023,59035,59039,59224,59230,59728,59737,59773,59777,60066,60070,60079,60602,60608,60636,60640,61031,61038,61289,61293,61519,61521,61524],[11,57337,57333],{"id":57338},"wbs-work-breakdown-structure",[15,57340,57341,57344,57351],{},[18,57342,57343],{},"맑은메시지(가칭) — NHN Cloud Notification Hub 기반 멀티 테넌트 메시징 SaaS.",[18,57345,57346,57347,57350],{},"5단계(준비 → 정책 → 기획·설계 → 디자인 → 개발)를 가중치로 묶어 단일 사이클로 진행. 일별 변경은 ",[26,57348,12440],{"href":57349},"history\u002F","에 누적 기록.",[18,57352,57353,57355],{},[21,57354,45592],{},": 2026-06-04",[73,57357],{},[76,57359,57361],{"id":57360},"진행률-스냅샷-2026-06-04-기준","진행률 스냅샷 (2026-06-04 기준)",[101,57363,57364,57377],{},[104,57365,57366],{},[107,57367,57368,57370,57372,57374],{},[110,57369,30322],{},[110,57371,115],{},[110,57373,118],{},[110,57375,57376],{},"핵심 진행 사항",[126,57378,57379,57394,57413,57428,57443],{},[107,57380,57381,57385,57387,57391],{},[131,57382,57383],{},[21,57384,135],{},[131,57386,138],{},[131,57388,57389],{},[21,57390,143],{},[131,57392,57393],{},"R&R·텔레그램·화면설계 정본·환경 셋팅 완료. 도메인·브랜딩·계약서·마케팅 진행 중",[107,57395,57396,57400,57402,57406],{},[131,57397,57398],{},[21,57399,156],{},[131,57401,159],{},[131,57403,57404],{},[21,57405,143],{},[131,57407,57408,57409,57412],{},"회원 구조·결제·메시지 채널·주소록 정책 90% 합의. ",[21,57410,57411],{},"PG = 토스(TossPayments) 확정","(6\u002F4). 후불결제·캠페인 AB 테스트 미정",[107,57414,57415,57419,57421,57425],{},[131,57416,57417],{},[21,57418,176],{},[131,57420,179],{},[131,57422,57423],{},[21,57424,184],{},[131,57426,57427],{},"BackOffice 1차 5종 70%. 2차·통계·운영가이드 미진",[107,57429,57430,57434,57436,57440],{},[131,57431,57432],{},[21,57433,197],{},[131,57435,138],{},[131,57437,57438],{},[21,57439,179],{},[131,57441,57442],{},"사용자단 Relay-inspired DS 정본·디자인 가이드(\u002Fguide) 살아있는 카탈로그로 대체 운영. 정식 스타일 가이드·MD 산출물은 미작성",[107,57444,57445,57449,57451,57455],{},[131,57446,57447],{},[21,57448,216],{},[131,57450,219],{},[131,57452,57453],{},[21,57454,143],{},[131,57456,57457,57458,57460,57461,57464,57465,57468],{},"UI(5-3A) 거의 완료(14✅) \u002F API(5-2) 약 72%(13✅+2🟢+3⚪) — 6\u002F4 신규 5-2-19 WBS R2 + 5-2-20 ",[32,57459,39656],{}," + 5-2-21 NHN Notification Hub 어댑터 \u002F ",[21,57462,57463],{},"연동(5-3C) 약 40%(10✅+2🟢+8⚪)"," — 6\u002F4 5-3C-7(이메일·휴대폰 변경 OTP) + 5-3C-20(서비스 담당자 이메일 변경) 완료 \u002F 관리자단(5-4) ",[21,57466,57467],{},"핸드오프 17 페이지 화면 ✅"," + dev=screen\u002Fpartial\u002Flive 라벨 + 로고 통일 \u002F 통합·배포(5-5) Hyperdrive Tunnel 전환 + NHN Email 실 발송 ✅. 가중평균 약 55%.",[18,57470,57471,6685,57474,4343],{},[21,57472,57473],{},"전체 가중평균: 약 47.5%",[32,57475,57476],{},"0.10×55 + 0.15×55 + 0.20×35 + 0.10×20 + 0.45×55 ≈ 47.5",[18,57478,57479,57481],{},[21,57480,70],{},": ✅ 완료 · 🟢 진행 중 · ⚪ 대기 · ⛔ 보류",[237,57483,57485],{"id":57484},"김도형-담당-73-개발테스트-완료-일정-64-결정","김도형 담당 — 7\u002F3 개발·테스트 완료 일정 (6\u002F4 결정)",[18,57487,57488],{},"남은 task를 우선순위·블로커 의존 순으로 5주에 배분.",[101,57490,57491,57507],{},[104,57492,57493],{},[107,57494,57495,57498,57501,57504],{},[110,57496,57497],{},"주차",[110,57499,57500],{},"기간",[110,57502,57503],{},"주요 마일스톤",[110,57505,57506],{},"대상 task",[126,57508,57509,57523,57537,57551,57565],{},[107,57510,57511,57514,57517,57520],{},[131,57512,57513],{},"W1",[131,57515,57516],{},"6\u002F5~6\u002F9",[131,57518,57519],{},"잔작업·in_progress 마무리 + P0 backbone",[131,57521,57522],{},"5-3C-2(로그아웃) · 5-3C-5(약관) · 5-3C-6(companyType) · 5-2-10(웹훅 전 채널)",[107,57524,57525,57528,57531,57534],{},[131,57526,57527],{},"W2",[131,57529,57530],{},"6\u002F10~6\u002F15",[131,57532,57533],{},"사용자단 발송·NHN real 1차",[131,57535,57536],{},"5-3C-3(비번 재설정 OTP) · 5-3-14(시스템 페이지) · 5-2-16(envelope) · 5-5-12(NHN SMS) · 5-3C-12(발송 6채널 실 API)",[107,57538,57539,57542,57545,57548],{},[131,57540,57541],{},"W3",[131,57543,57544],{},"6\u002F16~6\u002F22",[131,57546,57547],{},"이력\u002F통계\u002F주소록\u002F문의 + Flow 엔진",[131,57549,57550],{},"5-3C-13·14·16 · 5-2-11(Export worker) · 5-2-12(Flow 엔진) · 5-5-5(push\u002Frcs\u002Fkakao 어댑터)",[107,57552,57553,57556,57559,57562],{},[131,57554,57555],{},"W4",[131,57557,57558],{},"6\u002F23~6\u002F29",[131,57560,57561],{},"캠페인·PG·관리자단 P0",[131,57563,57564],{},"5-2-13(캠페인) · 5-2-14(PG 토스) · 5-5-8(결제 연동) · 5-3C-15(크레딧·결제) · 5-3C-8·9·10(계정 부속) · 5-4-3·4·5·6·8(관리자 P0)",[107,57566,57567,57570,57573,57576],{},[131,57568,57569],{},"W5",[131,57571,57572],{},"6\u002F30~7\u002F3",[131,57574,57575],{},"관리자단 잔여 + AI + 외부 의존 + 통합 테스트",[131,57577,57578],{},"5-4-7·9·10·11·12·13 · 5-2-15(AI 게이트웨이) · 5-5-9(AI 연동) · 5-5-6(NICE 실 모드, 외부 의존)",[15,57580,57581],{},[18,57582,57583],{},"운영성 task(5-5-1 사용자단 배포 카운터·5-5-3 API Workers 배포 카운터)는 단일 마감일이 없는 누적 지표이므로 일정 미설정.",[73,57585],{},[76,57587,27085],{"id":57588},"단계별-가중치",[101,57590,57591,57600],{},[104,57592,57593],{},[107,57594,57595,57597],{},[110,57596,112],{},[110,57598,57599],{},"비중",[126,57601,57602,57609,57616,57623,57630],{},[107,57603,57604,57607],{},[131,57605,57606],{},"1. 프로젝트 준비",[131,57608,138],{},[107,57610,57611,57614],{},[131,57612,57613],{},"2. 주요 서비스 정책 이슈 정리",[131,57615,159],{},[107,57617,57618,57621],{},[131,57619,57620],{},"3. 서비스 기획 (화면설계)",[131,57622,179],{},[107,57624,57625,57628],{},[131,57626,57627],{},"4. 디자인 \u002F 퍼블리싱",[131,57629,138],{},[107,57631,57632,57635],{},[131,57633,57634],{},"5. 서비스 개발",[131,57636,219],{},[15,57638,57639],{},[18,57640,57641],{},"개발 비중이 큰 프로젝트라 Step 5를 45%로 가중. Step 1·2는 합의·문서 위주라 가볍게.",[73,57643],{},[11,57645,27091],{"id":57646},"step-1-프로젝트-준비-10",[76,57648,57650],{"id":57649},"_1-1-rr-사업-기획","1-1. R&R · 사업 기획",[101,57652,57653,57674],{},[104,57654,57655],{},[107,57656,57657,57659,57661,57663,57665,57668,57671],{},[110,57658,264],{},[110,57660,267],{},[110,57662,270],{},[110,57664,273],{},[110,57666,57667],{},"산출물 \u002F 메모",[110,57669,57670],{},"목표일",[110,57672,57673],{},"완료일",[126,57675,57676,57693,57710],{},[107,57677,57678,57680,57682,57684,57686,57689,57691],{},[131,57679,286],{},[131,57681,291],{},[131,57683,3869],{},[131,57685,298],{},[131,57687,57688],{},"메모 확인",[131,57690,42215],{},[131,57692,42215],{},[107,57694,57695,57697,57699,57701,57703,57706,57708],{},[131,57696,308],{},[131,57698,313],{},[131,57700,3869],{},[131,57702,319],{},[131,57704,57705],{},"경쟁사 단가표",[131,57707,322],{},[131,57709,322],{},[107,57711,57712,57714,57717,57720,57722,57725,57727],{},[131,57713,329],{},[131,57715,57716],{},"당사 원가 확인 및 가격 정책 결정 (단가 결정)",[131,57718,57719],{},"🟢",[131,57721,319],{},[131,57723,57724],{},"기본 단가 책정(고객 모수에 따른 할인률 정책 필요), MMS는 이미지 3장까지 비용설계, 단가표(기획안)",[131,57726,322],{},[131,57728,322],{},[76,57730,57732],{"id":57731},"_1-2-사업-준비","1-2. 사업 준비",[101,57734,57735,57753],{},[104,57736,57737],{},[107,57738,57739,57741,57743,57745,57747,57749,57751],{},[110,57740,264],{},[110,57742,267],{},[110,57744,270],{},[110,57746,273],{},[110,57748,57667],{},[110,57750,57670],{},[110,57752,57673],{},[126,57754,57755,57773,57789,57805],{},[107,57756,57757,57759,57762,57764,57766,57769,57771],{},[131,57758,373],{},[131,57760,57761],{},"특수한 유형의 메시징 사업자 신청",[131,57763,3931],{},[131,57765,319],{},[131,57767,57768],{},"프로젝트 추진 중간평가 이후 진행",[131,57770,322],{},[131,57772,322],{},[107,57774,57775,57777,57779,57781,57783,57785,57787],{},[131,57776,393],{},[131,57778,398],{},[131,57780,3931],{},[131,57782,319],{},[131,57784,57768],{},[131,57786,322],{},[131,57788,322],{},[107,57790,57791,57793,57795,57797,57799,57801,57803],{},[131,57792,412],{},[131,57794,417],{},[131,57796,3931],{},[131,57798,322],{},[131,57800,57768],{},[131,57802,322],{},[131,57804,322],{},[107,57806,57807,57809,57811,57813,57815,57818,57820],{},[131,57808,430],{},[131,57810,435],{},[131,57812,57719],{},[131,57814,319],{},[131,57816,57817],{},"가입신청서·이용약관·개인정보처리방침·요금신고내역 초안 \u002F 1차 검토 완료 → 2차 수정본 진행 \u002F 전무님 검토 필요",[131,57819,322],{},[131,57821,322],{},[76,57823,57825],{"id":57824},"_1-3-커뮤니케이션","1-3. 커뮤니케이션",[101,57827,57828,57846],{},[104,57829,57830],{},[107,57831,57832,57834,57836,57838,57840,57842,57844],{},[110,57833,264],{},[110,57835,267],{},[110,57837,270],{},[110,57839,273],{},[110,57841,57667],{},[110,57843,57670],{},[110,57845,57673],{},[126,57847,57848,57865,57884],{},[107,57849,57850,57852,57854,57856,57858,57861,57863],{},[131,57851,473],{},[131,57853,478],{},[131,57855,3869],{},[131,57857,484],{},[131,57859,57860],{},"맑은메시지 TF",[131,57862,42215],{},[131,57864,42215],{},[107,57866,57867,57869,57872,57874,57876,57879,57882],{},[131,57868,493],{},[131,57870,57871],{},"화면설계 — 피그마 정본",[131,57873,3869],{},[131,57875,504],{},[131,57877,57878],{},"피그마",[131,57880,57881],{},"5\u002F11",[131,57883,57881],{},[107,57885,57886,57888,57890,57892,57894,57897,57899],{},[131,57887,514],{},[131,57889,519],{},[131,57891,3931],{},[131,57893,298],{},[131,57895,57896],{},"프로젝트 폴더",[131,57898,322],{},[131,57900,322],{},[76,57902,57904],{"id":57903},"_1-4-서비스-메타-결정","1-4. 서비스 메타 결정",[101,57906,57907,57921],{},[104,57908,57909],{},[107,57910,57911,57913,57915,57917,57919],{},[110,57912,264],{},[110,57914,267],{},[110,57916,270],{},[110,57918,273],{},[110,57920,57667],{},[126,57922,57923,57935,57948],{},[107,57924,57925,57927,57929,57931,57933],{},[131,57926,557],{},[131,57928,562],{},[131,57930,3931],{},[131,57932,298],{},[131,57934,322],{},[107,57936,57937,57939,57942,57944,57946],{},[131,57938,575],{},[131,57940,57941],{},"브랜딩 (맑은메시지 외 함께 아이데이션)",[131,57943,3931],{},[131,57945,298],{},[131,57947,322],{},[107,57949,57950,57952,57955,57957,57959],{},[131,57951,593],{},[131,57953,57954],{},"마케팅 기획 — 기존 고객군 & 메시징 only 고객군",[131,57956,3931],{},[131,57958,604],{},[131,57960,322],{},[76,57962,57964],{"id":57963},"_1-5-프로젝트-환경-셋팅","1-5. 프로젝트 환경 셋팅",[101,57966,57967,57985],{},[104,57968,57969],{},[107,57970,57971,57973,57975,57977,57979,57981,57983],{},[110,57972,264],{},[110,57974,267],{},[110,57976,270],{},[110,57978,273],{},[110,57980,57667],{},[110,57982,57670],{},[110,57984,57673],{},[126,57986,57987,58004,58021,58041,58060],{},[107,57988,57989,57991,57993,57995,57997,58000,58002],{},[131,57990,637],{},[131,57992,642],{},[131,57994,3869],{},[131,57996,298],{},[131,57998,57999],{},"폴더 셋팅",[131,58001,42215],{},[131,58003,42215],{},[107,58005,58006,58008,58010,58012,58014,58017,58019],{},[131,58007,656],{},[131,58009,661],{},[131,58011,3869],{},[131,58013,484],{},[131,58015,58016],{},"3 레포(malgn-noti·-admin·-api) + Cloudflare Pages 2 + Workers 1",[131,58018,57881],{},[131,58020,57881],{},[107,58022,58023,58025,58027,58029,58031,58037,58039],{},[131,58024,675],{},[131,58026,680],{},[131,58028,3869],{},[131,58030,484],{},[131,58032,58033],{},[26,58034,58035],{"href":58035,"rel":58036},"https:\u002F\u002Fmalgn-noti.pages.dev\u002F",[30],[131,58038,57881],{},[131,58040,57881],{},[107,58042,58043,58045,58047,58049,58051,58056,58058],{},[131,58044,693],{},[131,58046,698],{},[131,58048,3869],{},[131,58050,484],{},[131,58052,58053],{},[26,58054,24905],{"href":24905,"rel":58055},[30],[131,58057,57881],{},[131,58059,57881],{},[107,58061,58062,58064,58066,58068,58070,58075,58077],{},[131,58063,711],{},[131,58065,716],{},[131,58067,3869],{},[131,58069,484],{},[131,58071,58072],{},[26,58073,18675],{"href":18675,"rel":58074},[30],[131,58076,57881],{},[131,58078,57881],{},[73,58080],{},[11,58082,27097],{"id":58083},"step-2-주요-서비스-정책-이슈-정리-15",[76,58085,58087],{"id":58086},"_2-1-프로토타입-및-문서","2-1. 프로토타입 및 문서",[101,58089,58090,58104],{},[104,58091,58092],{},[107,58093,58094,58096,58098,58100,58102],{},[110,58095,264],{},[110,58097,267],{},[110,58099,270],{},[110,58101,273],{},[110,58103,57667],{},[126,58105,58106,58125,58138,58151,58164],{},[107,58107,58108,58110,58112,58114,58116],{},[131,58109,769],{},[131,58111,774],{},[131,58113,57719],{},[131,58115,298],{},[131,58117,58118,89,58122],{},[26,58119,58120],{"href":58120,"rel":58121},"https:\u002F\u002Fmalgn-notifications.pages.dev\u002F#\u002F",[30],[21,58123,58124],{},"IA 정본(263 페이지)",[107,58126,58127,58129,58131,58133,58135],{},[131,58128,788],{},[131,58130,793],{},[131,58132,3931],{},[131,58134,322],{},[131,58136,58137],{},"\u002F#\u002Fsitemap",[107,58139,58140,58142,58144,58146,58148],{},[131,58141,806],{},[131,58143,811],{},[131,58145,3931],{},[131,58147,298],{},[131,58149,58150],{},"\u002F#\u002Fpagelists",[107,58152,58153,58155,58157,58159,58161],{},[131,58154,824],{},[131,58156,829],{},[131,58158,3931],{},[131,58160,504],{},[131,58162,58163],{},"프로토타입 만들지 말지 결정",[107,58165,58166,58168,58170,58172,58174],{},[131,58167,843],{},[131,58169,848],{},[131,58171,3931],{},[131,58173,322],{},[131,58175,58176],{},"\u002F#\u002Fbackoffice + Google Sheets",[76,58178,58180],{"id":58179},"_2-2-주요-서비스-참조","2-2. 주요 서비스 참조",[101,58182,58183,58197],{},[104,58184,58185],{},[107,58186,58187,58189,58191,58193,58195],{},[110,58188,264],{},[110,58190,267],{},[110,58192,270],{},[110,58194,273],{},[110,58196,57667],{},[126,58198,58199,58212],{},[107,58200,58201,58203,58205,58207,58209],{},[131,58202,885],{},[131,58204,890],{},[131,58206,3931],{},[131,58208,322],{},[131,58210,58211],{},"통합 대상",[107,58213,58214,58216,58218,58220,58222],{},[131,58215,904],{},[131,58217,909],{},[131,58219,3931],{},[131,58221,322],{},[131,58223,58224],{},"참조",[76,58226,58228],{"id":58227},"_2-3-캠페인-서비스","2-3. 캠페인 서비스",[101,58230,58231,58245],{},[104,58232,58233],{},[107,58234,58235,58237,58239,58241,58243],{},[110,58236,264],{},[110,58238,267],{},[110,58240,270],{},[110,58242,273],{},[110,58244,57667],{},[126,58246,58247],{},[107,58248,58249,58251,58253,58255,58257],{},[131,58250,947],{},[131,58252,952],{},[131,58254,3931],{},[131,58256,604],{},[131,58258,58259],{},"솔라피(CRM 결합) + 개별 문자 발송 사례",[76,58261,58263],{"id":58262},"_2-4-회원결제계약-정책","2-4. 회원·결제·계약 정책",[101,58265,58266,58284],{},[104,58267,58268],{},[107,58269,58270,58272,58274,58276,58278,58280,58282],{},[110,58271,264],{},[110,58273,267],{},[110,58275,270],{},[110,58277,273],{},[110,58279,57667],{},[110,58281,57670],{},[110,58283,57673],{},[126,58285,58286,58304,58322,58339,58355,58372],{},[107,58287,58288,58290,58292,58294,58296,58299,58302],{},[131,58289,990],{},[131,58291,995],{},[131,58293,57719],{},[131,58295,298],{},[131,58297,58298],{},"법인사업자\u002F개인사업자\u002F개인 3유형, 회원유형별 계약서 초안 단계별, 카드 충전식 vs 후불 결제, 계정관리>계약관리에 지급이행보증보험 첨부 인터페이스 추가",[131,58300,58301],{},"5\u002F12",[131,58303,58301],{},[107,58305,58306,58308,58311,58313,58315,58318,58320],{},[131,58307,1010],{},[131,58309,58310],{},"회원 구조 — 멀티 계정 (주계정·보조계정)",[131,58312,57719],{},[131,58314,298],{},[131,58316,58317],{},"법인·개인사업자만 멀티계정 추가 탭 노출, 개인은 미노출",[131,58319,58301],{},[131,58321,58301],{},[107,58323,58324,58326,58328,58330,58332,58335,58337],{},[131,58325,1029],{},[131,58327,1034],{},[131,58329,3931],{},[131,58331,298],{},[131,58333,58334],{},"크레딧 일정금액 이하일 때 n원 자동 충전 (향후 재논의)",[131,58336,322],{},[131,58338,322],{},[107,58340,58341,58343,58345,58347,58349,58351,58353],{},[131,58342,1048],{},[131,58344,1053],{},[131,58346,3931],{},[131,58348,298],{},[131,58350,322],{},[131,58352,322],{},[131,58354,322],{},[107,58356,58357,58359,58361,58363,58365,58368,58370],{},[131,58358,1066],{},[131,58360,1071],{},[131,58362,3931],{},[131,58364,298],{},[131,58366,58367],{},"후불결제 내부로직은 -크레딧, 후불시 사용 크레딧 표현, 다음 결제일 표현",[131,58369,322],{},[131,58371,322],{},[107,58373,58374,58376,58378,58380,58382,58385,58387],{},[131,58375,1085],{},[131,58377,1090],{},[131,58379,3931],{},[131,58381,322],{},[131,58383,58384],{},"법인·개인사업자 온라인 계약 절차 후 BackOffice 승인 → 로그인 가능 (후불시 통장사본). 개인은 계약관리 없음 → 즉시 사용",[131,58386,322],{},[131,58388,322],{},[76,58390,58392],{"id":58391},"_2-5-메시지-채널-정책","2-5. 메시지 채널 정책",[101,58394,58395,58413],{},[104,58396,58397],{},[107,58398,58399,58401,58403,58405,58407,58409,58411],{},[110,58400,264],{},[110,58402,267],{},[110,58404,270],{},[110,58406,273],{},[110,58408,57667],{},[110,58410,57670],{},[110,58412,57673],{},[126,58414,58415,58432,58449,58466,58483],{},[107,58416,58417,58419,58421,58423,58425,58428,58430],{},[131,58418,1128],{},[131,58420,1133],{},[131,58422,57719],{},[131,58424,298],{},[131,58426,58427],{},"발송창(알림톡 제외)에 AI검토(맞춤법·문장 다듬기). 문자·RCS·이메일에 AI 문장다듬기 기능 추가",[131,58429,58301],{},[131,58431,58301],{},[107,58433,58434,58436,58438,58440,58442,58445,58447],{},[131,58435,1147],{},[131,58437,1152],{},[131,58439,3931],{},[131,58441,298],{},[131,58443,58444],{},"광고 선택 시 수신거부 번호 입력창 분리(맨 마지막). 내용 재확인 후 인터페이스 설계 예정",[131,58446,322],{},[131,58448,322],{},[107,58450,58451,58453,58455,58457,58459,58462,58464],{},[131,58452,1166],{},[131,58454,1171],{},[131,58456,57719],{},[131,58458,298],{},[131,58460,58461],{},"알림톡 미수신 시 SMS\u002FLMS 순차발송 기본세팅, RCS·복합·푸시는 상단 설명. 복합(플로우) 생성 관리 화면의 Default 알림톡→SMS→이메일 순 호출",[131,58463,58301],{},[131,58465,58301],{},[107,58467,58468,58470,58472,58474,58476,58479,58481],{},[131,58469,1185],{},[131,58471,1190],{},[131,58473,57719],{},[131,58475,298],{},[131,58477,58478],{},"랜딩페이지 관리 페이지 + 기본형·확장형 화면 추가",[131,58480,58301],{},[131,58482,58301],{},[107,58484,58485,58487,58489,58491,58493,58496,58498],{},[131,58486,1204],{},[131,58488,1209],{},[131,58490,57719],{},[131,58492,298],{},[131,58494,58495],{},"유선(증명서 업로드) + 휴대폰(본인인증 PASS) 인터페이스",[131,58497,58301],{},[131,58499,58301],{},[76,58501,58503],{"id":58502},"_2-6-캠페인주소록브랜드","2-6. 캠페인·주소록·브랜드",[101,58505,58506,58524],{},[104,58507,58508],{},[107,58509,58510,58512,58514,58516,58518,58520,58522],{},[110,58511,264],{},[110,58513,267],{},[110,58515,270],{},[110,58517,273],{},[110,58519,57667],{},[110,58521,57670],{},[110,58523,57673],{},[126,58525,58526,58543,58560],{},[107,58527,58528,58530,58532,58534,58536,58539,58541],{},[131,58529,1247],{},[131,58531,1252],{},[131,58533,3931],{},[131,58535,298],{},[131,58537,58538],{},"대상자에게 랜덤으로 A·B 메시지 발송 후 성과 비교. 캠페인 관리 기능 최종 정의 후 설계",[131,58540,322],{},[131,58542,322],{},[107,58544,58545,58547,58549,58551,58553,58556,58558],{},[131,58546,1266],{},[131,58548,1271],{},[131,58550,57719],{},[131,58552,298],{},[131,58554,58555],{},"이메일·전화번호 클릭 시 단건 발송 레이어 팝업. 연락처·그룹에 메시지채널 선택 후 바로가기. CRM 예제 화면 수집(안병훈)",[131,58557,58301],{},[131,58559,58301],{},[107,58561,58562,58564,58566,58568,58570,58572,58574],{},[131,58563,1285],{},[131,58565,1290],{},[131,58567,3931],{},[131,58569,1295],{},[131,58571,322],{},[131,58573,322],{},[131,58575,322],{},[73,58577],{},[11,58579,58581],{"id":58580},"step-3-서비스-기획-화면설계-or-설계-20","Step 3 — 서비스 기획 (화면설계 or 설계) (20%)",[76,58583,58585],{"id":58584},"_3-1-front","3-1. Front",[101,58587,58588,58604],{},[104,58589,58590],{},[107,58591,58592,58594,58596,58598,58600,58602],{},[110,58593,264],{},[110,58595,267],{},[110,58597,270],{},[110,58599,273],{},[110,58601,57667],{},[110,58603,57670],{},[126,58605,58606,58623,58637],{},[107,58607,58608,58610,58612,58614,58616,58621],{},[131,58609,1344],{},[131,58611,1349],{},[131,58613,57719],{},[131,58615,1354],{},[131,58617,58618],{},[26,58619,58120],{"href":58120,"rel":58620},[30],[131,58622,322],{},[107,58624,58625,58627,58629,58631,58633,58635],{},[131,58626,1363],{},[131,58628,1368],{},[131,58630,3931],{},[131,58632,1373],{},[131,58634,322],{},[131,58636,322],{},[107,58638,58639,58641,58643,58645,58647,58655],{},[131,58640,1382],{},[131,58642,1387],{},[131,58644,3931],{},[131,58646,1354],{},[131,58648,58649,58650,58654],{},"(사용자단 ",[26,58651,5744],{"href":58652,"rel":58653},"https:\u002F\u002Fmalgn-noti.pages.dev\u002Fhelp",[30]," 라이브 — 컨텐츠 보강 필요)",[131,58656,322],{},[76,58658,58660],{"id":58659},"_3-2-backoffice-1차","3-2. BackOffice 1차",[101,58662,58663,58679],{},[104,58664,58665],{},[107,58666,58667,58669,58671,58673,58675,58677],{},[110,58668,264],{},[110,58670,267],{},[110,58672,270],{},[110,58674,273],{},[110,58676,57667],{},[110,58678,57670],{},[126,58680,58681,58698,58714,58729,58745,58760,58776,58790,58806,58823],{},[107,58682,58683,58685,58688,58690,58692,58695],{},[131,58684,1425],{},[131,58686,58687],{},"공통 \u002F 로그인 \u002F 계정 관리",[131,58689,57719],{},[131,58691,504],{},[131,58693,58694],{},"바로가기",[131,58696,58697],{},"5\u002F22",[107,58699,58700,58702,58705,58707,58709,58712],{},[131,58701,1444],{},[131,58703,58704],{},"회원 \u002F 고객사 관리",[131,58706,57719],{},[131,58708,504],{},[131,58710,58711],{},"회원 발송 이력 관리, 고객사 결제 상세, 환불신청 제외",[131,58713,58697],{},[107,58715,58716,58718,58720,58722,58724,58727],{},[131,58717,1463],{},[131,58719,1468],{},[131,58721,57719],{},[131,58723,504],{},[131,58725,58726],{},"운영자 계정 관리, 권한\u002F역할 관리(RBAC), 감사 로그",[131,58728,58697],{},[107,58730,58731,58733,58736,58738,58740,58742],{},[131,58732,1482],{},[131,58734,58735],{},"요금 \u002F 단가 관리",[131,58737,57719],{},[131,58739,504],{},[131,58741,58694],{},[131,58743,58744],{},"5\u002F29",[107,58746,58747,58749,58751,58753,58755,58758],{},[131,58748,1501],{},[131,58750,1506],{},[131,58752,57719],{},[131,58754,504],{},[131,58756,58757],{},"운영 가이드 관리 제외",[131,58759,58744],{},[107,58761,58762,58764,58766,58768,58770,58773],{},[131,58763,1520],{},[131,58765,1525],{},[131,58767,3931],{},[131,58769,504],{},[131,58771,58772],{},"캠페인 제외, 회원\u002F고객사 관리의 회원 발송 이력 포함",[131,58774,58775],{},"6\u002F12",[107,58777,58778,58780,58782,58784,58786,58788],{},[131,58779,1540],{},[131,58781,1545],{},[131,58783,3931],{},[131,58785,504],{},[131,58787,322],{},[131,58789,58775],{},[107,58791,58792,58794,58797,58799,58801,58803],{},[131,58793,1558],{},[131,58795,58796],{},"결제 \u002F 크레딧 관리, 고객사 상세 결제 탭",[131,58798,3931],{},[131,58800,504],{},[131,58802,322],{},[131,58804,58805],{},"6\u002F19",[107,58807,58808,58810,58813,58815,58817,58820],{},[131,58809,1577],{},[131,58811,58812],{},"템플릿 검수 \u002F 관리",[131,58814,3931],{},[131,58816,504],{},[131,58818,58819],{},"샘플 템플릿 관리, AI 템플릿 정책 관리 제외",[131,58821,58822],{},"6\u002F24",[107,58824,58825,58827,58829,58831,58833,58835],{},[131,58826,1597],{},[131,58828,1602],{},[131,58830,3931],{},[131,58832,504],{},[131,58834,322],{},[131,58836,58822],{},[76,58838,58840],{"id":58839},"_3-3-backoffice-2차","3-3. BackOffice 2차",[101,58842,58843,58857],{},[104,58844,58845],{},[107,58846,58847,58849,58851,58853,58855],{},[110,58848,264],{},[110,58850,267],{},[110,58852,270],{},[110,58854,273],{},[110,58856,57667],{},[126,58858,58859,58872,58884,58898,58911,58924,58938,58950],{},[107,58860,58861,58863,58866,58868,58870],{},[131,58862,1639],{},[131,58864,58865],{},"통계 \u002F 리포트",[131,58867,3931],{},[131,58869,504],{},[131,58871,322],{},[107,58873,58874,58876,58878,58880,58882],{},[131,58875,1657],{},[131,58877,1662],{},[131,58879,3931],{},[131,58881,504],{},[131,58883,322],{},[107,58885,58886,58888,58891,58893,58895],{},[131,58887,1675],{},[131,58889,58890],{},"템플릿 검수 \u002F 관리 (AI 템플릿 정책 관리)",[131,58892,3931],{},[131,58894,504],{},[131,58896,58897],{},"샘플 템플릿 관리, AI 템플릿 정책 관리",[107,58899,58900,58902,58904,58906,58908],{},[131,58901,1693],{},[131,58903,1698],{},[131,58905,3931],{},[131,58907,504],{},[131,58909,58910],{},"캠페인 진행",[107,58912,58913,58915,58917,58919,58921],{},[131,58914,1711],{},[131,58916,1506],{},[131,58918,3931],{},[131,58920,504],{},[131,58922,58923],{},"운영 가이드 관리",[107,58925,58926,58928,58931,58933,58935],{},[131,58927,1729],{},[131,58929,58930],{},"콘텐츠 \u002F 사이트 관리",[131,58932,3931],{},[131,58934,504],{},[131,58936,58937],{},"시스템 설정, 점검 모드 관리, 외부 연동 설정",[107,58939,58940,58942,58944,58946,58948],{},[131,58941,1748],{},[131,58943,1468],{},[131,58945,3931],{},[131,58947,504],{},[131,58949,322],{},[107,58951,58952,58954,58956,58958,58960],{},[131,58953,1765],{},[131,58955,1770],{},[131,58957,3931],{},[131,58959,504],{},[131,58961,322],{},[73,58963],{},[11,58965,27109],{"id":58966},"step-4-디자인-퍼블리싱-10",[101,58968,58969,58983],{},[104,58970,58971],{},[107,58972,58973,58975,58977,58979,58981],{},[110,58974,264],{},[110,58976,267],{},[110,58978,270],{},[110,58980,273],{},[110,58982,57667],{},[126,58984,58985,59005],{},[107,58986,58987,58989,58991,58993,58995],{},[131,58988,1819],{},[131,58990,1824],{},[131,58992,3931],{},[131,58994,1830],{},[131,58996,58997,58998,59000,59001,59004],{},"(개발 측에서 ",[26,58999,11891],{"href":7646}," Relay-inspired v1.0 정본 + ",[26,59002,4457],{"href":15418,"rel":59003},[30]," 라이브 카탈로그를 운영 — 디자인팀 정식 스타일 가이드 별도 필요)",[107,59006,59007,59009,59011,59013,59015],{},[131,59008,1839],{},[131,59010,1844],{},[131,59012,3931],{},[131,59014,1830],{},[131,59016,59017],{},"(개발 측에서 Nuxt 3 + Nuxt UI v3 + Tailwind v4로 직접 퍼블리싱 운영 중)",[73,59019],{},[11,59021,27120],{"id":59022},"step-5-서비스-개발-45",[15,59024,59025],{},[18,59026,49281,59027,59030,59031,59034],{},[21,59028,59029],{},"2026-06-01 재구성"," — 원본 WBS는 채널·도메인 단위로 묶여 있었으나, 실제 진행은 ",[21,59032,59033],{},"사용자단 화면이 6채널·전 도메인에 걸쳐 빠르게 완성","된 반면 관리자단은 셸·기획 단계에 머무름. 진행 그래프가 더 잘 보이도록 산출물 단위로 재정렬.",[76,59036,59038],{"id":59037},"_5-1-설계-및-준비","5-1. 설계 및 준비",[101,59040,59041,59059],{},[104,59042,59043],{},[107,59044,59045,59047,59049,59051,59053,59055,59057],{},[110,59046,264],{},[110,59048,267],{},[110,59050,270],{},[110,59052,273],{},[110,59054,57667],{},[110,59056,57670],{},[110,59058,57673],{},[126,59060,59061,59082,59117,59141,59163,59181,59203],{},[107,59062,59063,59065,59067,59069,59071,59077,59080],{},[131,59064,1898],{},[131,59066,1903],{},[131,59068,3869],{},[131,59070,484],{},[131,59072,59073,59076],{},[26,59074,59075],{"href":44645},"malgn-noti\u002Fdoc\u002FSTACK.md"," — 3 레포 책임 분리 + Cloudflare\u002FAWS 혼합 토폴로지 + NHN 통합 모델",[131,59078,59079],{},"5\u002F14",[131,59081,59079],{},[107,59083,59084,59086,59088,59090,59092,59112,59115],{},[131,59085,1918],{},[131,59087,1923],{},[131,59089,3869],{},[131,59091,484],{},[131,59093,59094,59095,4536,59097,4536,59099,4339,59102,4361,59105,4361,59107,6078,59109,59111],{},"49 테이블 데이터 모델(",[32,59096,20067],{},[32,59098,20071],{},[32,59100,59101],{},"status INT 1\u002F0\u002F-1",[32,59103,59104],{},"*_state",[32,59106,20226],{},[32,59108,20087],{},[32,59110,12969],{}," 분리), Mermaid ERD 9종, 확장성 전략(월 RANGE 파티셔닝·Hot\u002FWarm\u002FCold·R2 오프로드)",[131,59113,59114],{},"5\u002F27",[131,59116,59114],{},[107,59118,59119,59121,59123,59125,59127,59136,59139],{},[131,59120,1938],{},[131,59122,1943],{},[131,59124,3869],{},[131,59126,484],{},[131,59128,59129,59130,59132,59133,59135],{},"Relay-inspired v1.0 (",[26,59131,11891],{"href":7646},") — ink 11단 + 그린 액센트 ",[32,59134,4672],{}," + Inter + JetBrains Mono + Pretendard",[131,59137,59138],{},"5\u002F18",[131,59140,59138],{},[107,59142,59143,59145,59148,59150,59152,59158,59161],{},[131,59144,1958],{},[131,59146,59147],{},"사용자단 디자인 가이드 (살아있는 카탈로그)",[131,59149,3869],{},[131,59151,484],{},[131,59153,59154,59157],{},[26,59155,4457],{"href":15418,"rel":59156},[30]," — 18+ 섹션 라이브 카탈로그",[131,59159,59160],{},"5\u002F19",[131,59162,59160],{},[107,59164,59165,59167,59170,59172,59174,59177,59179],{},[131,59166,1977],{},[131,59168,59169],{},"관리자단 부트스트랩 + 셸 레이아웃",[131,59171,3869],{},[131,59173,484],{},[131,59175,59176],{},"Nuxt 3 + Nuxt UI v3 + LNB(256px·8그룹) + TopBar(64px) + 첫 배포",[131,59178,59114],{},[131,59180,59114],{},[107,59182,59183,59185,59187,59189,59191,59199,59201],{},[131,59184,1996],{},[131,59186,2001],{},[131,59188,3869],{},[131,59190,484],{},[131,59192,59193,59198],{},[26,59194,59197],{"href":59195,"rel":59196},"https:\u002F\u002Fmalgn-noti-admin.pages.dev\u002Fguide",[30],"admin \u002Fguide"," — 14 섹션 단일 페이지",[131,59200,59114],{},[131,59202,59114],{},[107,59204,59205,59207,59210,59212,59214,59220,59222],{},[131,59206,2014],{},[131,59208,59209],{},"관리자단 페이지 기획 MD",[131,59211,3869],{},[131,59213,484],{},[131,59215,59216,59219],{},[32,59217,59218],{},"malgn-noti-admin\u002Fdoc\u002Fpages\u002F"," 33개 MD (8 그룹 + 32 sub)",[131,59221,59114],{},[131,59223,59114],{},[76,59225,59227,59228,4343],{"id":59226},"_5-2-api-서버-malgn-noti-api","5-2. API 서버 (",[32,59229,50],{},[101,59231,59232,59250],{},[104,59233,59234],{},[107,59235,59236,59238,59240,59242,59244,59246,59248],{},[110,59237,264],{},[110,59239,267],{},[110,59241,270],{},[110,59243,273],{},[110,59245,57667],{},[110,59247,57670],{},[110,59249,57673],{},[126,59251,59252,59276,59295,59318,59344,59366,59383,59402,59422,59442,59464,59487,59513,59531,59558,59576,59593,59622,59640,59661,59684],{},[107,59253,59254,59256,59259,59261,59263,59271,59274],{},[131,59255,2057],{},[131,59257,59258],{},"Hono on Workers 부트스트랩 + Hyperdrive(Aurora) 연결",[131,59260,3869],{},[131,59262,484],{},[131,59264,59265,59266,4339,59268,59270],{},"drizzle-orm\u002Fmysql2 + ",[32,59267,20116],{},[32,59269,20120],{}," + 프로덕션 배포 #1",[131,59272,59273],{},"5\u002F26",[131,59275,59273],{},[107,59277,59278,59280,59282,59284,59286,59291,59293],{},[131,59279,2077],{},[131,59281,2082],{},[131,59283,3869],{},[131,59285,484],{},[131,59287,59288,59290],{},[32,59289,24249],{}," Aurora 적용 (49 테이블 + 75 파티션 라이브)",[131,59292,59273],{},[131,59294,59273],{},[107,59296,59297,59299,59301,59303,59305,59314,59316],{},[131,59298,2096],{},[131,59300,2101],{},[131,59302,3869],{},[131,59304,484],{},[131,59306,59307,51,59309,51,59311,59313],{},[32,59308,3987],{},[32,59310,21826],{},[32,59312,21839],{}," 등 + 표준 errors\u002Fpagination\u002Fauth\u002FDrizzle 스키마",[131,59315,59273],{},[131,59317,59273],{},[107,59319,59320,59322,59324,59326,59328,59340,59342],{},[131,59321,2115],{},[131,59323,2120],{},[131,59325,3869],{},[131,59327,484],{},[131,59329,59330,59334,59335,6219,59337,59339],{},[26,59331,16900],{"href":59332,"rel":59333},"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002Fdoc",[30]," — paths 37 \u002F schemas 45+, 루트 ",[32,59336,4361],{},[32,59338,16900],{}," 302",[131,59341,59114],{},[131,59343,59114],{},[107,59345,59346,59348,59350,59352,59354,59362,59364],{},[131,59347,2134],{},[131,59349,2139],{},[131,59351,3869],{},[131,59353,484],{},[131,59355,59356,59357,6219,59359,59361],{},"Phase 1·2·3 — ",[32,59358,23258],{},[32,59360,3987],{},", JWT HS256, JWT_SECRET secret",[131,59363,59273],{},[131,59365,59273],{},[107,59367,59368,59370,59372,59374,59376,59379,59381],{},[131,59369,2153],{},[131,59371,2158],{},[131,59373,3869],{},[131,59375,484],{},[131,59377,59378],{},"발신정보 검증·옵트아웃 필터·크레딧 hold·트랜잭션. 채널 branching generic화",[131,59380,59114],{},[131,59382,59114],{},[107,59384,59385,59387,59389,59391,59393,59398,59400],{},[131,59386,2172],{},[131,59388,2177],{},[131,59390,3869],{},[131,59392,484],{},[131,59394,59395,59397],{},[32,59396,23837],{}," race-free 패턴",[131,59399,59114],{},[131,59401,59114],{},[107,59403,59404,59406,59408,59410,59412,59418,59420],{},[131,59405,2191],{},[131,59407,2196],{},[131,59409,3869],{},[131,59411,484],{},[131,59413,59414,59417],{},[32,59415,59416],{},"src\u002Fadapters\u002Fnhn\u002F{sms,email,kakao,push,rcs}.ts"," + types",[131,59419,59114],{},[131,59421,59114],{},[107,59423,59424,59426,59428,59430,59432,59438,59440],{},[131,59425,2210],{},[131,59427,2215],{},[131,59429,3869],{},[131,59431,484],{},[131,59433,59434,4339,59436,25310],{},[32,59435,24037],{},[32,59437,20261],{},[131,59439,59114],{},[131,59441,59114],{},[107,59443,59444,59446,59449,59451,59453,59459,59462],{},[131,59445,2229],{},[131,59447,59448],{},"NHN Webhook 핸들러 (SMS·RCS)",[131,59450,57719],{},[131,59452,484],{},[131,59454,59455,59458],{},[32,59456,59457],{},"POST \u002Fwebhooks\u002Fnhn\u002F{sms,rcs}"," — HMAC-SHA256 + dedup_key. Email\u002FKakao\u002FPush 미",[131,59460,59461],{},"6\u002F9",[131,59463,322],{},[107,59465,59466,59468,59470,59472,59474,59482,59485],{},[131,59467,2249],{},[131,59469,2254],{},[131,59471,57719],{},[131,59473,484],{},[131,59475,59476,59478,59479,59481],{},[32,59477,24543],{}," ✅ DDL 적용 + ",[32,59480,23355],{}," CRUD ✅ 라이브 검증 (POST 201, GET 200). 처리 worker + R2 미",[131,59483,59484],{},"6\u002F17",[131,59486,322],{},[107,59488,59489,59491,59493,59495,59497,59508,59511],{},[131,59490,2269],{},[131,59492,2274],{},[131,59494,57719],{},[131,59496,484],{},[131,59498,59499,59501,59502,59504,59505,59507],{},[32,59500,24549],{}," ✅ DDL 적용 (FK 6 포함) + ",[32,59503,24552],{}," CRUD ✅ 라이브 검증 (POST 201, GET 200). 실행 엔진(",[32,59506,26315],{},") 미",[131,59509,59510],{},"6\u002F22",[131,59512,322],{},[107,59514,59515,59517,59520,59522,59524,59526,59529],{},[131,59516,2289],{},[131,59518,59519],{},"캠페인 API (스케줄러·시뮬레이션·테스트 발송)",[131,59521,3931],{},[131,59523,484],{},[131,59525,322],{},[131,59527,59528],{},"6\u002F25",[131,59530,322],{},[107,59532,59533,59535,59537,59539,59541,59554,59556],{},[131,59534,2308],{},[131,59536,2313],{},[131,59538,3931],{},[131,59540,484],{},[131,59542,59543,59545,59546,59548,59549,4361,59551,59553],{},[21,59544,42686],{}," (6\u002F4). ",[32,59547,43521],{}," 신규 작성 + ",[32,59550,43524],{},[32,59552,43527],{}," secret + 콜백 webhook 예정",[131,59555,58822],{},[131,59557,322],{},[107,59559,59560,59562,59564,59566,59568,59571,59574],{},[131,59561,2330],{},[131,59563,2335],{},[131,59565,3931],{},[131,59567,484],{},[131,59569,59570],{},"제공자 미정 (Anthropic \u002F OpenAI)",[131,59572,59573],{},"7\u002F1",[131,59575,322],{},[107,59577,59578,59580,59582,59584,59586,59589,59591],{},[131,59579,2350],{},[131,59581,2355],{},[131,59583,57719],{},[131,59585,484],{},[131,59587,59588],{},"6\u002F4 §6. Notification Hub OAuth2(client_credentials → Bearer) 어댑터 재작성 완료. SMS·Email 라우트 활성화. envelope 암호화·테넌트별 자격증명은 후속",[131,59590,58775],{},[131,59592,322],{},[107,59594,59595,59597,59603,59605,59607,59617,59620],{},[131,59596,2393],{},[131,59598,59599,59600,59602],{},"계약·서류 R2 라우트 (",[32,59601,45813],{},") + FILES 바인딩",[131,59604,3869],{},[131,59606,484],{},[131,59608,59609,59610,4339,59612,4361,59614,59616],{},"5 라우트(list\u002Fsign\u002Ffiles list\u002Fupload\u002Fdownload\u002Fdelete) + R2 bucket ",[32,59611,35819],{},[32,59613,35831],{},[32,59615,35778],{}," schema.ts + signup auto-create + reviewing 자동 전이 + lazy backfill (6\u002F2 §11·§12·§13)",[131,59618,59619],{},"6\u002F2",[131,59621,59619],{},[107,59623,59624,59626,59628,59630,59632,59635,59638],{},[131,59625,2413],{},[131,59627,2418],{},[131,59629,3869],{},[131,59631,484],{},[131,59633,59634],{},"6\u002F1 §5 + 6\u002F2 §16 + 6\u002F4. mock 모드 — 자격증명 등록 후 콘솔 IP 정책 1007(Workers outbound IPv6 vs NICE 콘솔 IPv4) 미해결로 mock 유지",[131,59636,59637],{},"6\u002F1",[131,59639,59637],{},[107,59641,59642,59644,59646,59648,59650,59656,59659],{},[131,59643,2433],{},[131,59645,2438],{},[131,59647,3869],{},[131,59649,484],{},[131,59651,59652,59653,59655],{},"6\u002F4 §5. DB 미사용 — R2 단일 JSON(",[32,59654,58],{},", FILES 바인딩) 시드 142 task. GET 공개 + PATCH 인증 2 라우트. last-write-wins",[131,59657,59658],{},"6\u002F4",[131,59660,59658],{},[107,59662,59663,59665,59670,59672,59674,59680,59682],{},[131,59664,2453],{},[131,59666,59667,59669],{},[32,59668,39621],{}," — 서비스 담당자 이메일 변경",[131,59671,3869],{},[131,59673,484],{},[131,59675,59676,59677,59679],{},"6\u002F4. 비밀번호 + OTP(",[32,59678,42347],{},") + email-only UPDATE (loginid 가입 시 식별자로 고정 유지). 라이브 e2e 5 시나리오 통과",[131,59681,59658],{},[131,59683,59658],{},[107,59685,59686,59688,59691,59693,59695,59724,59726],{},[131,59687,2472],{},[131,59689,59690],{},"NHN Notification Hub 어댑터 신규 (OAuth + Bearer)",[131,59692,3869],{},[131,59694,484],{},[131,59696,59697,59698,59701,59702,4361,59704,59706,59707,7086,59710,4361,59713,4473,59716,4339,59718,5439,59721,59723],{},"6\u002F4 §6. ",[32,59699,59700],{},"adapters\u002Fnhn\u002Foauth.ts","(토큰 발급+캐시) + ",[32,59703,25109],{},[32,59705,42328],{}," 재작성(POST ",[32,59708,59709],{},"\u002Fmessage\u002Fv1.0\u002F{SMS|EMAIL}\u002Ffree-form-messages\u002F{purpose}",[32,59711,59712],{},"contactType=PHONE_NUMBER",[32,59714,59715],{},"EMAIL_ADDRESS",[32,59717,42412],{},[32,59719,59720],{},"X-NHN-Authorization",[32,59722,25095],{}," 확장(userAccessKey\u002FsecretAccessKey + legacy secretKey 옵셔널)",[131,59725,59658],{},[131,59727,59658],{},[76,59729,59731,59732,6121,59734],{"id":59730},"_5-3-사용자단-malgn-noti-3-트랙으로-분리","5-3. 사용자단 (",[32,59733,7664],{},[21,59735,59736],{},"3 트랙으로 분리",[15,59738,59739,59746,59770],{},[18,59740,59741,59742,59745],{},"⚠️ 2026-06-02 재구성. 그동안 5-3은 ",[21,59743,59744],{},"화면(UI)만 완료된 상태도 ✅로 표기","되어 \"실제로는 안 했는데 완료처럼 보이는\" 문제가 있었다. 이를 명확히 하기 위해 한 도메인을 세 트랙으로 나눈다:",[81,59747,59748,59755,59763],{},[84,59749,59750,59752,59753],{},[21,59751,30229],{}," (목업 데이터로 페이지 그리기) — ",[32,59754,30234],{},[84,59756,59757,89,59760,59762],{},[21,59758,59759],{},"B. 백엔드 API 엔드포인트",[32,59761,30252],{},"에 이미 정의됨 (5-2를 도메인 단위로 재정렬한 결과는 5-3M 매트릭스 참조)",[84,59764,59765,59767,59768],{},[21,59766,30266],{}," (실 데이터 흐름 + 상태 관리 + 에러 처리) — ",[32,59769,30271],{},[18,59771,59772],{},"✅의 의미가 트랙마다 다르다는 점에 주의. 5-3A의 ✅는 \"UI 화면이 목업으로 그려짐\" 단계까지를 의미.",[237,59774,59776],{"id":59775},"_5-3a-화면-ui-구성-목업-데이터-기준","5-3A. 화면 UI 구성 (목업 데이터 기준)",[101,59778,59779,59793],{},[104,59780,59781],{},[107,59782,59783,59785,59787,59789,59791],{},[110,59784,264],{},[110,59786,267],{},[110,59788,7757],{},[110,59790,273],{},[110,59792,57667],{},[126,59794,59795,59818,59835,59853,59869,59887,59903,59921,59942,59963,59981,59995,60013,60029,60043],{},[107,59796,59797,59800,59802,59804,59806],{},[131,59798,59799],{},"5-3A-1",[131,59801,2520],{},[131,59803,3869],{},[131,59805,484],{},[131,59807,59808,51,59810,51,59812,51,59814,51,59816],{},[32,59809,9335],{},[32,59811,17881],{},[32,59813,17738],{},[32,59815,18318],{},[32,59817,17640],{},[107,59819,59820,59823,59826,59828,59830],{},[131,59821,59822],{},"5-3A-2",[131,59824,59825],{},"발송 — 6채널 (SMS\u002FRCS\u002FKakao\u002FEmail\u002FPush\u002FFlow)",[131,59827,3869],{},[131,59829,484],{},[131,59831,59832,59834],{},[32,59833,34052],{}," + PU 풀세트",[107,59836,59837,59840,59843,59845,59847],{},[131,59838,59839],{},"5-3A-3",[131,59841,59842],{},"이력 \u002F 통계 (5채널 + 대시보드)",[131,59844,3869],{},[131,59846,484],{},[131,59848,59849,59852],{},[32,59850,59851],{},"\u002Fhistory\u002F{sms,rcs,kakao,email,push,stats}"," + 비동기 다운로드 요청 UI",[107,59854,59855,59858,59860,59862,59864],{},[131,59856,59857],{},"5-3A-4",[131,59859,2579],{},[131,59861,3869],{},[131,59863,484],{},[131,59865,59866],{},[32,59867,59868],{},"\u002Fcontacts\u002F{list,groups,optout}",[107,59870,59871,59874,59877,59879,59881],{},[131,59872,59873],{},"5-3A-5",[131,59875,59876],{},"발신 정보 — 6종",[131,59878,3869],{},[131,59880,484],{},[131,59882,59883,59886],{},[32,59884,59885],{},"\u002Fsender\u002F{numbers,brands,domains,push-cert,profiles,optout-080}"," + 등록 마법사",[107,59888,59889,59892,59894,59896,59898],{},[131,59890,59891],{},"5-3A-6",[131,59893,2617],{},[131,59895,3869],{},[131,59897,484],{},[131,59899,59900],{},[32,59901,59902],{},"\u002Fmanage\u002F{sms,rcs,kakao,email,push,settings}",[107,59904,59905,59908,59911,59913,59915],{},[131,59906,59907],{},"5-3A-7",[131,59909,59910],{},"캠페인 — 본안 + 변형 v3",[131,59912,3869],{},[131,59914,484],{},[131,59916,59917,51,59919],{},[32,59918,17204],{},[32,59920,11618],{},[107,59922,59923,59926,59929,59931,59933],{},[131,59924,59925],{},"5-3A-8",[131,59927,59928],{},"크레딧 \u002F 결제 — 충전·결과·내역·영수증·카드",[131,59930,3869],{},[131,59932,484],{},[131,59934,59935,51,59937,51,59939],{},[32,59936,18609],{},[32,59938,18621],{},[32,59940,59941],{},"\u002Faccount\u002F{credit,cards}",[107,59943,59944,59947,59949,59951,59953],{},[131,59945,59946],{},"5-3A-9",[131,59948,2674],{},[131,59950,3869],{},[131,59952,484],{},[131,59954,59955,51,59957,51,59960],{},[32,59956,18473],{},[32,59958,59959],{},"\u002Finquiry\u002Fcomplete",[32,59961,59962],{},"\u002Faccount\u002Finquiries(\u002Fdetail)",[107,59964,59965,59968,59970,59972,59974],{},[131,59966,59967],{},"5-3A-10",[131,59969,2693],{},[131,59971,3869],{},[131,59973,484],{},[131,59975,59976,59978,59979],{},[32,59977,5699],{}," 9종 + ",[32,59980,5836],{},[107,59982,59983,59986,59988,59990,59992],{},[131,59984,59985],{},"5-3A-11",[131,59987,2712],{},[131,59989,3869],{},[131,59991,484],{},[131,59993,59994],{},"목록 · 기본형\u002F확장형 등록 폼 · 미리보기",[107,59996,59997,60000,60002,60004,60006],{},[131,59998,59999],{},"5-3A-12",[131,60001,2731],{},[131,60003,3869],{},[131,60005,484],{},[131,60007,60008,60010,60011],{},[32,60009,4361],{},"(공개) + ",[32,60012,5744],{},[107,60014,60015,60018,60020,60022,60024],{},[131,60016,60017],{},"5-3A-13",[131,60019,2750],{},[131,60021,3869],{},[131,60023,484],{},[131,60025,60026,60028],{},[32,60027,4457],{}," — 18+ 섹션",[107,60030,60031,60034,60036,60038,60040],{},[131,60032,60033],{},"5-3A-14",[131,60035,2768],{},[131,60037,57719],{},[131,60039,484],{},[131,60041,60042],{},"단독 일부 라이브. 점검 \u002F 네트워크 \u002F 인증 메일 템플릿 미",[107,60044,60045,60048,60053,60055,60057],{},[131,60046,60047],{},"5-3A-15",[131,60049,60050,60052],{},[32,60051,26863],{}," 페이지 — R2 정본 비동기 로드 + 인라인 편집 모달",[131,60054,3869],{},[131,60056,484],{},[131,60058,60059,60060,5439,60063,60065],{},"6\u002F4 §5. 임베디드 STAGES 제거 → top-level await ",[32,60061,60062],{},"api(\u002Fwbs)",[32,60064,4476],{}," 편집 다이얼로그(owner·note·href·targetDate·completionDate). 비로그인 읽기 전용 + \"로그인하면 편집 가능\" 힌트",[237,60067,60069],{"id":60068},"_5-3m-화면-api-연동-매트릭스-3-트랙-한눈에","5-3M. 화면 ↔ API 연동 매트릭스 (3 트랙 한눈에)",[15,60071,60072],{},[18,60073,60074,60075,60078],{},"각 도메인의 진척을 ",[21,60076,60077],{},"UI \u002F API \u002F 연동"," 3 트랙으로 분리해 한 행에. ✅=완료, 🟢=진행 중·부분, ⚪=미.",[101,60080,60081,60102],{},[104,60082,60083],{},[107,60084,60085,60088,60091,60094,60100],{},[110,60086,60087],{},"도메인",[110,60089,60090],{},"UI (5-3A)",[110,60092,60093],{},"API 엔드포인트 (5-2)",[110,60095,60096,60099],{},[21,60097,60098],{},"연동"," (5-3C)",[110,60101,7718],{},[126,60103,60104,60129,60147,60165,60183,60200,60218,60240,60256,60274,60295,60323,60343,60358,60378,60394,60422,60439,60457,60474,60493,60510,60530,60548,60569,60586],{},[107,60105,60106,60111,60113,60124,60126],{},[131,60107,60108,60110],{},[21,60109,3979],{}," (로그인\u002F가입\u002Fme)",[131,60112,3869],{},[131,60114,60115,60116,4536,60118,4536,60120,4536,60122,4343],{},"✅ (",[32,60117,23258],{},[32,60119,23261],{},[32,60121,3987],{},[32,60123,45642],{},[131,60125,3869],{},[131,60127,60128],{},"6\u002F1 §4·§5 완료. JWT 쿠키 + 부트스트랩 플러그인. signup OTP 인증 실 API 연동.",[107,60130,60131,60136,60138,60142,60144],{},[131,60132,60133],{},[21,60134,60135],{},"발송 SMS\u002FLMS\u002FMMS",[131,60137,3869],{},[131,60139,60115,60140,4343],{},[32,60141,23428],{},[131,60143,3931],{},[131,60145,60146],{},"producer 라이브, 실 NHN 자격증명 미 → mock fallback",[107,60148,60149,60154,60156,60161,60163],{},[131,60150,60151],{},[21,60152,60153],{},"발송 RCS",[131,60155,3869],{},[131,60157,60115,60158,4343],{},[32,60159,60160],{},"POST \u002Fsend\u002Frcs",[131,60162,3931],{},[131,60164],{},[107,60166,60167,60172,60174,60179,60181],{},[131,60168,60169],{},[21,60170,60171],{},"발송 알림톡\u002F친구톡",[131,60173,3869],{},[131,60175,60115,60176,4343],{},[32,60177,60178],{},"POST \u002Fsend\u002Fkakao",[131,60180,3931],{},[131,60182],{},[107,60184,60185,60190,60192,60196,60198],{},[131,60186,60187],{},[21,60188,60189],{},"발송 Email",[131,60191,3869],{},[131,60193,60115,60194,4343],{},[32,60195,42473],{},[131,60197,3931],{},[131,60199],{},[107,60201,60202,60207,60209,60214,60216],{},[131,60203,60204],{},[21,60205,60206],{},"발송 Push",[131,60208,3869],{},[131,60210,60115,60211,4343],{},[32,60212,60213],{},"POST \u002Fsend\u002Fpush",[131,60215,3931],{},[131,60217],{},[107,60219,60220,60225,60227,60236,60238],{},[131,60221,60222],{},[21,60223,60224],{},"발송 Flow",[131,60226,3869],{},[131,60228,60229,60230,60232,60233,60235],{},"🟢 (",[32,60231,28021],{}," CRUD ✅, ",[32,60234,26315],{}," 실행 엔진 미)",[131,60237,3931],{},[131,60239],{},[107,60241,60242,60247,60249,60252,60254],{},[131,60243,60244],{},[21,60245,60246],{},"이력\u002F통계",[131,60248,3869],{},[131,60250,60251],{},"🟢 (목록 라우트 부분, 통계 라우트 미)",[131,60253,3931],{},[131,60255],{},[107,60257,60258,60263,60265,60270,60272],{},[131,60259,60260],{},[21,60261,60262],{},"다운로드 요청 (Export)",[131,60264,3869],{},[131,60266,60229,60267,60269],{},[32,60268,23355],{}," CRUD ✅, 처리 worker + R2 미)",[131,60271,3931],{},[131,60273],{},[107,60275,60276,60281,60283,60291,60293],{},[131,60277,60278,60280],{},[21,60279,14250],{}," (연락처\u002F그룹\u002F수신거부)",[131,60282,3869],{},[131,60284,60115,60285,4536,60287,4536,60289,4343],{},[32,60286,21826],{},[32,60288,21839],{},[32,60290,22056],{},[131,60292,3931],{},[131,60294],{},[107,60296,60297,60303,60305,60319,60321],{},[131,60298,60299,60302],{},[21,60300,60301],{},"발신 정보"," (6종)",[131,60304,3869],{},[131,60306,60115,60307,4536,60309,4536,60311,4536,60313,4536,60315,4536,60317,4343],{},[32,60308,21849],{},[32,60310,22571],{},[32,60312,22584],{},[32,60314,22596],{},[32,60316,22620],{},[32,60318,22632],{},[131,60320,3931],{},[131,60322],{},[107,60324,60325,60331,60333,60339,60341],{},[131,60326,60327,60330],{},[21,60328,60329],{},"템플릿"," (5채널 + settings)",[131,60332,3869],{},[131,60334,60115,60335,4536,60337,4343],{},[32,60336,22059],{},[32,60338,22693],{},[131,60340,3931],{},[131,60342],{},[107,60344,60345,60349,60351,60354,60356],{},[131,60346,60347],{},[21,60348,17201],{},[131,60350,3869],{},[131,60352,60353],{},"⚪ (스케줄러·시뮬레이션·테스트)",[131,60355,3931],{},[131,60357],{},[107,60359,60360,60364,60366,60374,60376],{},[131,60361,60362],{},[21,60363,4047],{},[131,60365,3869],{},[131,60367,60229,60368,60370,60371,60373],{},[32,60369,22730],{}," 읽기, ",[32,60372,22706],{}," 일부, PG 어댑터 미)",[131,60375,3931],{},[131,60377],{},[107,60379,60380,60384,60386,60390,60392],{},[131,60381,60382],{},[21,60383,4066],{},[131,60385,3869],{},[131,60387,60115,60388,4343],{},[32,60389,4073],{},[131,60391,3931],{},[131,60393],{},[107,60395,60396,60401,60403,60413,60415],{},[131,60397,60398,60400],{},[21,60399,18522],{}," — 회원 정보 변경",[131,60402,3869],{},[131,60404,60115,60405,4420,60407,4420,60409,4420,60411,4343],{},[32,60406,21820],{},[32,60408,32697],{},[32,60410,32700],{},[32,60412,39621],{},[131,60414,3869],{},[131,60416,60417,60418,60421],{},"6\u002F2 §6 + 6\u002F4. 서비스 담당자 이메일 변경(OTP + 비밀번호 검증, ",[21,60419,60420],{},"loginid 유지",") 실 API 연동",[107,60423,60424,60428,60430,60435,60437],{},[131,60425,60426,48593],{},[21,60427,18522],{},[131,60429,3869],{},[131,60431,60432,60433,4343],{},"⚪ (",[32,60434,45834],{},[131,60436,3931],{},[131,60438],{},[107,60440,60441,60446,60448,60452,60454],{},[131,60442,60443,60445],{},[21,60444,18522],{}," — 보안로그인 (2FA)",[131,60447,3869],{},[131,60449,60432,60450,4343],{},[32,60451,45855],{},[131,60453,3931],{},[131,60455,60456],{},"TB_VERIFICATION 재사용",[107,60458,60459,60464,60466,60470,60472],{},[131,60460,60461,60463],{},[21,60462,18522],{}," — 멀티 계정 (담당자 초대)",[131,60465,3869],{},[131,60467,60432,60468,4343],{},[32,60469,29403],{},[131,60471,3931],{},[131,60473],{},[107,60475,60476,60481,60483,60488,60490],{},[131,60477,60478,60480],{},[21,60479,18522],{}," — 계약 관리",[131,60482,3869],{},[131,60484,60115,60485,60487],{},[32,60486,45813],{}," 5 라우트 + R2)",[131,60489,3869],{},[131,60491,60492],{},"6\u002F2 §11~§15. 이용계약·서명·R2 업로드·미리보기·삭제·휴대폰 본인인증 모두 실 API. 운영자 승인 화면만 미",[107,60494,60495,60499,60501,60506,60508],{},[131,60496,60497],{},[21,60498,17628],{},[131,60500,3869],{},[131,60502,60432,60503,60505],{},[32,60504,46914],{}," — OTP 인프라 재활용)",[131,60507,3931],{},[131,60509],{},[107,60511,60512,60516,60519,60525,60527],{},[131,60513,60514],{},[21,60515,48319],{},[131,60517,60518],{},"(GNB 데모 토글)",[131,60520,60521,60522,60524],{},"(클라이언트 쿠키 삭제만, ",[32,60523,47444],{}," 미)",[131,60526,3931],{},[131,60528,60529],{},"GNB 미연동",[107,60531,60532,60536,60539,60543,60545],{},[131,60533,60534],{},[21,60535,48366],{},[131,60537,60538],{},"(Step 3 화면용 체크박스)",[131,60540,60432,60541,4343],{},[32,60542,46930],{},[131,60544,3931],{},[131,60546,60547],{},"TB_TERMS·TB_TERMS_AGREEMENT",[107,60549,60550,60555,60558,60564,60566],{},[131,60551,60552],{},[21,60553,60554],{},"사업자등록증 심사 승인 게이트",[131,60556,60557],{},"✅ (배너 + 미들웨어)",[131,60559,60560,60561,60563],{},"✅ (DB 4단계 + ",[32,60562,34718],{}," 18 라우트 + reviewing 자동 전이)",[131,60565,3869],{},[131,60567,60568],{},"6\u002F2 §7~§10·§12·§13·§14. 운영자단 승인 UI만 미",[107,60570,60571,60575,60577,60582,60584],{},[131,60572,60573],{},[21,60574,2712],{},[131,60576,3869],{},[131,60578,60432,60579,60581],{},[32,60580,22718],{}," 부분)",[131,60583,3931],{},[131,60585],{},[107,60587,60588,60593,60595,60598,60600],{},[131,60589,60590],{},[21,60591,60592],{},"시스템 페이지",[131,60594,57719],{},[131,60596,60597],{},"(정적)",[131,60599,322],{},[131,60601],{},[18,60603,60604,60607],{},[21,60605,60606],{},"진척 합계"," (트랙별, 2026-06-04 기준):",[81,60609,60610,60620,60630],{},[84,60611,60612,60613,60616,60617,60619],{},"UI(5-3A) — 14 ✅ + 1 🟢 = ",[21,60614,60615],{},"거의 완료"," (6\u002F4 신규 5-3A-15 ",[32,60618,26863],{}," 페이지 R2 비동기 로드 + 인라인 편집)",[84,60621,60622,60623,60626,60627,60629],{},"API(5-2) — 13 ✅ + 2 🟢 + 3 ⚪ = ",[21,60624,60625],{},"약 72%"," (6\u002F4 신규 5-2-19 WBS R2 + 5-2-20 ",[32,60628,39656],{}," + 5-2-21 NHN Notification Hub 어댑터)",[84,60631,60632,60635],{},[21,60633,60634],{},"연동(5-3C) — 10 ✅ + 2 🟢 + 8 ⚪ = 약 40%"," (인증·계정 \u002F 이메일 OTP \u002F login-by-email \u002F companyType \u002F 회원정보 \u002F 계약 관리 \u002F 승인 게이트 \u002F 계약 서명 본인인증 \u002F 사업자등록증 reviewing 자동전이 \u002F 서비스 담당자 이메일 변경)",[237,60637,60639],{"id":60638},"_5-3c-화면-api-연동-개별-작업-항목-진행-중","5-3C. 화면 ↔ API 연동 (개별 작업 항목, 진행 중)",[101,60641,60642,60656],{},[104,60643,60644],{},[107,60645,60646,60648,60650,60652,60654],{},[110,60647,264],{},[110,60649,267],{},[110,60651,270],{},[110,60653,273],{},[110,60655,57667],{},[126,60657,60658,60678,60697,60713,60731,60747,60762,60778,60803,60819,60836,60853,60875,60889,60902,60915,60928,60944,60964,60977,60995],{},[107,60659,60660,60662,60669,60671,60673],{},[131,60661,2826],{},[131,60663,60664,60665,4339,60667,4343],{},"인증·계정 (",[32,60666,30131],{},[32,60668,3987],{},[131,60670,3869],{},[131,60672,484],{},[131,60674,60675,60676],{},"6\u002F1 §4 완료. JWT 쿠키 + 부트스트랩 플러그인 + ",[32,60677,28742],{},[107,60679,60680,60682,60687,60689,60691],{},[131,60681,2845],{},[131,60683,60684,60685,4343],{},"이메일 OTP (",[32,60686,45642],{},[131,60688,3869],{},[131,60690,484],{},[131,60692,60693,60694,60696],{},"6\u002F1 §5 완료. ",[32,60695,14163],{}," Step 3에서 실 API 호출",[107,60698,60699,60701,60704,60706,60708],{},[131,60700,2864],{},[131,60702,60703],{},"로그아웃 — GNB 실 연결",[131,60705,3931],{},[131,60707,484],{},[131,60709,60710,60712],{},[32,60711,29424],{}," 호출로 GNB 데모 토글 교체",[107,60714,60715,60717,60720,60722,60724],{},[131,60716,2884],{},[131,60718,60719],{},"비밀번호 재설정 — OTP 인프라 재활용",[131,60721,3931],{},[131,60723,484],{},[131,60725,60726,4339,60728,60730],{},[32,60727,48518],{},[32,60729,46914],{}," 신설",[107,60732,60733,60735,60740,60742,60744],{},[131,60734,2904],{},[131,60736,60737,60739],{},[32,60738,30107],{}," — companyId UX 개선",[131,60741,3869],{},[131,60743,484],{},[131,60745,60746],{},"6\u002F2 §2. 고객사 ID 필드 제거",[107,60748,60749,60751,60756,60758,60760],{},[131,60750,2923],{},[131,60752,60753,60754,4343],{},"약관 동의 적재 (",[32,60755,46930],{},[131,60757,3931],{},[131,60759,484],{},[131,60761,28079],{},[107,60763,60764,60766,60771,60773,60775],{},[131,60765,2942],{},[131,60767,60768,60770],{},[32,60769,30486],{}," 전달·저장 + 화면 분기",[131,60772,57719],{},[131,60774,484],{},[131,60776,60777],{},"6\u002F2 §7. signup 전달·저장 ✅. 개인 메뉴 미노출 분기 미",[107,60779,60780,60782,60788,60790,60792],{},[131,60781,2981],{},[131,60783,60784,4339,60786,55406],{},[32,60785,32697],{},[32,60787,3991],{},[131,60789,3869],{},[131,60791,484],{},[131,60793,60794,60795,4536,60797,4536,60800,60802],{},"6\u002F2 §6 + 6\u002F4. ",[32,60796,32697],{},[32,60798,60799],{},"\u002Fme\u002Fcompany",[32,60801,39621],{},". 서비스 담당자 이메일 변경(loginid 유지·email만)·결제 이메일 변경·광고수신 토글 모두 실 API. 비밀번호 변경은 5-3C-8 별도",[107,60804,60805,60807,60813,60815,60817],{},[131,60806,3000],{},[131,60808,60809,4339,60811,55437],{},[32,60810,45834],{},[32,60812,45821],{},[131,60814,3931],{},[131,60816,484],{},[131,60818],{},[107,60820,60821,60823,60830,60832,60834],{},[131,60822,3019],{},[131,60824,60825,60827,60828],{},[32,60826,19452],{}," (2FA) + ",[32,60829,45855],{},[131,60831,3931],{},[131,60833,484],{},[131,60835,60456],{},[107,60837,60838,60840,60846,60848,60850],{},[131,60839,3038],{},[131,60841,60842,4339,60844],{},[32,60843,19455],{},[32,60845,29403],{},[131,60847,3931],{},[131,60849,484],{},[131,60851,60852],{},"초대 토큰·수락 흐름",[107,60854,60855,60857,60865,60867,60869],{},[131,60856,3057],{},[131,60858,60859,4339,60861,60864],{},[32,60860,19458],{},[32,60862,60863],{},"\u002Fcontracts\u002F*\u002Ffiles"," (R2 업로드)",[131,60866,3869],{},[131,60868,484],{},[131,60870,60871,60872,60874],{},"6\u002F2 §11~§15. ",[32,60873,45813],{}," 5 라우트 + R2 + 미리보기·삭제·휴대폰 본인인증 서명 + 사업자등록증 자동 reviewing 전이",[107,60876,60877,60879,60882,60884,60886],{},[131,60878,3076],{},[131,60880,60881],{},"발송 6채널 — UI에 실 API 호출 (Idempotency-Key 헤더 포함)",[131,60883,3931],{},[131,60885,484],{},[131,60887,60888],{},"NHN Notification Hub 자격증명 + 어댑터 재작성 필요 (6\u002F2 §16)",[107,60890,60891,60893,60895,60897,60899],{},[131,60892,3096],{},[131,60894,3101],{},[131,60896,3931],{},[131,60898,484],{},[131,60900,60901],{},"API 일부 미구현이라 5-2 동시 진행 필요",[107,60903,60904,60906,60909,60911,60913],{},[131,60905,3116],{},[131,60907,60908],{},"주소록·발신 정보·템플릿 — CRUD 연동 (API 모두 ✅이므로 프런트 작업)",[131,60910,3931],{},[131,60912,484],{},[131,60914],{},[107,60916,60917,60919,60922,60924,60926],{},[131,60918,3134],{},[131,60920,60921],{},"크레딧·결제 — 충전 흐름은 PG 어댑터 미정 (블로커)",[131,60923,3931],{},[131,60925,484],{},[131,60927],{},[107,60929,60930,60932,60938,60940,60942],{},[131,60931,3153],{},[131,60933,60934,60935,60937],{},"문의 — ",[32,60936,4073],{}," 연동",[131,60939,3931],{},[131,60941,484],{},[131,60943],{},[107,60945,60946,60948,60951,60953,60955],{},[131,60947,2962],{},[131,60949,60950],{},"사업자등록증 심사 승인 게이트 (DB + 미들웨어 + 배너 + 라우트 가드)",[131,60952,3869],{},[131,60954,484],{},[131,60956,60957,60958,60960,60961,60963],{},"6\u002F2 §7~§10·§12·§13·§14. 4단계 enum(pending\u002Freviewing\u002Fapproved\u002Frejected) + ",[32,60959,34718],{}," 18 라우트 + ",[32,60962,34887],{}," 3분기 + lazy backfill",[107,60965,60966,60968,60970,60972,60974],{},[131,60967,3171],{},[131,60969,3176],{},[131,60971,3869],{},[131,60973,484],{},[131,60975,60976],{},"6\u002F2 §12·§14",[107,60978,60979,60981,60988,60990,60992],{},[131,60980,3190],{},[131,60982,60983,60984,54260,60986,4343],{},"계약서 전자서명 다이얼로그 — 휴대폰 본인인증 sub-step (",[32,60985,54259],{},[32,60987,38570],{},[131,60989,3869],{},[131,60991,484],{},[131,60993,60994],{},"6\u002F2 §15. 공인인증서 탭 제거 + dialog open 시 fetchMe 강제 hydrate",[107,60996,60997,60999,61001,61003,61005],{},[131,60998,3209],{},[131,61000,3214],{},[131,61002,3869],{},[131,61004,484],{},[131,61006,61007,61008,51,61010,4361,61012,13746,61014,6100,61016,61018,61019,61022,61023,6219,61026,5439,61028],{},"6\u002F4. ",[32,61009,18591],{},[32,61011,42527],{},[32,61013,42530],{},[32,61015,42533],{},[32,61017,42347],{},")로 교체. confirm payload ",[32,61020,61021],{},"{newEmail,code,password}",". auth store ",[32,61024,61025],{},"changeEmail()",[32,61027,39621],{},[21,61029,61030],{},"결제 이메일 변경은 기존 흐름 유지",[76,61032,61034,61035,61037],{"id":61033},"_5-4-관리자단-malgn-noti-admin-화면-개발","5-4. 관리자단 (",[32,61036,7672],{},") 화면 개발",[101,61039,61040,61054],{},[104,61041,61042],{},[107,61043,61044,61046,61048,61050,61052],{},[110,61045,264],{},[110,61047,267],{},[110,61049,270],{},[110,61051,273],{},[110,61053,57667],{},[126,61055,61056,61069,61085,61099,61112,61125,61137,61150,61162,61175,61189,61202,61215,61228,61244,61269],{},[107,61057,61058,61060,61062,61064,61066],{},[131,61059,3252],{},[131,61061,3257],{},[131,61063,3869],{},[131,61065,484],{},[131,61067,61068],{},"부트스트랩·라이브",[107,61070,61071,61073,61075,61077,61079],{},[131,61072,3271],{},[131,61074,3276],{},[131,61076,3869],{},[131,61078,484],{},[131,61080,61081,61084],{},[32,61082,61083],{},"doc\u002Fpages\u002F"," — P0: 14 \u002F P1: 13 \u002F P2: 5",[107,61086,61087,61089,61092,61094,61096],{},[131,61088,3290],{},[131,61090,61091],{},"회원·고객사 관리 (P0)",[131,61093,3931],{},[131,61095,484],{},[131,61097,61098],{},"회원 발송 이력 \u002F 고객사 결제 상세 \u002F 환불",[107,61100,61101,61103,61106,61108,61110],{},[131,61102,3308],{},[131,61104,61105],{},"시스템 관리 (P0) — 운영자 계정 \u002F RBAC \u002F 감사 로그",[131,61107,3931],{},[131,61109,484],{},[131,61111,322],{},[107,61113,61114,61116,61119,61121,61123],{},[131,61115,3327],{},[131,61117,61118],{},"요금 \u002F 단가 관리 (P0)",[131,61120,3931],{},[131,61122,484],{},[131,61124,322],{},[107,61126,61127,61129,61131,61133,61135],{},[131,61128,3345],{},[131,61130,3350],{},[131,61132,3931],{},[131,61134,484],{},[131,61136,58757],{},[107,61138,61139,61141,61143,61145,61147],{},[131,61140,3363],{},[131,61142,3368],{},[131,61144,3931],{},[131,61146,484],{},[131,61148,61149],{},"캠페인 제외",[107,61151,61152,61154,61156,61158,61160],{},[131,61153,3382],{},[131,61155,3387],{},[131,61157,3931],{},[131,61159,484],{},[131,61161,322],{},[107,61163,61164,61166,61169,61171,61173],{},[131,61165,3401],{},[131,61167,61168],{},"결제 \u002F 크레딧 관리 + 고객사 상세 결제 탭 (P0)",[131,61170,3931],{},[131,61172,484],{},[131,61174,322],{},[107,61176,61177,61179,61182,61184,61186],{},[131,61178,3420],{},[131,61180,61181],{},"템플릿 검수·관리 (P0)",[131,61183,3931],{},[131,61185,484],{},[131,61187,61188],{},"샘플·AI 템플릿 정책 제외",[107,61190,61191,61193,61196,61198,61200],{},[131,61192,3438],{},[131,61194,61195],{},"수신거부(운영) (P1)",[131,61197,3931],{},[131,61199,484],{},[131,61201,322],{},[107,61203,61204,61206,61209,61211,61213],{},[131,61205,3456],{},[131,61207,61208],{},"통계 \u002F 리포트 + 대시보드 (P2)",[131,61210,3931],{},[131,61212,484],{},[131,61214,322],{},[107,61216,61217,61219,61222,61224,61226],{},[131,61218,3475],{},[131,61220,61221],{},"콘텐츠 \u002F 사이트 관리 + 시스템 관리 + API 관리 (P2)",[131,61223,3931],{},[131,61225,484],{},[131,61227,322],{},[107,61229,61230,61232,61234,61236,61238],{},[131,61231,3493],{},[131,61233,3498],{},[131,61235,3869],{},[131,61237,484],{},[131,61239,61240,61241,61243],{},"6\u002F4 §3. ",[32,61242,39750],{},"(3,129줄 jsx) → Vue 1:1 포팅. 셸 완전 재정비 + 공유 컴포넌트 14종 + 차트 4종 + 17 페이지(대시보드·고객사·고객사 상세·계정·모니터링·발신번호·발신프로필·템플릿검수·결제·채널단가·충전쿠폰·1:1문의·FAQ·공지·통계·운영자·권한그룹·API). 18 라우트 라이브 200",[107,61245,61246,61248,61253,61255,61257],{},[131,61247,3512],{},[131,61249,61250,61251,4343],{},"페이지 진척 상태 라벨 (",[32,61252,42722],{},[131,61254,3869],{},[131,61256,484],{},[131,61258,61007,61259,61261,61262,61264,61265,61268],{},[32,61260,40867],{}," prop ",[32,61263,20679],{}," 3단계. 화면(neutral·flask)·일부 연동(warning·construction)·연동(미표시). 17 페이지 모두 ",[32,61266,61267],{},"dev=\"screen\"","으로 명시",[107,61270,61271,61273,61275,61277,61279],{},[131,61272,3531],{},[131,61274,3536],{},[131,61276,3869],{},[131,61278,484],{},[131,61280,61281,61282,61284,61285,61288],{},"6\u002F4. 기존 파랑 그라데이션 박스 폐기 → ",[32,61283,10089],{},"(말풍선+스파클) + \"맑은 message\" + ",[32,61286,61287],{},"primary-50"," 배경 \"관리자\" 배지",[76,61290,61292],{"id":61291},"_5-5-통합배포","5-5. 통합·배포",[101,61294,61295,61309],{},[104,61296,61297],{},[107,61298,61299,61301,61303,61305,61307],{},[110,61300,264],{},[110,61302,267],{},[110,61304,270],{},[110,61306,273],{},[110,61308,57667],{},[126,61310,61311,61325,61338,61355,61381,61400,61413,61433,61448,61460,61483,61503],{},[107,61312,61313,61315,61318,61320,61322],{},[131,61314,3574],{},[131,61316,61317],{},"사용자단 Cloudflare Pages 배포 #1~#80+ alias 다수",[131,61319,57719],{},[131,61321,484],{},[131,61323,61324],{},"매 마일스톤 직후 배포 (6\u002F4 누적 #80+ alias 다수)",[107,61326,61327,61329,61331,61333,61335],{},[131,61328,3593],{},[131,61330,3598],{},[131,61332,3869],{},[131,61334,484],{},[131,61336,61337],{},"정적 placeholder → 실 Nuxt 앱 (#1)",[107,61339,61340,61342,61345,61347,61349],{},[131,61341,3612],{},[131,61343,61344],{},"API Workers 배포 #1~#25+",[131,61346,57719],{},[131,61348,484],{},[131,61350,61351,61352],{},"6\u002F4 최신 Version ",[32,61353,61354],{},"1ca0446e-ed3f-4079-be5f-3407f4550ba7",[107,61356,61357,61359,61369,61371,61373],{},[131,61358,3631],{},[131,61360,61361,61362,6247,61365,61368],{},"DDL — ",[32,61363,61364],{},"0001",[32,61366,61367],{},"0005"," 라이브 적용",[131,61370,3869],{},[131,61372,484],{},[131,61374,61375,61376,4361,61378,61380],{},"0001 idempotency \u002F 0002 export_flow \u002F 0003 loginid global unique \u002F 0004 nice_auth \u002F 0005 company_approval. ",[32,61377,35831],{},[32,61379,35778],{},"은 §11에서 schema.ts 정의(라이브에 이미 존재)",[107,61382,61383,61385,61388,61390,61392],{},[131,61384,3650],{},[131,61386,61387],{},"NHN Notification Hub 실 자격증명 등록 + 어댑터 재작성",[131,61389,57719],{},[131,61391,484],{},[131,61393,61394,61396,61397,61399],{},[21,61395,3658],{}," + Email real 발송 검증 통과. SMS는 NHN 콘솔 발신번호 등록 + ",[32,61398,31521],{}," secret 대기. push\u002Frcs\u002Fkakao 어댑터 마이그레이션 후속",[107,61401,61402,61404,61406,61408,61410],{},[131,61403,3672],{},[131,61405,3677],{},[131,61407,3931],{},[131,61409,484],{},[131,61411,61412],{},"6\u002F4 재시도 → 여전히 1007 (Workers outbound IPv6 vs NICE 콘솔 IPv4 등록). 사용자 콘솔 IP 정책 해결 대기",[107,61414,61415,61417,61423,61425,61427],{},[131,61416,3691],{},[131,61418,61419,61420,61422],{},"R2 bucket ",[32,61421,35819],{}," 신규 + FILES 바인딩",[131,61424,3869],{},[131,61426,484],{},[131,61428,61429,61430,61432],{},"6\u002F2 §11. 사업자등록증·대부업등록증·보험증권 첨부용. 6\u002F4 §5에서 ",[32,61431,58],{}," 시드 추가",[107,61434,61435,61437,61439,61441,61443],{},[131,61436,3710],{},[131,61438,3715],{},[131,61440,3931],{},[131,61442,484],{},[131,61444,61445,61447],{},[21,61446,42686],{},"(6\u002F4). 미구현",[107,61449,61450,61452,61454,61456,61458],{},[131,61451,3728],{},[131,61453,3733],{},[131,61455,3931],{},[131,61457,484],{},[131,61459,322],{},[107,61461,61462,61464,61466,61468,61470],{},[131,61463,3746],{},[131,61465,3751],{},[131,61467,3869],{},[131,61469,484],{},[131,61471,61472,61473,6219,61476,61479,61480,61482],{},"6\u002F4 §2. id ",[32,61474,61475],{},"a2ba…",[32,61477,61478],{},"439b…"," 신규 origin ",[32,61481,39738],{}," + access_client_id. Aurora SG egress IP 화이트리스트 운영 부담 해소. 정본 3개(API CLAUDE.md §3·§8·§12, SCALABILITY.md §6 신규 절, MIGRATION.md §1) 동기화. 라이브 검증 통과",[107,61484,61485,61487,61489,61491,61493],{},[131,61486,3765],{},[131,61488,3770],{},[131,61490,3869],{},[131,61492,484],{},[131,61494,61007,61495,61497,61498,4361,61500,61502],{},[32,61496,3775],{}," 발신 도메인 NHN Notification Hub 콘솔 등록 + ",[32,61499,31517],{},[32,61501,42337],{}," secret 등록. NHN 직접 호출 SUCCESS · messageId 발급 확인",[107,61504,61505,61507,61509,61511,61513],{},[131,61506,3789],{},[131,61508,3794],{},[131,61510,3931],{},[131,61512,484],{},[131,61514,61515,61516,61518],{},"어댑터·인증·페이로드 검증 완료. NHN 콘솔 발신번호 등록 + ",[32,61517,31521],{}," secret 설정 + 라이브 e2e 1건 대기",[73,61520],{},[76,61522,27126],{"id":61523},"알려진-한계-다음-단계",[81,61525,61526,61536,61544,61552,61558,61564,61573,61579,61591,61596],{},[84,61527,61528,61532,61533,61535],{},[20367,61529,61530],{},[21,61531,26336],{}," — 2026-06-01 라이브 적용 확인 + e2e 검증 완료. 라이브 정본이 더 정교한 인덱스\u002FFK로 적용돼 있어 ",[32,61534,24556],{}," 파일도 정본에 맞춰 동기화 — 신규 환경에서도 동일 적용 가능.",[84,61537,61538,61543],{},[21,61539,61540,61542],{},[32,61541,20120],{}," 의존성 다중화"," — 1105 같은 dev\u002Fpreview 장애가 또 일어나면 또 막힘. Cloudflare Tunnel·RDS Proxy·bastion 등 후보 중 하나 도입 검토 (CLAUDE.md §12 TODO).",[84,61545,61546,89,61549,61551],{},[21,61547,61548],{},"Drizzle schema.ts 정합화",[32,61550,21654],{},"의 export\u002Fflow 테이블 정의는 인덱스\u002FFK 미선언(컬럼만). 동작 영향은 없으나 위생적으로 명시화 후속.",[84,61553,61554,61557],{},[21,61555,61556],{},"김도형 담당 마감 = 7\u002F3"," (개발·테스트 통합 완료 목표) — 남은 task는 위 W1~W5 일정에 분배.",[84,61559,61560,61563],{},[21,61561,61562],{},"백엔드 연동 점진 진행 중"," — 인증·계정·계약·승인 게이트·서비스 담당자 이메일 변경까지 실 API. 발송 6채널·이력\u002F통계·주소록·발신정보·템플릿·캠페인은 여전히 목업.",[84,61565,61566,61569,61570,61572],{},[21,61567,61568],{},"관리자단 화면 vs 연동 분리"," — 6\u002F4 핸드오프 17 페이지 화면(",[32,61571,61267],{},")은 모두 완료. API 연동·관리자 인증\u002FRBAC·실데이터 바인딩은 미. 대시보드 차트 4종은 더미 데이터.",[84,61574,61575,61578],{},[21,61576,61577],{},"NHN real 모드"," — Email ✅ 라이브 발송. SMS는 발신번호 등록 대기. push\u002Frcs\u002Fkakao 어댑터는 Notification Hub로 마이그레이션 미.",[84,61580,61581,61584,61585,61587,61588,61590],{},[21,61582,61583],{},"PG"," — TossPayments 확정. ",[32,61586,43521],{}," + 카드 등록\u002F결제\u002F취소\u002Fwebhook 미구현. ",[21,61589,3944],{}," 게이트웨이도 미.",[84,61592,61593,61595],{},[21,61594,60592],{}," — 시안의 \"쏠쏠 브랜드\" 단독 404·점검·네트워크 페이지를 맑은 브랜드로 재작업 필요.",[84,61597,61598,61601,61602,4339,61604,61607],{},[21,61599,61600],{},"Step 4 정식 디자인 산출물"," — 디자인팀 정식 스타일 가이드 + 퍼블리싱 MD는 미작성. 현재는 개발 측 ",[26,61603,7689],{"href":7646},[26,61605,4457],{"href":15418,"rel":61606},[30]," 카탈로그로 대체.",{"title":4208,"searchDepth":4209,"depth":4209,"links":61609},[61610,61613,61614,61615,61616,61617,61618,61619,61620,61621,61622,61623,61624,61625,61626,61627,61628,61629,61631,61637,61639,61640],{"id":57360,"depth":4212,"text":57361,"children":61611},[61612],{"id":57484,"depth":4209,"text":57485},{"id":57588,"depth":4212,"text":27085},{"id":57649,"depth":4212,"text":57650},{"id":57731,"depth":4212,"text":57732},{"id":57824,"depth":4212,"text":57825},{"id":57903,"depth":4212,"text":57904},{"id":57963,"depth":4212,"text":57964},{"id":58086,"depth":4212,"text":58087},{"id":58179,"depth":4212,"text":58180},{"id":58227,"depth":4212,"text":58228},{"id":58262,"depth":4212,"text":58263},{"id":58391,"depth":4212,"text":58392},{"id":58502,"depth":4212,"text":58503},{"id":58584,"depth":4212,"text":58585},{"id":58659,"depth":4212,"text":58660},{"id":58839,"depth":4212,"text":58840},{"id":59037,"depth":4212,"text":59038},{"id":59226,"depth":4212,"text":61630},"5-2. API 서버 (malgn-noti-api)",{"id":59730,"depth":4212,"text":61632,"children":61633},"5-3. 사용자단 (malgn-noti) — 3 트랙으로 분리",[61634,61635,61636],{"id":59775,"depth":4209,"text":59776},{"id":60068,"depth":4209,"text":60069},{"id":60638,"depth":4209,"text":60639},{"id":61033,"depth":4212,"text":61638},"5-4. 관리자단 (malgn-noti-admin) 화면 개발",{"id":61291,"depth":4212,"text":61292},{"id":61523,"depth":4212,"text":27126},{},{"title":57333,"description":4208},"D1Ttn0SbJXP4yJQCcpjMvjzaXxSHLgIzRb35M2GHqqE",1780638909349]