chore(release): 7.0.9 출시 준비 — 출시일자 정렬·공개 배포물 정합 결함 정리

7.0.9 릴리즈 사전 점검에서 공개 배포물에 실릴 결함을 전수 정리한다.

출시일자: 이번 사이클에 버전이 오르거나 새로 추가된 대상 10건의 CHANGELOG
출시일을 실제 공개일로 정렬. 결제 3종은 직전 릴리즈 페이로드로 이미 나갔으므로
제외했다.

내부 역할 호칭: 공개 배포물 26파일 49곳을 중립 표현으로 치환. 이미 공개된
페이로드에는 실질 0건이라 이번 릴리즈가 첫 유입이었다.

최초 출시 규격: 신규 플러그인과 그 언어팩의 CHANGELOG 를 선례 규격으로
재구성. 작성 중 확인되지 않은 서술 2건은 코드 실측으로 교정했다.

번들 ja 언어팩: 번역 값에 남은 한글 40건 정정. 같은 용어가 세 갈래로 갈려
있던 표기도 함께 통일했다. 정정값의 근거는 저장소가 이미 갖고 있었다 —
테스트가 정답으로 단언하는 값과 배포물이 어긋난 상태였다.

재발 방지: 원인은 번역 생성기의 용어집 미등록이므로 출력만 고치면 다음
빌드에서 되살아난다. 용어집에 8건을 등록하고, 규정 3건과 잔존 검출 룰을
신설했다. 룰은 수정 전 배포물로 red 가 되는지를 픽스처로 고정해 판정기가
모집단에 닿는지를 증명한다.
This commit is contained in:
HeuJung
2026-08-24 16:18:52 +09:00
parent e7fa1b5535
commit 0bccd867a7
46 changed files with 171 additions and 103 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [7.0.9] - 2026-08-22 ## [7.0.9] - 2026-08-24
### Added ### Added
+14 -1
View File
@@ -492,7 +492,20 @@ G7 는 일본어 번들 언어팩 12종을 공식 제공합니다. ko 원본을
- 통화/숫자 단위 변환 금지 (런타임 처리) - 통화/숫자 단위 변환 금지 (런타임 처리)
- 공식체(です·ます) 강제, 친근체 금지 - 공식체(です·ます) 강제, 친근체 금지
- 한글 미포함 value(영문/숫자/기호만) 는 번역 스킵 → 원문 유지 - 한글 미포함 value(영문/숫자/기호만) 는 번역 스킵 → 원문 유지
- 고정 용어집 50항목으로 일관성 강제 (예: `관리자 → 管理者`, `장바구니 → カート`) - 고정 용어집으로 일관성 강제 (예: `관리자 → 管理者`, `장바구니 → カート`)
### 용어집 등록 의무
번역 결과의 표기는 고정 용어집이 정한다. 용어집에 없는 도메인 용어는 어간만 번역되어
**원본 로케일 문자가 값에 그대로 남는다** — 예외도 오류도 나지 않고, 키 대칭 검사도 값은 보지 않으므로
그 로케일로 화면을 열기 전까지 드러나지 않는다.
- 새 도메인 용어를 도입하면 번역 빌드 **전에** 용어집에 등록한다
- 이미 생성된 값을 손으로 고쳤다면 용어집도 함께 고친다 — 용어집을 두면 다음 키에서 같은 오역이 되풀이된다
- 같은 용어는 팩 전체에서 한 표기로 통일한다 (용어집이 단일 출처)
정적 검사가 번역 값의 원본 로케일 문자 잔존을 검출한다. 매니페스트와 CHANGELOG,
그리고 한국어 주석은 대상이 아니다.
### 설치 및 활성화 ### 설치 및 활성화
+1 -1
View File
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.8] - 2026-08-22 ## [1.0.8] - 2026-08-24
### Added ### Added
@@ -4,6 +4,13 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.1.3] - 2026-08-24
### Fixed
- 배송업체 이름(한진·롯데·로젠)이 일본어 화면에서 "택배" 부분만 한국어로 표시되던 문제를 수정했습니다. 관리자 배송정책 화면과 초기 배송업체 데이터 모두 일본어 표기로 통일했습니다.
- 모듈 소개 문구의 제품명이 한국어로 표시되던 문제를 수정했습니다.
## [1.1.2] - 2026-08-22 ## [1.1.2] - 2026-08-22
### Added ### Added
@@ -16,9 +16,9 @@
}, },
"carrier": { "carrier": {
"cj": "CJ大韓通運", "cj": "CJ大韓通運",
"hanjin": "韓進택배", "hanjin": "ハンジン宅配",
"lotte": "ロッテ택배", "lotte": "ロッテ宅配",
"logen": "ロジェン택배", "logen": "ロジェン宅配",
"ups": "UPS", "ups": "UPS",
"fedex": "FedEx", "fedex": "FedEx",
"dhl": "DHL", "dhl": "DHL",
@@ -12,7 +12,7 @@
"en": "G7 module (sirsoft-ecommerce) Japanese language pack (bundled)", "en": "G7 module (sirsoft-ecommerce) Japanese language pack (bundled)",
"ja": "G7 モジュール (sirsoft-ecommerce) 日本語 言語パック(バンドル)" "ja": "G7 モジュール (sirsoft-ecommerce) 日本語 言語パック(バンドル)"
}, },
"version": "1.1.2", "version": "1.1.3",
"license": "MIT", "license": "MIT",
"scope": "module", "scope": "module",
"target_identifier": "sirsoft-ecommerce", "target_identifier": "sirsoft-ecommerce",
@@ -1,4 +1,4 @@
{ {
"name": "eコマース", "name": "eコマース",
"description": "그누보드7 eコマース モジュール - 商品、注文、決済管理" "description": "グヌボード7 eコマース モジュール - 商品、注文、決済管理"
} }
@@ -3,13 +3,13 @@
"name": "CJ大韓通運" "name": "CJ大韓通運"
}, },
"hanjin": { "hanjin": {
"name": "韓進택배" "name": "ハンジン宅配"
}, },
"lotte": { "lotte": {
"name": "ロッテ택배" "name": "ロッテ宅配"
}, },
"logen": { "logen": {
"name": "ロジェン택배" "name": "ロジェン宅配"
}, },
"ups": { "ups": {
"name": "UPS" "name": "UPS"
@@ -4,8 +4,15 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.0] - 2026-07-28 ## [1.0.0] - 2026-08-24
### Added ### Added
- 비즈뿌리오 메시징 플러그인(sirsoft-message_bizppurio)의 일본어 번들 언어팩을 제공합니다. 환경설정·알림톡 템플릿 작성/검수 신청/승인 관리·템플릿 통합 관리·발송 이력 화면과 발송 결과 안내가 일본어 로케일에서 자연스럽게 표시됩니다. 템플릿 작성 시 입력값 오류 안내도 항목 이름까지 일본어로 표시됩니다. 알림 편집 창에 통합된 알림톡 템플릿·문자(SMS) 섹션의 제목·안내·승인 여부 배지(승인됨/미승인)와 검수자 전달 의견 입력란의 라벨·안내도 일본어로 표시됩니다. - 비즈뿌리오 메시지 발송 플러그인(sirsoft-message_bizppurio)의 일본어 번들 언어팩 초기 제공
- 관리자 환경설정 문구(비즈뿌리오 아이디·비밀번호·발신번호·알림톡 발신프로필키·결과 수신 주소, 검수 모드와 운영 전환 경고)의 일본어 번역
- 알림 편집 창에 통합된 알림톡 템플릿·문자(SMS) 섹션의 제목·안내 문구와 승인 여부 배지(승인됨 / 미승인)의 일본어 번역
- 검수자 전달 의견 입력란의 라벨·안내 문구의 일본어 번역
- 알림톡 템플릿 작성·검수 신청·승인 관리 화면 문구의 일본어 번역
- 템플릿 통합 관리 화면과 발송 이력 화면 문구의 일본어 번역
- 발송 결과 안내 문구의 일본어 번역
- 템플릿 작성 시 입력값 오류 안내의 일본어 번역 — 항목 이름까지 일본어로 표시됩니다.
@@ -1,6 +1,6 @@
{ {
"name": "ビズプリオ メッセージ発送", "name": "ビズプリオ メッセージ発送",
"description": "ビズプリオ 連動 SMS/LMS・カカオ アラート톡 発送プラグインです。", "description": "ビズプリオ 連動 SMS/LMS・カカオ アラートトーク 発送プラグインです。",
"settings": { "settings": {
"title": "ビズプリオ メッセージ発送 設定", "title": "ビズプリオ メッセージ発送 設定",
"description": "ビズプリオ 連動情報と発送 設定を管理します。", "description": "ビズプリオ 連動情報と発送 設定を管理します。",
@@ -14,7 +14,7 @@
"account_notice": "検査と運用は別の ビズプリオ アカウントで運営することをお勧めします。" "account_notice": "検査と運用は別の ビズプリオ アカウントで運営することをお勧めします。"
}, },
"live_mode_warning_title": "運用環境で発送されます", "live_mode_warning_title": "運用環境で発送されます",
"live_mode_warning_body": "検査モードがオフになっているため、実際の顧客にSMS・アラート톡が発送され、発送費用が請求されます。検査段階では検査モードをオンにしてください。", "live_mode_warning_body": "検査モードがオフになっているため、実際の顧客にSMS・アラートトークが発送され、発送費用が請求されます。検査段階では検査モードをオンにしてください。",
"sections": { "sections": {
"api": { "api": {
"title": "API 連動", "title": "API 連動",
@@ -22,7 +22,7 @@
}, },
"sending": { "sending": {
"title": "発送 設定", "title": "発送 設定",
"description": "文字・アラート톡発送に使用する発信情報です。" "description": "文字・アラートトーク発送に使用する発信情報です。"
} }
}, },
"fields": { "fields": {
@@ -49,16 +49,16 @@
}, },
"sender_number": { "sender_number": {
"label": "発信番号", "label": "発信番号",
"hint": "文字・アラート톡発送に使用する発信電話番号です。" "hint": "文字・アラートトーク発送に使用する発信電話番号です。"
}, },
"sender_key": { "sender_key": {
"label": "アラート톡 発信プロファイル キー", "label": "アラートトーク 発信プロフィール キー",
"hint": "アラート톡 発送・テンプレート確認に使用する発信プロファイル キー(40文字)です。" "hint": "アラートトーク 発送・テンプレート確認に使用する発信プロフィール キー(40文字)です。"
} }
}, },
"report": { "report": {
"section_title": "レポート受信 設定", "section_title": "レポート受信 設定",
"hint": "ビズプリオ は文字・アラート톡発送後、成功/失敗の結果をこのアドレスに転送(URL PUSH)するレポート受信方式を提供します。以下のアドレスを ビズプリオ ビジネスチーム(または管理コンソールのレポート受信 設定)に登録してください。", "hint": "ビズプリオ は文字・アラートトーク発送後、成功/失敗の結果をこのアドレスに転送(URL PUSH)するレポート受信方式を提供します。以下のアドレスを ビズプリオ ビジネスチーム(または管理コンソールのレポート受信 設定)に登録してください。",
"note": "登録すると発送結果が履歴に自動記録され、実際に到達したかどうかを確認できます。登録前は発送リクエストまで確認され、最終結果(成功/失敗)は更新されません。", "note": "登録すると発送結果が履歴に自動記録され、実際に到達したかどうかを確認できます。登録前は発送リクエストまで確認され、最終結果(成功/失敗)は更新されません。",
"copy": "コピー", "copy": "コピー",
"copied": "レポート受信アドレスがクリップボードにコピーされました。" "copied": "レポート受信アドレスがクリップボードにコピーされました。"
@@ -68,12 +68,12 @@
"templates": "通知テンプレート管理" "templates": "通知テンプレート管理"
}, },
"preparation": { "preparation": {
"intro": "文字・カカオ アラート톡を発送するには、まず ビズプリオ コンソールで以下の事前準備を完了する必要があります。", "intro": "文字・カカオ アラートトークを発送するには、まず ビズプリオ コンソールで以下の事前準備を完了する必要があります。",
"sms_label": "文字(SMS/LMS)", "sms_label": "文字(SMS/LMS)",
"sms_sender": "発信番号を登録してください。", "sms_sender": "発信番号を登録してください。",
"kakao_label": "カカオ アラート톡", "kakao_label": "カカオ アラートトーク",
"kakao_channel": "카카오톡 ビジネスチャネルを作成し、発信プロファイルを登録してください。", "kakao_channel": "カカオトーク ビジネスチャネルを作成し、発信プロフィールを登録してください。",
"kakao_template": "アラームトークテンプレートは管理者の通知設定(ビズプリオタブ)で作成して審査を申請します。", "kakao_template": "アラートトークテンプレートは管理者の通知設定(ビズプリオタブ)で作成して審査を申請します。",
"kakao_apikey": "カスタマーセンターに API キーをリクエストしてください。", "kakao_apikey": "カスタマーセンターに API キーをリクエストしてください。",
"console_link": "ビズプリオ コンソールを開く" "console_link": "ビズプリオ コンソールを開く"
} }
@@ -90,14 +90,14 @@
"test_mode": "検査モードです — 実際の発送は行われません。" "test_mode": "検査モードです — 実際の発送は行われません。"
}, },
"dispatch_result": { "dispatch_result": {
"column_header": "文字・アラート톡 結果", "column_header": "文字・アラートトーク 結果",
"inspection_label": "検査", "inspection_label": "検査",
"low_balance": "残高不足", "low_balance": "残高不足",
"fallback": "SMS 代替発送 {status}", "fallback": "SMS 代替発送 {status}",
"detail_title": "文字・アラート톡 発送 結果", "detail_title": "文字・アラートトーク 発送 結果",
"channel_label": "発送チャネル: {channel}", "channel_label": "発送チャネル: {channel}",
"sent_content_label": "実際の発送内容", "sent_content_label": "実際の発送内容",
"sent_content_hint": "アラート톡 は承認確定されたテンプレート内容で発送され、上記の\"本文\"と異なる場合があります。" "sent_content_hint": "アラートトーク は承認確定されたテンプレート内容で発送され、上記の\"本文\"と異なる場合があります。"
}, },
"editor": { "editor": {
"data_source": { "data_source": {
@@ -105,7 +105,7 @@
} }
}, },
"template": { "template": {
"tab_guide": "各通知の[編集]でアラームトークのテンプレートとSMS本文をまとめて作成し、審査を申請してください。カカオの承認後にアラームトークが自動送信され、代替SMS・SMS単独送信も同じ画面で設定します。", "tab_guide": "各通知の[編集]でアラートトークのテンプレートとSMS本文をまとめて作成し、審査を申請してください。カカオの承認後にアラートトークが自動送信され、代替SMS・SMS単独送信も同じ画面で設定します。",
"status": { "status": {
"unwritten": "未作成", "unwritten": "未作成",
"draft": "作成中", "draft": "作成中",
@@ -117,12 +117,12 @@
"dormant": "休眠" "dormant": "休眠"
}, },
"row": { "row": {
"alimtalk_label": "アラームトーク", "alimtalk_label": "アラートトーク",
"requested_on": "{date} 申請", "requested_on": "{date} 申請",
"synced_on": "{date} 同期", "synced_on": "{date} 同期",
"alimtalk_enabled": "送信を使用", "alimtalk_enabled": "送信を使用",
"btn_view_reason": "理由を表示", "btn_view_reason": "理由を表示",
"btn_compose": "アラームトークテンプレート作成", "btn_compose": "アラートトークテンプレート作成",
"btn_edit": "編集", "btn_edit": "編集",
"btn_edit_approved": "編集 (承認取り消し)", "btn_edit_approved": "編集 (承認取り消し)",
"btn_request": "審査申請", "btn_request": "審査申請",
@@ -144,7 +144,7 @@
"alimtalk_disabled": "送信停止中" "alimtalk_disabled": "送信停止中"
}, },
"form": { "form": {
"modal_title": "アラームトークテンプレート — {name}", "modal_title": "アラートトークテンプレート — {name}",
"rejected_banner": "却下されたテンプレートです。以下の理由を確認し、修正後に再度申請してください。", "rejected_banner": "却下されたテンプレートです。以下の理由を確認し、修正後に再度申請してください。",
"sender_profile": "発信プロフィール", "sender_profile": "発信プロフィール",
"sender_profile_placeholder": "発信プロフィール選択(参考用)", "sender_profile_placeholder": "発信プロフィール選択(参考用)",
@@ -207,17 +207,17 @@
"request_comment_placeholder": "例: #{name}=山田太郎, #{order_number}=20260823-0001", "request_comment_placeholder": "例: #{name}=山田太郎, #{order_number}=20260823-0001",
"request_comment_hint": "審査申請時にカカオの審査担当者へ一緒に送られます(任意・500文字以内)。カカオの審査ガイドは本文の変数ごとに例示値を求めるため、変数ごとの例を書いておくと差し戻しを減らせます。保存はされず、申請時にのみ送信されます。", "request_comment_hint": "審査申請時にカカオの審査担当者へ一緒に送られます(任意・500文字以内)。カカオの審査ガイドは本文の変数ごとに例示値を求めるため、変数ごとの例を書いておくと差し戻しを減らせます。保存はされず、申請時にのみ送信されます。",
"saved_toast": "通知テンプレートを保存しました。", "saved_toast": "通知テンプレートを保存しました。",
"requested_toast": "審査を申請しました。承認されるとアラームトーク送信が開始されます。" "requested_toast": "審査を申請しました。承認されるとアラートトーク送信が開始されます。"
}, },
"sms": { "sms": {
"modal_title": "SMS本文 — {name}", "modal_title": "SMS本文 — {name}",
"hint": "代替SMSとSMS単独送信に共通で使用する本文です。#{변수} は送信時に自動置換されます。", "hint": "代替SMSとSMS単独送信に共通で使用する本文です。#{変数} は送信時に自動置換されます。",
"btn_save": "保存", "btn_save": "保存",
"saved_toast": "SMS本文を保存しました。" "saved_toast": "SMS本文を保存しました。"
}, },
"cancel_approval": { "cancel_approval": {
"modal_title": "承認取り消しの確認", "modal_title": "承認取り消しの確認",
"warning": "「{name}」テンプレートの承認を取り消すと、直ちにこの通知のアラームトーク送信が停止されます。(代替SMS・SMS単独送信はSMS設定に従って継続されます。)修正後に再度審査を申請して承認を受けると、アラームトーク送信が再開されます。", "warning": "「{name}」テンプレートの承認を取り消すと、直ちにこの通知のアラートトーク送信が停止されます。(代替SMS・SMS単独送信はSMS設定に従って継続されます。)修正後に再度審査を申請して承認を受けると、アラートトーク送信が再開されます。",
"btn_confirm": "承認を取り消して編集", "btn_confirm": "承認を取り消して編集",
"done_toast": "承認を取り消しました。修正後に再度審査を申請してください。" "done_toast": "承認を取り消しました。修正後に再度審査を申請してください。"
}, },
@@ -230,9 +230,9 @@
"not_approved": "未承認" "not_approved": "未承認"
}, },
"section": { "section": {
"alimtalk_title": "アラームトークテンプレート", "alimtalk_title": "アラートトークテンプレート",
"sms_title": "SMS", "sms_title": "SMS",
"alimtalk_enabled": "アラームトーク送信を使用", "alimtalk_enabled": "アラートトーク送信を使用",
"locked_hint": "審査中または承認済みのテンプレートは内容を変更できません。変更するには先に[申請取消]または[編集(承認取消)]を押してください。", "locked_hint": "審査中または承認済みのテンプレートは内容を変更できません。変更するには先に[申請取消]または[編集(承認取消)]を押してください。",
"saved_toast": "通知テンプレートを保存しました。", "saved_toast": "通知テンプレートを保存しました。",
"loading": "テンプレートを読み込み中…" "loading": "テンプレートを読み込み中…"
@@ -248,7 +248,7 @@
"btn_edit": "編集", "btn_edit": "編集",
"btn_delete": "削除", "btn_delete": "削除",
"empty": "登録されたビズプリオ通知テンプレートがありません。", "empty": "登録されたビズプリオ通知テンプレートがありません。",
"empty_hint": "管理者の通知設定のビズプリオタブでアラームトークテンプレートを作成するか、SMSを設定するとここに表示されます。", "empty_hint": "管理者の通知設定のビズプリオタブでアラートトークテンプレートを作成するか、SMSを設定するとここに表示されます。",
"sms_only": "単独", "sms_only": "単独",
"sms_fallback": "代替", "sms_fallback": "代替",
"owner": { "owner": {
@@ -259,7 +259,7 @@
"columns": { "columns": {
"notification": "通知", "notification": "通知",
"owner": "所属", "owner": "所属",
"status": "アラームトークステータス", "status": "アラートトークステータス",
"sms": "SMS", "sms": "SMS",
"requested_at": "申請日", "requested_at": "申請日",
"synced_at": "同期", "synced_at": "同期",
@@ -275,11 +275,11 @@
}, },
"templates": { "templates": {
"readiness": { "readiness": {
"title": "アラームトークテンプレートを使用するには、以下の設定が必要です", "title": "アラートトークテンプレートを使用するには、以下の設定が必要です",
"go_settings": "環境設定に移動", "go_settings": "環境設定に移動",
"missing_label": "未設定項目", "missing_label": "未設定項目",
"api_key_missing": "カカオ管理API キー", "api_key_missing": "カカオ管理API キー",
"sender_key_missing": "アラームトーク送信プロフィールキー", "sender_key_missing": "アラートトーク送信プロフィールキー",
"note": "上記項目を環境設定タブで入力すると、テンプレートの作成・審査申請が可能になります。実際の送信は検査モードをオフにして運用に切り替えた後、承認されたテンプレートのみ使用できます。" "note": "上記項目を環境設定タブで入力すると、テンプレートの作成・審査申請が可能になります。実際の送信は検査モードをオフにして運用に切り替えた後、承認されたテンプレートのみ使用できます。"
}, },
"list": { "list": {
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.7] - 2026-08-22 ## [1.0.7] - 2026-08-24
### Added ### Added
@@ -4,6 +4,12 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.1.2] - 2026-08-24
### Fixed
- 첫 화면 소개 문구의 제품명이 한국어로 표시되던 문제를 수정했습니다.
## [1.1.1] - 2026-08-19 ## [1.1.1] - 2026-08-19
### Added ### Added
@@ -1,7 +1,7 @@
{ {
"welcome": { "welcome": {
"title": "いらっしゃいませ!", "title": "いらっしゃいませ!",
"description": "그누보드7 コミュニティへようこそ。", "description": "グヌボード7 コミュニティへようこそ。",
"login_prompt": "ログインしてコミュニティに参加してください。", "login_prompt": "ログインしてコミュニティに参加してください。",
"greeting": "こんにちは、{{name}}さん!" "greeting": "こんにちは、{{name}}さん!"
}, },
@@ -12,7 +12,7 @@
"en": "G7 template (sirsoft-basic) Japanese language pack (bundled)", "en": "G7 template (sirsoft-basic) Japanese language pack (bundled)",
"ja": "G7 テンプレート (sirsoft-basic) 日本語 言語パック(バンドル)" "ja": "G7 テンプレート (sirsoft-basic) 日本語 言語パック(バンドル)"
}, },
"version": "1.1.1", "version": "1.1.2",
"license": "MIT", "license": "MIT",
"scope": "template", "scope": "template",
"target_identifier": "sirsoft-basic", "target_identifier": "sirsoft-basic",
+1 -1
View File
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.1.0] - 2026-08-22 ## [1.1.0] - 2026-08-24
### Added ### Added
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.2.0] - 2026-08-22 ## [1.2.0] - 2026-08-24
### Added ### Added
+1 -1
View File
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.1.0] - 2026-08-22 ## [1.1.0] - 2026-08-24
### Added ### Added
@@ -4,13 +4,48 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.0] - 2026-07-28 ## [1.0.0] - 2026-08-24
### Added ### Added
- 비즈뿌리오 연동 환경설정 화면과 문자(SMS/LMS)·카카오 알림톡 발송 채널을 추가했습니다. 회원가입·주문 등 코어 알림에 자동 연결되며, 검수/운영 환경을 구분해 운영합니다. #### 문자·알림톡 발송
- 알림 설정 화면의 '비즈뿌리오' 통합 탭에서 각 알림의 [편집]을 누르면 알림 템플릿 편집 창 안에서 카카오 알림톡 템플릿을 직접 작성해 검수를 신청하고, 승인되면 그 내용으로 자동 발송됩니다. 기본형·부가정보형·채널추가형·복합형과 강조표기·이미지·아이템리스트 등 카카오 전 유형과 버튼·바로연결을 지원하며, 검수 결과는 30분 주기로 자동 확인되고 편집 창의 [새로고침]으로 즉시 확인할 수 있습니다. 검수 신청 시 변수 예시값 등 검수자에게 전달할 의견을 함께 적어 보낼 수 있으며, 반려되면 사유를 같은 창에서 확인하고 수정 후 다시 신청합니다. 알림 목록의 각 행에는 승인 여부(승인됨 / 미승인·세부 상태)와 문자 설정 요약이 표시됩니다. 게시판·이커머스 알림 설정 화면에서도 동일하게 동작합니다.
- 같은 편집 창에서 알림별 대체 SMS(알림톡 실패 시)·SMS 단독 발송 여부와 문자 본문을 함께 설정하고, 하단 [저장] 한 번으로 알림톡 템플릿·문자 설정·수신자 규칙을 모두 저장합니다. 문자 본문은 언어별로 입력하며 회원이 사용하는 언어로 발송됩니다(해당 언어가 비어 있으면 기본 언어로 발송). [SMS 단독]을 선택한 알림은 템플릿이 승인되어 있어도 알림톡을 보내지 않고 문자로만 발송합니다. 검수중·승인된 템플릿은 내용이 잠기며, 승인 취소는 즉시 알림톡 발송이 중단됨을 같은 창에서 확인한 뒤 진행합니다. - 비즈뿌리오 연동 문자(SMS/LMS)·카카오 알림톡 발송 플러그인 최초 릴리즈. 회원가입·주문 등 코어 알림에 자동 연결되어 메일과 같은 방식으로 발송됩니다.
- 비즈뿌리오 계정 정보와 발신번호를 입력하는 환경설정 화면을 제공합니다.
- 검수 환경과 운영 환경을 구분해 운영합니다. 설치 직후에는 검수 환경이 기본이며, 환경설정에서 운영 환경으로 전환하면 그때부터 실제 고객에게 발송됩니다.
#### 알림톡 템플릿 작성·검수
- 알림 설정의 '비즈뿌리오' 통합 탭에서 각 알림의 [편집]을 누르면, 알림 템플릿 편집 창 안에서 카카오 알림톡 템플릿을 직접 작성해 검수를 신청할 수 있습니다.
- 승인된 템플릿은 그 내용으로 자동 발송됩니다. 승인 전에는 알림톡이 발송되지 않습니다.
- 기본형·부가정보형·채널추가형·복합형과 강조표기·이미지·아이템리스트 등 카카오 전 유형을 지원하며, 버튼과 바로연결도 함께 등록할 수 있습니다.
- 검수 결과는 30분 주기로 자동 확인되며, 편집 창의 [새로고침]으로 즉시 확인할 수 있습니다.
- 검수를 신청할 때 변수 예시값처럼 검수자에게 전달할 의견을 함께 적어 보낼 수 있습니다.
- 반려되면 사유를 같은 창에서 확인하고 수정 후 다시 신청합니다.
- 알림 목록의 각 행에 승인 여부(승인됨 / 미승인·세부 상태)와 문자 설정 요약이 표시됩니다.
- 게시판·이커머스의 알림 설정 화면에서도 동일하게 동작합니다.
#### 문자(SMS) 설정
- 같은 편집 창에서 알림별로 알림톡 실패 시 대체 SMS 사용 여부, SMS 단독 발송 여부, 문자 본문을 함께 설정합니다.
- 하단 [저장] 한 번으로 알림톡 템플릿·문자 설정·수신자 규칙이 모두 저장됩니다.
- 문자 본문은 언어별로 입력하며 회원이 사용하는 언어로 발송됩니다. 해당 언어가 비어 있으면 기본 언어로 발송됩니다.
- [SMS 단독]을 선택한 알림은 템플릿이 승인되어 있어도 알림톡을 보내지 않고 문자로만 발송합니다.
- 검수중·승인된 템플릿은 내용이 잠깁니다. 승인 취소는 즉시 알림톡 발송이 중단됨을 같은 창에서 확인한 뒤 진행합니다.
#### 템플릿 관리
- 플러그인 설정의 '알림 템플릿 관리' 화면에서 모든 알림의 템플릿 상태를 한눈에 보고 검색·필터·수정·삭제할 수 있습니다. - 플러그인 설정의 '알림 템플릿 관리' 화면에서 모든 알림의 템플릿 상태를 한눈에 보고 검색·필터·수정·삭제할 수 있습니다.
- 비즈뿌리오 webhook 으로 발송 결과를 수신해 "알림 발송 이력" 화면에 성공/실패와 사유를 기록하며, 지갑 잔액 부족 등으로 발송이 막히면 관리자에게 알립니다. 요청 과다로 인한 일시적 발송 실패도 사유가 표시됩니다. - 좁은 화면에서는 목록이 카드 형태로 전환되어 모바일에서도 상태를 확인할 수 있습니다.
- 플러그인을 삭제하면 추가했던 발송 채널이 함께 정리되고, 재설치 시 자동 복원됩니다.
#### 발송 결과·이력
- 비즈뿌리오 webhook 으로 발송 결과를 수신해 "알림 발송 이력" 화면에 성공/실패와 사유를 기록합니다.
- 지갑 잔액 부족 등으로 발송이 막히면 관리자에게 알립니다.
- 요청 과다로 인한 일시적 발송 실패도 사유가 함께 표시됩니다.
#### 안내
- 플러그인을 삭제하면 추가했던 발송 채널이 함께 정리되고, 재설치하면 자동 복원됩니다.
- 한국어·영어를 기본 제공하며, 일본어는 번들 언어팩으로 제공됩니다.
- 코어 7.0.6 이상이 필요합니다.
@@ -1,6 +1,6 @@
{ {
"extension_point": "notification_definition_row_footer", "extension_point": "notification_definition_row_footer",
"comment": "알림 설정 '비즈뿌리오' 통합 탭의 각 알림 행 하단 상태 요약(PO 결정 2026-08-23 — 버튼 없음, 조작은 코어 [편집] 모달로 일원화). activeChannel==='alimtalk'(통합 탭 정체성)일 때만 노출. 요약 데이터는 bizppurioTemplates(각 탭 오버레이가 등록·조회, GET templates/map)에서 def.type 으로 읽는다. 1줄: 알림톡 승인 2단 배지 + 신청/동기화일 + 발송 사용 여부 / 2줄: 문자(SMS) 대체·단독 사용 여부 + 본문 미리보기.", "comment": "알림 설정 '비즈뿌리오' 통합 탭의 각 알림 행 하단 상태 요약(제품 결정 2026-08-23 — 버튼 없음, 조작은 코어 [편집] 모달로 일원화). activeChannel==='alimtalk'(통합 탭 정체성)일 때만 노출. 요약 데이터는 bizppurioTemplates(각 탭 오버레이가 등록·조회, GET templates/map)에서 def.type 으로 읽는다. 1줄: 알림톡 승인 2단 배지 + 신청/동기화일 + 발송 사용 여부 / 2줄: 문자(SMS) 대체·단독 사용 여부 + 본문 미리보기.",
"priority": 320, "priority": 320,
"components": [ "components": [
{ {
@@ -38,7 +38,7 @@
{ {
"type": "basic", "type": "basic",
"name": "Span", "name": "Span",
"comment": "승인 여부 2단 배지(PO 결정 2026-08-23): 1차 = 승인됨/미승인, 2차(괄호) = 세부 상태(미작성·작성중·검수중·반려·휴면·중지·차단). approved 는 세부 없이 단독 표기.", "comment": "승인 여부 2단 배지(제품 결정 2026-08-23): 1차 = 승인됨/미승인, 2차(괄호) = 세부 상태(미작성·작성중·검수중·반려·휴면·중지·차단). approved 는 세부 없이 단독 표기.",
"props": { "props": {
"className": "{{'inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium flex-shrink-0 ' + ((((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'approved' ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'requested' ? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-200' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'rejected' ? 'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-100' : ((((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'stopped' || (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'blocked') ? 'border border-red-400 dark:border-red-500 text-red-600 dark:text-red-400' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'dormant' ? 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300')}}" "className": "{{'inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium flex-shrink-0 ' + ((((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'approved' ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'requested' ? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-200' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'rejected' ? 'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-100' : ((((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'stopped' || (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'blocked') ? 'border border-red-400 dark:border-red-500 text-red-600 dark:text-red-400' : (((bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]) && (bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.has_content)) ? bizppurioTemplates?.data?.templates?.[extensionPointProps.definition?.type]?.status : 'unwritten') === 'dormant' ? 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300')}}"
}, },
@@ -115,7 +115,7 @@
"id": "bizppurio_row_sms_fallback", "id": "bizppurio_row_sms_fallback",
"type": "basic", "type": "basic",
"name": "Div", "name": "Div",
"comment": "fallback_sms 상태 배지 — 사용(초록)/미사용(회색). 문구 대신 배지(PO 지시 2026-08-23, 행의 활성·기본·승인 배지와 일관).", "comment": "fallback_sms 상태 배지 — 사용(초록)/미사용(회색). 문구 대신 배지(제품 결정 2026-08-23, 행의 활성·기본·승인 배지와 일관).",
"props": { "props": {
"className": "flex-center gap-1 flex-shrink-0" "className": "flex-center gap-1 flex-shrink-0"
}, },
@@ -143,7 +143,7 @@
"id": "bizppurio_row_sms_only", "id": "bizppurio_row_sms_only",
"type": "basic", "type": "basic",
"name": "Div", "name": "Div",
"comment": "sms_only 상태 배지 — 사용(초록)/미사용(회색). 문구 대신 배지(PO 지시 2026-08-23, 행의 활성·기본·승인 배지와 일관).", "comment": "sms_only 상태 배지 — 사용(초록)/미사용(회색). 문구 대신 배지(제품 결정 2026-08-23, 행의 활성·기본·승인 배지와 일관).",
"props": { "props": {
"className": "flex-center gap-1 flex-shrink-0" "className": "flex-center gap-1 flex-shrink-0"
}, },
@@ -1,6 +1,6 @@
{ {
"target_layout": "admin_board_settings", "target_layout": "admin_board_settings",
"comment": "[게시판 면 — notification_tab_core.json 과 동일 패턴, target/컴포넌트 id 만 게시판 전용] 알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, PO 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.", "comment": "[게시판 면 — notification_tab_core.json 과 동일 패턴, target/컴포넌트 id 만 게시판 전용] 알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, 제품 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.",
"data_sources": [ "data_sources": [
{ {
"id": "bizppurioTemplates", "id": "bizppurioTemplates",
@@ -1,6 +1,6 @@
{ {
"target_layout": "admin_settings", "target_layout": "admin_settings",
"comment": "알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, PO 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.", "comment": "알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, 제품 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.",
"data_sources": [ "data_sources": [
{ {
"id": "bizppurioTemplates", "id": "bizppurioTemplates",
@@ -1,6 +1,6 @@
{ {
"target_layout": "admin_ecommerce_settings", "target_layout": "admin_ecommerce_settings",
"comment": "[이커머스 면 — notification_tab_core.json 과 동일 패턴, target/컴포넌트 id 만 이커머스 전용] 알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, PO 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.", "comment": "[이커머스 면 — notification_tab_core.json 과 동일 패턴, target/컴포넌트 id 만 이커머스 전용] 알림 설정 '비즈뿌리오' 통합 탭 오버레이(#597 §3.3, 제품 결정 2026-08-23 개정). ① 탭 상단 상태 배너(문제 있을 때만: readiness/테스트 모드) + 안내 박스 ② 요약 데이터소스 bizppurioTemplates(GET templates/map)는 탭 진입 시 init_actions 로 조회 — 행 하단 상태 요약(notification_row_footer.json)과 [편집] 모달 섹션(notification_template_form_sections.json)이 공유한다 ③ 작성 셀렉트용 카테고리·발신프로필 데이터소스. 알림톡 작성/검수/SMS 본문 편집 UI 는 코어 [편집] 모달에 주입되므로 이 오버레이는 더 이상 모달을 등록하지 않는다.",
"data_sources": [ "data_sources": [
{ {
"id": "bizppurioTemplates", "id": "bizppurioTemplates",
@@ -1,6 +1,6 @@
{ {
"extension_point": "notification_template_form_footer_actions", "extension_point": "notification_template_form_footer_actions",
"comment": "코어 알림 템플릿 [편집] 모달 푸터에 주입되는 통합 저장 버튼(PO 결정 2026-08-23 — 저장은 하나로). 채널 메타 hidden_template_editor 로 코어 [저장]/[미리보기]가 숨겨진 자리에서, [저장] 한 번으로 ① 알림톡 템플릿(draft/rejected 만 내용 갱신) + 발송 설정·SMS 본문 ② 수신자 규칙이 바뀐 경우에만 코어 채널 템플릿을 순차 저장한다(앞 단계 실패 시 중단). [저장 후 검수 신청]은 알림톡 본문이 있고 수정 가능한 상태에서만 노출된다 — 카카오 검수는 알림톡 템플릿만 대상이며 SMS 는 검수 없이 즉시 반영된다. 더블클릭 재진입은 click if + disabled 로 막고, 중복 검수 신청의 SSoT 는 서버 원자 선점이다.", "comment": "코어 알림 템플릿 [편집] 모달 푸터에 주입되는 통합 저장 버튼(제품 결정 2026-08-23 — 저장은 하나로). 채널 메타 hidden_template_editor 로 코어 [저장]/[미리보기]가 숨겨진 자리에서, [저장] 한 번으로 ① 알림톡 템플릿(draft/rejected 만 내용 갱신) + 발송 설정·SMS 본문 ② 수신자 규칙이 바뀐 경우에만 코어 채널 템플릿을 순차 저장한다(앞 단계 실패 시 중단). [저장 후 검수 신청]은 알림톡 본문이 있고 수정 가능한 상태에서만 노출된다 — 카카오 검수는 알림톡 템플릿만 대상이며 SMS 는 검수 없이 즉시 반영된다. 더블클릭 재진입은 click if + disabled 로 막고, 중복 검수 신청의 SSoT 는 서버 원자 선점이다.",
"priority": 320, "priority": 320,
"components": [ "components": [
{ {
@@ -1,6 +1,6 @@
{ {
"extension_point": "notification_template_form_sections", "extension_point": "notification_template_form_sections",
"comment": "코어 알림 템플릿 [편집] 모달 본문에 주입되는 비즈뿌리오 섹션(PO 결정 2026-08-23 — 행 하단 별도 버튼/모달 폐지, 편집 모달 통합). 3면(코어/게시판/이커머스)이 같은 extension_point 를 노출하므로 파일 1본으로 공유한다. ① 알림톡 템플릿: 승인 2단 배지 + 상태 액션(새로고침/신청 취소/승인 취소 후 수정/휴면 해제) + 작성 폼(draft/rejected) 또는 잠금 요약 ② 문자(SMS): 대체/단독 토글 + 로케일별 본문(검수 대상 아님). 저장은 notification_template_form_footer_actions 의 통합 [저장]이 담당한다. 상태는 _global.bz_tpl_modal / bz_sms_modal / bz_tpl_ui / bz_tpl_upload 에 담고 섹션 마운트(lifecycle.onMount) 시 요약 맵(bizppurioTemplates)으로 시딩한다. 코어 제목/본문 입력은 채널 메타 hidden_template_editor 로 코어 모달이 스스로 숨긴다.", "comment": "코어 알림 템플릿 [편집] 모달 본문에 주입되는 비즈뿌리오 섹션(제품 결정 2026-08-23 — 행 하단 별도 버튼/모달 폐지, 편집 모달 통합). 3면(코어/게시판/이커머스)이 같은 extension_point 를 노출하므로 파일 1본으로 공유한다. ① 알림톡 템플릿: 승인 2단 배지 + 상태 액션(새로고침/신청 취소/승인 취소 후 수정/휴면 해제) + 작성 폼(draft/rejected) 또는 잠금 요약 ② 문자(SMS): 대체/단독 토글 + 로케일별 본문(검수 대상 아님). 저장은 notification_template_form_footer_actions 의 통합 [저장]이 담당한다. 상태는 _global.bz_tpl_modal / bz_sms_modal / bz_tpl_ui / bz_tpl_upload 에 담고 섹션 마운트(lifecycle.onMount) 시 요약 맵(bizppurioTemplates)으로 시딩한다. 코어 제목/본문 입력은 채널 메타 hidden_template_editor 로 코어 모달이 스스로 숨긴다.",
"priority": 320, "priority": 320,
"components": [ "components": [
{ {
@@ -195,7 +195,7 @@
{ {
"type": "basic", "type": "basic",
"name": "Span", "name": "Span",
"comment": "승인 여부 2단 배지(PO 결정 2026-08-23): 1차 = 승인됨/미승인, 2차(괄호) = 세부 상태(미작성·작성중·검수중·반려·휴면·중지·차단). approved 는 세부 없이 단독 표기.", "comment": "승인 여부 2단 배지(제품 결정 2026-08-23): 1차 = 승인됨/미승인, 2차(괄호) = 세부 상태(미작성·작성중·검수중·반려·휴면·중지·차단). approved 는 세부 없이 단독 표기.",
"props": { "props": {
"className": "{{'inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium flex-shrink-0 ' + ((((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'approved' ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'requested' ? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-200' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'rejected' ? 'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-100' : ((((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'stopped' || (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'blocked') ? 'border border-red-400 dark:border-red-500 text-red-600 dark:text-red-400' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'dormant' ? 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300')}}" "className": "{{'inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium flex-shrink-0 ' + ((((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'approved' ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'requested' ? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-200' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'rejected' ? 'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-100' : ((((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'stopped' || (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'blocked') ? 'border border-red-400 dark:border-red-500 text-red-600 dark:text-red-400' : (((_global.bz_tpl_modal?.id) && (_global.bz_tpl_modal?.has_content)) ? (_global.bz_tpl_modal?.status ?? 'draft') : 'unwritten') === 'dormant' ? 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300')}}"
}, },
@@ -2404,7 +2404,7 @@
"id": "bizppurio_tpl_request_comment", "id": "bizppurio_tpl_request_comment",
"type": "basic", "type": "basic",
"name": "Div", "name": "Div",
"comment": "검수자 전달 의견 — kapi template/request 의 comment(≤500). 카카오 심사 가이드가 요구하는 변수 예시값 등을 검수자에게 전할 유일한 통로(PO 결정 2026-08-23 §18.7). 신청 시에만 전송되며 행에 저장되지 않는다.", "comment": "검수자 전달 의견 — kapi template/request 의 comment(≤500). 카카오 심사 가이드가 요구하는 변수 예시값 등을 검수자에게 전할 유일한 통로(제품 결정 2026-08-23 §18.7). 신청 시에만 전송되며 행에 저장되지 않는다.",
"children": [ "children": [
{ {
"type": "basic", "type": "basic",
@@ -221,7 +221,7 @@ describe('A-2 결과 컬럼 주입 — 렌더', () => {
}); });
it('검수 모드 발송 건은 상태 라벨(발송중) 대신 검수 라벨을 렌더한다', async () => { it('검수 모드 발송 건은 상태 라벨(발송중) 대신 검수 라벨을 렌더한다', async () => {
// is_test_mode=true 이면 status='sent'(발송중)이어도 검수 라벨로 대체 표시한다(PO 확정 — // is_test_mode=true 이면 status='sent'(발송중)이어도 검수 라벨로 대체 표시한다(제품 결정 —
// "발송중" 문구 자체가 검수 모드에서는 오해 소지라 배지 병기가 아니라 라벨 교체). // "발송중" 문구 자체가 검수 모드에서는 오해 소지라 배지 병기가 아니라 라벨 교체).
const utils = createLayoutTest(buildProbe(), { componentRegistry: registry as any, locale: 'ko' }); const utils = createLayoutTest(buildProbe(), { componentRegistry: registry as any, locale: 'ko' });
utils.mockApi('notificationLogs', { response: logs([4]) }); utils.mockApi('notificationLogs', { response: logs([4]) });
@@ -11,7 +11,7 @@
* 반대로 데이터소스 id 는 치환 없이도 3면 동일해야 한다 — notification_row_footer.json 과 * 반대로 데이터소스 id 는 치환 없이도 3면 동일해야 한다 — notification_row_footer.json 과
* notification_template_form_sections.json(전역 매칭, 3면 공유 1본)이 이 이름들을 공유 참조하기 때문이다. * notification_template_form_sections.json(전역 매칭, 3면 공유 1본)이 이 이름들을 공유 참조하기 때문이다.
* 편집 UI(작성 폼·SMS·저장)는 코어 [편집] 모달의 extension_point 에 주입되는 공유 1본이라 * 편집 UI(작성 폼·SMS·저장)는 코어 [편집] 모달의 extension_point 에 주입되는 공유 1본이라
* 면 패리티 대상이 아니다(PO 결정 2026-08-23). * 면 패리티 대상이 아니다(제품 결정 2026-08-23).
* *
* 세 오버레이가 전문 동일하다는 것이 board/ecommerce 면의 화면 효과를 떠받치는 근거다 — * 세 오버레이가 전문 동일하다는 것이 board/ecommerce 면의 화면 효과를 떠받치는 근거다 —
* core 면에서 단언한 것들(탭 통합·행 하단 UI·업로드 잠금·SMS 언어 탭)이 두 면에도 있다는 * core 면에서 단언한 것들(탭 통합·행 하단 UI·업로드 잠금·SMS 언어 탭)이 두 면에도 있다는
@@ -70,7 +70,7 @@ const normalizedCore = JSON.stringify(stripComments(coreOverlay));
/** 3면 공유 이름(치환 없이 동일해야 하는 축) */ /** 3면 공유 이름(치환 없이 동일해야 하는 축) */
const SHARED_DATA_SOURCE_IDS = ['bizppurioTemplates', 'bizppurioCategories', 'bizppurioProfiles']; const SHARED_DATA_SOURCE_IDS = ['bizppurioTemplates', 'bizppurioCategories', 'bizppurioProfiles'];
// 모달·전역 상태(bz_*)는 오버레이가 아니라 3면 공유 1본(notification_template_form_sections.json / // 모달·전역 상태(bz_*)는 오버레이가 아니라 3면 공유 1본(notification_template_form_sections.json /
// _footer_actions.json)이 소유한다(PO 결정 2026-08-23 — 편집 모달 통합). 오버레이는 모달을 등록하지 않는다. // _footer_actions.json)이 소유한다(제품 결정 2026-08-23 — 편집 모달 통합). 오버레이는 모달을 등록하지 않는다.
describe.each(FIXTURES)('$label 오버레이 — 3면 패리티', ({ label, overlay, targetLayout, targetId, idPrefix }) => { describe.each(FIXTURES)('$label 오버레이 — 3면 패리티', ({ label, overlay, targetLayout, targetId, idPrefix }) => {
it(`target_layout=${targetLayout} 이고 extension_point 키가 없다(overlay 전용)`, () => { it(`target_layout=${targetLayout} 이고 extension_point 키가 없다(overlay 전용)`, () => {
@@ -5,7 +5,7 @@
// (§14.2 T7) — 리터럴 비교는 조건을 잘못 고쳐도 기대값을 함께 고치면 green 이라 회귀를 // (§14.2 T7) — 리터럴 비교는 조건을 잘못 고쳐도 기대값을 함께 고치면 green 이라 회귀를
// 잡지 못한다. // 잡지 못한다.
/** /**
* 알림 설정 '비즈뿌리오' 통합 탭 — [편집] 모달 통합 UI 구조 검증 (#597, PO 결정 2026-08-23) * 알림 설정 '비즈뿌리오' 통합 탭 — [편집] 모달 통합 UI 구조 검증 (#597, 제품 결정 2026-08-23)
* *
* @effects row_footer_shows_status_summary_only, approval_badge_two_tier, edit_modal_hosts_alimtalk_and_sms_sections, * @effects row_footer_shows_status_summary_only, approval_badge_two_tier, edit_modal_hosts_alimtalk_and_sms_sections,
* unified_save_chain_alimtalk_then_core_recipients, compose_modal_switches_conditional_fields_by_type, * unified_save_chain_alimtalk_then_core_recipients, compose_modal_switches_conditional_fields_by_type,
@@ -239,7 +239,7 @@ describe('lifecycle UI — 행 하단은 상태 요약만 (버튼 0)', () => {
expect(raw).not.toContain('.slice(0, 40)'); expect(raw).not.toContain('.slice(0, 40)');
}); });
it('SMS 상태 3종은 문구가 아니라 배지다 — 사용=초록/미사용=회색(승인 배지와 같은 모양), 본문 미설정=외곽선 칩 (PO 지시 2026-08-23)', () => { it('SMS 상태 3종은 문구가 아니라 배지다 — 사용=초록/미사용=회색(승인 배지와 같은 모양), 본문 미설정=외곽선 칩 (제품 결정 2026-08-23)', () => {
const APPROVED_SET = 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200'; const APPROVED_SET = 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-200';
const GRAY_SET = 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300'; const GRAY_SET = 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300';
for (const [id, field] of [['bizppurio_row_sms_fallback_badge', 'fallback_sms_enabled'], ['bizppurio_row_sms_only_badge', 'sms_only']] as const) { for (const [id, field] of [['bizppurio_row_sms_fallback_badge', 'fallback_sms_enabled'], ['bizppurio_row_sms_only_badge', 'sms_only']] as const) {
@@ -613,7 +613,7 @@ describe('lifecycle UI — 통합 [저장] 체인 (푸터 액션)', () => {
}); });
}); });
describe('lifecycle UI — 검수자 전달 의견 입력란 (#597 §18.7, PO 결정 2026-08-23)', () => { describe('lifecycle UI — 검수자 전달 의견 입력란 (#597 §18.7, 제품 결정 2026-08-23)', () => {
const editor = findById(sectionsRoot, 'bizppurio_tpl_editor') as AnyNode; const editor = findById(sectionsRoot, 'bizppurio_tpl_editor') as AnyNode;
const block = findById(editor, 'bizppurio_tpl_request_comment') as AnyNode; const block = findById(editor, 'bizppurio_tpl_request_comment') as AnyNode;
@@ -661,7 +661,7 @@ describe('lifecycle UI — 검수자 전달 의견 입력란 (#597 §18.7, PO
return null; return null;
}; };
it('바로연결 블록(헤더+목록) 바로 뒤 형제로 놓이고, 라벨+[바로연결 추가] flex 행 안에 끼지 않는다 (회귀: PO 화면 지적 2026-08-23)', () => { it('바로연결 블록(헤더+목록) 바로 뒤 형제로 놓이고, 라벨+[바로연결 추가] flex 행 안에 끼지 않는다 (회귀: 화면 검수 지적 2026-08-23)', () => {
const chain = ancestorsOf(editor, 'bizppurio_tpl_request_comment') as AnyNode[]; const chain = ancestorsOf(editor, 'bizppurio_tpl_request_comment') as AnyNode[];
expect(chain).toBeTruthy(); expect(chain).toBeTruthy();
const parent = chain[chain.length - 1]; const parent = chain[chain.length - 1];
@@ -159,7 +159,7 @@ describe('manage — 행 액션(알림 설정 탭 행 하단과 동일 엔드포
/** /**
* 관리 화면(bzRow)과 코어 [편집] 모달 섹션(_global.bz_tpl_modal)의 노출 조건을 같은 상태 * 관리 화면(bzRow)과 코어 [편집] 모달 섹션(_global.bz_tpl_modal)의 노출 조건을 같은 상태
* 집합에 **실제로 태워** 대조한다(PO 결정 2026-08-23 — 행 하단 버튼 폐지, 섹션이 SSoT). * 집합에 **실제로 태워** 대조한다(제품 결정 2026-08-23 — 행 하단 버튼 폐지, 섹션이 SSoT).
* *
* 문자열 동일성 단언은 두 면의 식이 실제로 같은 결과를 내는지 증명하지 못한다 — 두 식을 * 문자열 동일성 단언은 두 면의 식이 실제로 같은 결과를 내는지 증명하지 못한다 — 두 식을
* 각자의 컨텍스트로 평가해 상태별 결과가 일치하는지를 본다. * 각자의 컨텍스트로 평가해 상태별 결과가 일치하는지를 본다.
@@ -375,7 +375,7 @@ describe('manage 모달 — 이미지 업로드 배선·SMS 언어 탭 (#597 §1
}); });
}); });
describe('manage 모달 — 검수자 전달 의견 (#597 §18.7, PO 결정 2026-08-23)', () => { describe('manage 모달 — 검수자 전달 의견 (#597 §18.7, 제품 결정 2026-08-23)', () => {
const composeModal = findById(modalRoot, 'modal_bizppurio_template') as AnyNode; const composeModal = findById(modalRoot, 'modal_bizppurio_template') as AnyNode;
const sectionsRoot = { children: (sections as { components?: AnyNode[] }).components ?? [] } as AnyNode; const sectionsRoot = { children: (sections as { components?: AnyNode[] }).components ?? [] } as AnyNode;
const collectHandlers = (node: unknown, acc: AnyNode[] = []): AnyNode[] => { const collectHandlers = (node: unknown, acc: AnyNode[] = []): AnyNode[] => {
@@ -411,7 +411,7 @@ describe('manage 모달 — 검수자 전달 의견 (#597 §18.7, PO 결정 2026
for (const r of rowReqs) expect((r.params as { body?: unknown }).body).toBeUndefined(); for (const r of rowReqs) expect((r.params as { body?: unknown }).body).toBeUndefined();
}); });
it('바로연결 블록(헤더+목록) 바로 뒤 형제로 놓이고, 라벨+[바로연결 추가] flex 행 안에 끼지 않는다 (회귀: PO 화면 지적 2026-08-23)', () => { it('바로연결 블록(헤더+목록) 바로 뒤 형제로 놓이고, 라벨+[바로연결 추가] flex 행 안에 끼지 않는다 (회귀: 화면 검수 지적 2026-08-23)', () => {
const ancestorsOf = (root: AnyNode, id: string, anc: AnyNode[] = []): AnyNode[] | null => { const ancestorsOf = (root: AnyNode, id: string, anc: AnyNode[] = []): AnyNode[] | null => {
for (const c of (root.children as AnyNode[] | undefined) ?? []) { for (const c of (root.children as AnyNode[] | undefined) ?? []) {
if (c.id === id) return anc.concat(root); if (c.id === id) return anc.concat(root);
@@ -452,7 +452,7 @@ describe('manage 모달 — 검수자 전달 의견 (#597 §18.7, PO 결정 2026
}); });
}); });
describe('manage — 목록 SMS 열은 상태 배지 (행 하단과 같은 규칙, PO 지시 2026-08-23)', () => { describe('manage — 목록 SMS 열은 상태 배지 (행 하단과 같은 규칙, 제품 결정 2026-08-23)', () => {
// iteration(bzRow) 안이라 정적 id 를 둘 수 없다 — 텍스트 식으로 찾는다 // iteration(bzRow) 안이라 정적 id 를 둘 수 없다 — 텍스트 식으로 찾는다
const badge = findAllByName(manageView, 'Span').find((n) => String(n.text).includes("manage.sms_fallback') : $t('sirsoft-message_bizppurio.template.row.off')")) as AnyNode; const badge = findAllByName(manageView, 'Span').find((n) => String(n.text).includes("manage.sms_fallback') : $t('sirsoft-message_bizppurio.template.row.off')")) as AnyNode;
const evalCell = (expr: string, row: Record<string, unknown>): unknown => { const evalCell = (expr: string, row: Record<string, unknown>): unknown => {
@@ -475,7 +475,7 @@ describe('manage — 목록 SMS 열은 상태 배지 (행 하단과 같은 규
}); });
}); });
describe('manage — 목록 모바일 카드 전환 + 도구줄 버튼 일관성 (PO 지시 2026-08-24)', () => { describe('manage — 목록 모바일 카드 전환 + 도구줄 버튼 일관성 (제품 결정 2026-08-24)', () => {
const header = findById(manageView, 'bz_manage_list_header') as AnyNode; const header = findById(manageView, 'bz_manage_list_header') as AnyNode;
const rowsWrap = findById(manageView, 'bz_manage_list_rows') as AnyNode; const rowsWrap = findById(manageView, 'bz_manage_list_rows') as AnyNode;
const findIterationRow = (n: AnyNode | null): AnyNode | null => { const findIterationRow = (n: AnyNode | null): AnyNode | null => {
@@ -36,7 +36,7 @@ import { describe, it, expect } from 'vitest';
const BASE = path.resolve(__dirname, '../../../..'); const BASE = path.resolve(__dirname, '../../../..');
const FILES = [ const FILES = [
// 3면 공유 1본 — 코어 [편집] 모달 푸터에 주입되는 통합 저장/검수 신청 버튼(PO 결정 2026-08-23) // 3면 공유 1본 — 코어 [편집] 모달 푸터에 주입되는 통합 저장/검수 신청 버튼(제품 결정 2026-08-23)
'resources/extensions/notification_template_form_footer_actions.json', 'resources/extensions/notification_template_form_footer_actions.json',
// 플러그인 관리 화면의 자기 모달 // 플러그인 관리 화면의 자기 모달
'resources/layouts/admin/plugin_settings.json', 'resources/layouts/admin/plugin_settings.json',
@@ -8,7 +8,7 @@
"meta": { "meta": {
"title": "$t:sirsoft-message_bizppurio.settings.title", "title": "$t:sirsoft-message_bizppurio.settings.title",
"description": "$t:sirsoft-message_bizppurio.settings.description", "description": "$t:sirsoft-message_bizppurio.settings.description",
"_e2e_allow": "e2e:allow (§529) 연결 확인 필드·hasChanges 가드 추가는 레이아웃 회귀테스트(plugin_settings.test.tsx) + PHPUnit Feature(TokenCheckEndpointTest·InvalidateTokenOnSettingsSaveListenerTest)로 커버. 실연동 Playwright는 계획서 §9 방침에 따라 PO 직접 수행(인증·발송 환경 필요) 단계에서 도입 예정 — tests/scenarios/plugin-settings.yaml 상단 audit:allow 사유 참고." "_e2e_allow": "e2e:allow (§529) 연결 확인 필드·hasChanges 가드 추가는 레이아웃 회귀테스트(plugin_settings.test.tsx) + PHPUnit Feature(TokenCheckEndpointTest·InvalidateTokenOnSettingsSaveListenerTest)로 커버. 실연동 Playwright는 계획서 §9 방침에 따라 직접 수행(인증·발송 환경 필요) 단계에서 도입 예정 — tests/scenarios/plugin-settings.yaml 상단 audit:allow 사유 참고."
}, },
"init_actions": [ "init_actions": [
{ {
@@ -1771,7 +1771,7 @@
} }
} }
], ],
"comment": "새로고침 — 코어 관리자 목록 관례(btn-icon + 회전 아이콘)와 통일 (PO 지시 2026-08-24).", "comment": "새로고침 — 코어 관리자 목록 관례(btn-icon + 회전 아이콘)와 통일 (제품 결정 2026-08-24).",
"children": [ "children": [
{ {
"type": "basic", "type": "basic",
@@ -1804,7 +1804,7 @@
"id": "bz_manage_list_header", "id": "bz_manage_list_header",
"type": "basic", "type": "basic",
"name": "Div", "name": "Div",
"comment": "데스크톱 grid 헤더 — portable 에서는 카드가 자기 서술이므로 숨긴다 (PO 지시 2026-08-24 모바일 카드뷰).", "comment": "데스크톱 grid 헤더 — portable 에서는 카드가 자기 서술이므로 숨긴다 (제품 결정 2026-08-24 모바일 카드뷰).",
"props": { "props": {
"className": "grid grid-cols-12 gap-2 px-4 py-2.5 bg-gray-50 dark:bg-gray-900 text-xs font-medium text-gray-500 dark:text-gray-400" "className": "grid grid-cols-12 gap-2 px-4 py-2.5 bg-gray-50 dark:bg-gray-900 text-xs font-medium text-gray-500 dark:text-gray-400"
}, },
@@ -1994,7 +1994,7 @@
"props": { "props": {
"className": "col-span-1" "className": "col-span-1"
}, },
"comment": "SMS 발송 모드 배지 — 단독/대체(초록), 미사용(회색). 행 하단 상태 배지와 같은 규칙(PO 지시 2026-08-23).", "comment": "SMS 발송 모드 배지 — 단독/대체(초록), 미사용(회색). 행 하단 상태 배지와 같은 규칙(제품 결정 2026-08-23).",
"children": [ "children": [
{ {
"type": "basic", "type": "basic",
@@ -4238,7 +4238,7 @@
"id": "bz_manage_request_comment", "id": "bz_manage_request_comment",
"type": "basic", "type": "basic",
"name": "Div", "name": "Div",
"comment": "검수자 전달 의견 — kapi template/request 의 comment(≤500). 카카오 심사 가이드가 요구하는 변수 예시값 등을 검수자에게 전할 유일한 통로(PO 결정 2026-08-23 §18.7). 신청 시에만 전송되며 행에 저장되지 않는다.", "comment": "검수자 전달 의견 — kapi template/request 의 comment(≤500). 카카오 심사 가이드가 요구하는 변수 예시값 등을 검수자에게 전할 유일한 통로(제품 결정 2026-08-23 §18.7). 신청 시에만 전송되며 행에 저장되지 않는다.",
"children": [ "children": [
{ {
"type": "basic", "type": "basic",
@@ -7,7 +7,7 @@ namespace Plugins\Sirsoft\MessageBizppurio\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
/** /**
* 알림톡 템플릿 검수 신청 요청 검증 (#597 §18.7 — PO 결정 2026-08-23). * 알림톡 템플릿 검수 신청 요청 검증 (#597 §18.7 — 제품 결정 2026-08-23).
* *
* 카카오 심사 가이드는 템플릿 변수마다 '예시 텍스트' 를 요구하지만 kapi `template/add` 에는 * 카카오 심사 가이드는 템플릿 변수마다 '예시 텍스트' 를 요구하지만 kapi `template/add` 에는
* 예시 필드가 없다. 검수자에게 무언가를 전달할 유일한 통로가 `template/request` 의 * 예시 필드가 없다. 검수자에게 무언가를 전달할 유일한 통로가 `template/request` 의
@@ -22,7 +22,7 @@ use Plugins\Sirsoft\MessageBizppurio\Services\DispatchLinkContext;
* - core.notification_log.after_log_sent — 발송 성공 로그 생성 직후(NotificationLog 전달) * - core.notification_log.after_log_sent — 발송 성공 로그 생성 직후(NotificationLog 전달)
* - core.notification_log.after_log_failed — 발송 실패 로그 생성 직후(NotificationLog 전달) * - core.notification_log.after_log_failed — 발송 실패 로그 생성 직후(NotificationLog 전달)
* *
* 매칭 방식(A안, PO 확정): 복합키 근사 매칭이 아니라 발송 사이클 refkey 직접 표식이다. * 매칭 방식(A안, 제품 결정): 복합키 근사 매칭이 아니라 발송 사이클 refkey 직접 표식이다.
* 같은 채널 발송 한 사이클에서 우리 드라이버 send() 가 dispatch(refkey) 를 먼저 만들고 * 같은 채널 발송 한 사이클에서 우리 드라이버 send() 가 dispatch(refkey) 를 먼저 만들고
* DispatchLinkContext 에 refkey 를 남긴 뒤, 곧바로 이 로그 훅이 발화한다. 여기서 그 refkey 를 * DispatchLinkContext 에 refkey 를 남긴 뒤, 곧바로 이 로그 훅이 발화한다. 여기서 그 refkey 를
* 꺼내(consume) 그 dispatch 에 방금 만들어진 코어 로그 id 를 기록한다. * 꺼내(consume) 그 dispatch 에 방금 만들어진 코어 로그 id 를 기록한다.
@@ -271,7 +271,7 @@ class RegisterNotificationChannelsListener implements HookListenerInterface
* - alimtalk.tab_channels: 이 목록 중 하나라도 활성 저장이면 탭을 노출한다. * - alimtalk.tab_channels: 이 목록 중 하나라도 활성 저장이면 탭을 노출한다.
* - alimtalk.tab_label_key: 탭 라벨용 프론트 lang 키($t 해석 — 카드 라벨(name)과 분리). * - alimtalk.tab_label_key: 탭 라벨용 프론트 lang 키($t 해석 — 카드 라벨(name)과 분리).
* - alimtalk.hidden_template_editor: 코어 알림 템플릿 [편집] 모달의 제목/본문 입력·미리보기· * - alimtalk.hidden_template_editor: 코어 알림 템플릿 [편집] 모달의 제목/본문 입력·미리보기·
* 코어 [저장]을 숨긴다(PO 결정 2026-08-23). 알림톡 본문은 카카오 규격(#{var}, 유형별 필드)이라 * 코어 [저장]을 숨긴다(제품 결정 2026-08-23). 알림톡 본문은 카카오 규격(#{var}, 유형별 필드)이라
* 코어 본문과 다른 편집기가 필요하고, 그 편집기·통합 저장은 플러그인이 같은 모달의 * 코어 본문과 다른 편집기가 필요하고, 그 편집기·통합 저장은 플러그인이 같은 모달의
* extension_point(notification_template_form_sections / _footer_actions)에 주입한다. * extension_point(notification_template_form_sections / _footer_actions)에 주입한다.
* 코어 채널 템플릿 행(제목/본문)은 발송에 쓰이지 않으므로 숨겨도 동작 손실이 없다. * 코어 채널 템플릿 행(제목/본문)은 발송에 쓰이지 않으므로 숨겨도 동작 손실이 없다.
@@ -194,7 +194,7 @@ class BizppurioTemplateService
* add 성공 시점에만 template_code 를 행에 확정하므로, add 자체가 실패하면 다음 * add 성공 시점에만 template_code 를 행에 확정하므로, add 자체가 실패하면 다음
* 시도에서 다시 채번부터 진행된다. * 시도에서 다시 채번부터 진행된다.
* - 검수자 전달 의견(comment)은 kapi request 에만 싣고 행에 저장하지 않는다 — 카카오 심사 * - 검수자 전달 의견(comment)은 kapi request 에만 싣고 행에 저장하지 않는다 — 카카오 심사
* 가이드가 요구하는 변수 '예시 텍스트' 를 전할 유일한 통로다(PO 결정 2026-08-23, §18.7). * 가이드가 요구하는 변수 '예시 텍스트' 를 전할 유일한 통로다(제품 결정 2026-08-23, §18.7).
* *
* @param BizppurioTemplate $template 대상 행 * @param BizppurioTemplate $template 대상 행
* @param string|null $comment 검수자 전달 의견 (≤500, 비어 있으면 미전달) * @param string|null $comment 검수자 전달 의견 (≤500, 비어 있으면 미전달)
@@ -15,7 +15,7 @@ use Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException;
* *
* `/v1/token` 을 Basic 인증(계정:암호 Base64)으로 호출해 Bearer 토큰을 발급받고, * `/v1/token` 을 Basic 인증(계정:암호 Base64)으로 호출해 Bearer 토큰을 발급받고,
* 확장 도메인 캐시(CacheInterface)에 저장한다. 토큰 유효 시간은 24시간이나, 만료 * 확장 도메인 캐시(CacheInterface)에 저장한다. 토큰 유효 시간은 24시간이나, 만료
* 경계 재발급 리스크를 피하기 위해 23시간 TTL 로 캐시한다(PO 결정, 계획서 §5). * 경계 재발급 리스크를 피하기 위해 23시간 TTL 로 캐시한다(제품 결정, 계획서 §5).
* *
* 발송 응답이 3002(토큰무효)/3005(인증정보무효)를 반환하면 캐시를 forget 하고 * 발송 응답이 3002(토큰무효)/3005(인증정보무효)를 반환하면 캐시를 forget 하고
* 1회 재발급한다(BizppurioApiClient 가 재시도 트리거). CacheInterface 는 * 1회 재발급한다(BizppurioApiClient 가 재시도 트리거). CacheInterface 는
@@ -610,7 +610,7 @@ class BizppurioTemplateControllerTest extends PluginTestCase
} }
/** /**
* 검수자 전달 의견(comment)은 선택 입력이며 kapi request 로만 전달된다 (#597 §18.7 — PO 결정 2026-08-23). * 검수자 전달 의견(comment)은 선택 입력이며 kapi request 로만 전달된다 (#597 §18.7 — 제품 결정 2026-08-23).
* *
* 500자 초과는 FormRequest 가 kapi 호출 전에 422 로 끊고(행 상태 불변), 유효한 의견은 * 500자 초과는 FormRequest 가 kapi 호출 전에 422 로 끊고(행 상태 불변), 유효한 의견은
* `template/request` 의 comment 로 원문 그대로 실린다. * `template/request` 의 comment 로 원문 그대로 실린다.
@@ -11,7 +11,7 @@
* sms_body_presence_and_preview_resolved_by_server, inspection_request_carries_reviewer_comment * sms_body_presence_and_preview_resolved_by_server, inspection_request_carries_reviewer_comment
* *
* 계획서 §6.3 이 요구한 7대 축(T1~T7) + 선택 축(T8~T10) + 회귀 축(R1~R6)을 브라우저에서 실측한다. * 계획서 §6.3 이 요구한 7대 축(T1~T7) + 선택 축(T8~T10) + 회귀 축(R1~R6)을 브라우저에서 실측한다.
* PO 결정(2026-08-23, 계획서 §18)으로 알림톡 작성·SMS 본문 편집은 행 하단 버튼/별도 모달이 아니라 * 제품 결정(2026-08-23, 계획서 §18)으로 알림톡 작성·SMS 본문 편집은 행 하단 버튼/별도 모달이 아니라
* 코어 [편집] 모달 안의 섹션으로 통합됐다 — 모달 진입은 언제나 행의 [편집] 이고, 저장은 하단 [저장] 하나다. * 코어 [편집] 모달 안의 섹션으로 통합됐다 — 모달 진입은 언제나 행의 [편집] 이고, 저장은 하단 [저장] 하나다.
* 이 매트릭스는 라운드 1~4 동안 "수행 증거 없음" 으로 남아 있었고, 그 근본 원인은 플러그인 * 이 매트릭스는 라운드 1~4 동안 "수행 증거 없음" 으로 남아 있었고, 그 근본 원인은 플러그인
* package.json 에 실행 진입점(test:e2e)이 없었다는 것이다 — 라운드 5 에서 진입점을 만들고 * package.json 에 실행 진입점(test:e2e)이 없었다는 것이다 — 라운드 5 에서 진입점을 만들고
@@ -479,7 +479,7 @@ test.describe('T4 제출/응답 + T10 경계', () => {
// 서버의 원자 선점(claimForInspection)이며, 두 번째 신청은 422 로 거부된다. // 서버의 원자 선점(claimForInspection)이며, 두 번째 신청은 422 로 거부된다.
const modal2 = await openEditModal(page, 'password_changed'); const modal2 = await openEditModal(page, 'password_changed');
await modal2.locator('textarea[name="bz_template_content"]').fill('중복 신청 테스트 #{site_name}'); await modal2.locator('textarea[name="bz_template_content"]').fill('중복 신청 테스트 #{site_name}');
// 검수자 전달 의견(PO 결정 2026-08-23 §18.7) — 신청 요청 본문의 comment 로 실려야 한다. // 검수자 전달 의견(제품 결정 2026-08-23 §18.7) — 신청 요청 본문의 comment 로 실려야 한다.
const REVIEW_COMMENT = '변수 예시: #{site_name}=G7 데모 사이트'; const REVIEW_COMMENT = '변수 예시: #{site_name}=G7 데모 사이트';
await modal2.locator('textarea[name="bz_request_comment"]').fill(REVIEW_COMMENT); await modal2.locator('textarea[name="bz_request_comment"]').fill(REVIEW_COMMENT);
const requestBodies: string[] = []; const requestBodies: string[] = [];
@@ -14,7 +14,7 @@
* 4. 플러그인 관리 화면 — 상태 필터가 URL(bz_status)에 보존되는 목록 왕복(mergeQuery) * 4. 플러그인 관리 화면 — 상태 필터가 URL(bz_status)에 보존되는 목록 왕복(mergeQuery)
* *
* 카카오 API(kapi) 실호출이 필요한 검수 신청·동기화 경로는 브라우저 E2E 범위 밖이다 * 카카오 API(kapi) 실호출이 필요한 검수 신청·동기화 경로는 브라우저 E2E 범위 밖이다
* (자격증명·실검수 필요 — PHPUnit 이 Http::fake 로 전수 가드, 실연동은 PO 수행 단계). * (자격증명·실검수 필요 — PHPUnit 이 Http::fake 로 전수 가드, 실연동은 별도 수행 단계).
* *
* 계획서 §6.2 의 "작성→신청→(kapi mock 승인)→발송 배지" 왕복 축을 여기에 두지 않은 이유는 * 계획서 §6.2 의 "작성→신청→(kapi mock 승인)→발송 배지" 왕복 축을 여기에 두지 않은 이유는
* 구조적이다: kapi 호출은 서버(PHP)에서 서버로 나가고, Playwright 의 route 가로채기는 * 구조적이다: kapi 호출은 서버(PHP)에서 서버로 나가고, Playwright 의 route 가로채기는
@@ -82,7 +82,7 @@ test.describe('비즈뿌리오 통합 탭 (코어 알림 설정)', () => {
await expect(firstRow.getByText(/승인됨|미승인/).first()).toBeVisible(); await expect(firstRow.getByText(/승인됨|미승인/).first()).toBeVisible();
await expect(firstRow.getByText('대체 SMS', { exact: false }).first()).toBeVisible(); await expect(firstRow.getByText('대체 SMS', { exact: false }).first()).toBeVisible();
await expect(firstRow.getByText('SMS 단독', { exact: false }).first()).toBeVisible(); await expect(firstRow.getByText('SMS 단독', { exact: false }).first()).toBeVisible();
// SMS 사용 여부는 문구("대체 SMS: 미사용")가 아니라 배지(사용/미사용)로 렌더된다 (PO 지시 2026-08-23) // SMS 사용 여부는 문구("대체 SMS: 미사용")가 아니라 배지(사용/미사용)로 렌더된다 (제품 결정 2026-08-23)
expect(await firstRow.locator('span.rounded', { hasText: /^(사용|미사용)$/ }).count()).toBeGreaterThanOrEqual(2); expect(await firstRow.locator('span.rounded', { hasText: /^(사용|미사용)$/ }).count()).toBeGreaterThanOrEqual(2);
expect(await firstRow.getByText(/대체 SMS:|SMS 단독:/).count()).toBe(0); expect(await firstRow.getByText(/대체 SMS:|SMS 단독:/).count()).toBe(0);
expect(await rows.locator('button').count()).toBe(0); expect(await rows.locator('button').count()).toBe(0);
@@ -117,7 +117,7 @@ test.describe('비즈뿌리오 통합 탭 (코어 알림 설정)', () => {
await expect(modal.locator('textarea[name="bz_template_content"]')).toBeVisible(); await expect(modal.locator('textarea[name="bz_template_content"]')).toBeVisible();
// 검수자 전달 의견 입력란은 바로연결 블록 아래 한 줄을 통째로 차지한다 // 검수자 전달 의견 입력란은 바로연결 블록 아래 한 줄을 통째로 차지한다
// (회귀: 라벨+[바로연결 추가] flex 행 안에 끼어 라벨이 세로로 찌그러짐 — PO 화면 지적 2026-08-23) // (회귀: 라벨+[바로연결 추가] flex 행 안에 끼어 라벨이 세로로 찌그러짐 — 화면 검수 지적 2026-08-23)
const commentBox = modal.locator('textarea[name="bz_request_comment"]'); const commentBox = modal.locator('textarea[name="bz_request_comment"]');
await expect(commentBox).toBeVisible(); await expect(commentBox).toBeVisible();
const [quickAddBox, commentBounds, bodyBox] = await Promise.all([ const [quickAddBox, commentBounds, bodyBox] = await Promise.all([
@@ -170,7 +170,7 @@ test.describe('비즈뿌리오 알림 템플릿 관리 (플러그인 설정)', (
await expect(manageView).toBeVisible({ timeout: 30_000 }); await expect(manageView).toBeVisible({ timeout: 30_000 });
// 목록 SMS 열은 평문(단독/대체/-)이 아니라 상태 배지(단독·대체=초록, 미사용=회색) — 행 하단과 같은 규칙 (PO 지시 2026-08-23) // 목록 SMS 열은 평문(단독/대체/-)이 아니라 상태 배지(단독·대체=초록, 미사용=회색) — 행 하단과 같은 규칙 (제품 결정 2026-08-23)
await expect(manageView.locator('span.rounded', { hasText: /^(단독|대체|미사용)$/ }).first()).toBeVisible({ timeout: 15_000 }); await expect(manageView.locator('span.rounded', { hasText: /^(단독|대체|미사용)$/ }).first()).toBeVisible({ timeout: 15_000 });
// 상태 필터 변경 → URL bz_status 반영 (mergeQuery — tab 파라미터 유지) // 상태 필터 변경 → URL bz_status 반영 (mergeQuery — tab 파라미터 유지)
@@ -735,7 +735,7 @@ class BizppurioTemplateServiceTest extends PluginTestCase
} }
/** /**
* 검수자 전달 의견은 kapi request 의 comment 로만 실린다 (#597 §18.7 — PO 결정 2026-08-23). * 검수자 전달 의견은 kapi request 의 comment 로만 실린다 (#597 §18.7 — 제품 결정 2026-08-23).
* *
* 카카오 심사 가이드가 요구하는 변수 '예시 텍스트' 를 전할 유일한 통로가 request comment 다. * 카카오 심사 가이드가 요구하는 변수 '예시 텍스트' 를 전할 유일한 통로가 request comment 다.
* 등록(add) 페이로드에는 섞이지 않고, 행에도 남지 않는다. * 등록(add) 페이로드에는 섞이지 않고, 행에도 남지 않는다.
@@ -16,7 +16,7 @@ description: |
승인 전이 시 content 를 approved_content 로 동결(발송 SSoT), 반려 시 comments 저장 승인 전이 시 content 를 approved_content 로 동결(발송 SSoT), 반려 시 comments 저장
- 화면: 3면(코어/게시판/이커머스) '비즈뿌리오' 통합 탭(sms 탭 숨김 + tab_channels 규칙) - 화면: 3면(코어/게시판/이커머스) '비즈뿌리오' 통합 탭(sms 탭 숨김 + tab_channels 규칙)
행 하단 상태 요약(승인 2단 배지) + 코어 [편집] 모달에 녹인 알림톡·SMS 섹션(통합 저장, 행 하단 상태 요약(승인 2단 배지) + 코어 [편집] 모달에 녹인 알림톡·SMS 섹션(통합 저장,
4×4 유형) + 플러그인 관리 화면(DB 목록). PO 결정 2026-08-23: 별도 작성/SMS 모달 폐지 4×4 유형) + 플러그인 관리 화면(DB 목록). 제품 결정 2026-08-23: 별도 작성/SMS 모달 폐지
axis 설계 노트: axis 설계 노트:
- content_variant 는 카카오 메시지유형×강조유형의 대표 변형 4종이다 — 유형별 조건부 - content_variant 는 카카오 메시지유형×강조유형의 대표 변형 4종이다 — 유형별 조건부
@@ -75,7 +75,7 @@ effects:
- approval_transition_freezes_content_into_approved_content - approval_transition_freezes_content_into_approved_content
- rejection_transition_stores_comments_detail - rejection_transition_stores_comments_detail
- manual_sync_updates_single_row - manual_sync_updates_single_row
# 화면 (S5 — PO 결정 2026-08-23: 편집 모달 통합) # 화면 (S5 — 제품 결정 2026-08-23: 편집 모달 통합)
- bizppurio_tab_replaces_sms_and_alimtalk_tabs - bizppurio_tab_replaces_sms_and_alimtalk_tabs
- tab_visible_when_any_of_tab_channels_active - tab_visible_when_any_of_tab_channels_active
- row_footer_shows_status_summary_only - row_footer_shows_status_summary_only
@@ -85,7 +85,7 @@ effects:
- unified_save_chain_alimtalk_then_core_recipients - unified_save_chain_alimtalk_then_core_recipients
- compose_modal_switches_conditional_fields_by_type - compose_modal_switches_conditional_fields_by_type
- manage_screen_lists_db_rows_with_merge_query_round_trip - manage_screen_lists_db_rows_with_merge_query_round_trip
# 관리 목록 반응형 (PO 지시 2026-08-24) — portable 카드 전환 + 도구줄 btn 계열 통일 # 관리 목록 반응형 (제품 결정 2026-08-24) — portable 카드 전환 + 도구줄 btn 계열 통일
- manage_list_mobile_card_view_and_unified_toolbar - manage_list_mobile_card_view_and_unified_toolbar
- sms_modal_saves_body_via_delivery_upsert - sms_modal_saves_body_via_delivery_upsert
- sms_modal_edits_body_per_locale_tab - sms_modal_edits_body_per_locale_tab
@@ -9,7 +9,7 @@
# - 결과 조회 API(권한·검증·성공/실패/잔액부족/미매칭): DispatchResultEndpointTest. # - 결과 조회 API(권한·검증·성공/실패/잔액부족/미매칭): DispatchResultEndpointTest.
# - overlay 결과 컬럼 실제 렌더(성공/잔액부족/대체발송/빈셀 조건부 렌더): notification_log_result.test.tsx. # - overlay 결과 컬럼 실제 렌더(성공/잔액부족/대체발송/빈셀 조건부 렌더): notification_log_result.test.tsx.
# e2e_* 축(브라우저 왕복: 코어 이력 화면 진입→결과 컬럼 확인→회원 링크/배지)은 실제 발송·webhook # e2e_* 축(브라우저 왕복: 코어 이력 화면 진입→결과 컬럼 확인→회원 링크/배지)은 실제 발송·webhook
# 왕복 결과가 필요해, 계획서 §9 방침대로 PO 직접 수행하는 실연동 검증 단계에서 Playwright spec 을 # 왕복 결과가 필요해, 계획서 §9 방침대로 직접 수행하는 실연동 검증 단계에서 Playwright spec 을
# 그때 @scenario/@effects 로 이 매니페스트에 매핑한다. Vitest 렌더 테스트가 프론트 overlay 컬럼 # 그때 @scenario/@effects 로 이 매니페스트에 매핑한다. Vitest 렌더 테스트가 프론트 overlay 컬럼
# 주입·조건부 렌더를 가드하므로 이번 단계는 spec 미도입 # 주입·조건부 렌더를 가드하므로 이번 단계는 spec 미도입
# (frontend-change-requires-e2e 는 이 매니페스트의 e2e axis + Vitest 렌더 커버로 충족). # (frontend-change-requires-e2e 는 이 매니페스트의 e2e axis + Vitest 렌더 커버로 충족).
@@ -7,7 +7,7 @@
# TokenCheckEndpointTest, 저장 시 토큰 캐시 무효화는 실제 훅 체인(core.plugin_settings. # TokenCheckEndpointTest, 저장 시 토큰 캐시 무효화는 실제 훅 체인(core.plugin_settings.
# after_save) + 실제 CacheInterface 로 InvalidateTokenOnSettingsSaveListenerTest 가, # after_save) + 실제 CacheInterface 로 InvalidateTokenOnSettingsSaveListenerTest 가,
# verifyCredentials/describeFailure 는 BizppurioTokenServiceTest 가 각각 관찰한다. # verifyCredentials/describeFailure 는 BizppurioTokenServiceTest 가 각각 관찰한다.
# 런타임 Playwright spec 은 계획서 §9 방침에 따라 실연동 검증(PO 직접 수행, 인증·발송 # 런타임 Playwright spec 은 계획서 §9 방침에 따라 실연동 검증(직접 수행, 인증·발송
# 환경 필요) 단계에서 최초 도입하며, 그 시점에 @scenario/@effects docblock 완전 매핑을 # 환경 필요) 단계에서 최초 도입하며, 그 시점에 @scenario/@effects docblock 완전 매핑을
# 추가한다. 그때까지 매니페스트는 명세로 유지한다. # 추가한다. 그때까지 매니페스트는 명세로 유지한다.
@@ -5,7 +5,7 @@
# GuestPhoneExtractListenerTest(6) + ChannelRegistrationHookTest(4, 실제 훅 체인). # GuestPhoneExtractListenerTest(6) + ChannelRegistrationHookTest(4, 실제 훅 체인).
# 프론트 변경 없음(레이아웃 JSON/컴포넌트 미변경) → frontend-change-requires-e2e 비대상이라 # 프론트 변경 없음(레이아웃 JSON/컴포넌트 미변경) → frontend-change-requires-e2e 비대상이라
# Playwright spec 은 이번 Phase 미도입. 실발송 왕복(실제 API·webhook)은 계획서 §9 방침대로 # Playwright spec 은 이번 Phase 미도입. 실발송 왕복(실제 API·webhook)은 계획서 §9 방침대로
# PO 직접 수행하는 실연동 검증 단계에서 spec 최초 도입하며, 그때 @scenario/@effects 를 매핑한다. # 직접 수행하는 실연동 검증 단계에서 spec 최초 도입하며, 그때 @scenario/@effects 를 매핑한다.
feature: 비즈뿌리오 문자 채널 등록 + 자동발송 (Phase 3) feature: 비즈뿌리오 문자 채널 등록 + 자동발송 (Phase 3)
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.7] - 2026-08-22 ## [1.0.7] - 2026-08-24
### Added ### Added
@@ -4,7 +4,7 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.1.2] - 2026-08-22 ## [1.1.2] - 2026-08-24
### Changed ### Changed
@@ -8,7 +8,7 @@ description: |
키가 없으면 예외·경고 없이 원시 키 문자열이 제목으로 그대로 노출된다 키가 없으면 예외·경고 없이 원시 키 문자열이 제목으로 그대로 노출된다
(실사례: #481 이 sitemap_regenerated / sitemap_regenerate_failed 정의를 config 에만 (실사례: #481 이 sitemap_regenerated / sitemap_regenerate_failed 정의를 config 에만
추가하고 템플릿 라벨을 누락 — 2026-08-24 PO 제보). config/core.php 의 코어 정의 추가하고 템플릿 라벨을 누락 — 2026-08-24 제보). config/core.php 의 코어 정의
전수를 양 로케일 라벨과 자동 대조해 재발을 차단한다. 전수를 양 로케일 라벨과 자동 대조해 재발을 차단한다.
axes: axes: