Compare commits

..

179 Commits

Author SHA1 Message Date
ByungCheol 1b53af525e feat(account): 대출 실행금액 필드 추가 + 상환방식별 자동계산 분기
CI / build (push) Failing after 14m30s
- loan_amount(대출 실행 금액) 컬럼 추가(DB/도메인/DTO/mapper)
- 계좌 폼: 대출 실행 금액 입력 / 기록 시작 시 잔액(기존 openingBalance) 레이블 분리
- 계좌 카드: 실행금액·금리·상환방식 표시
- 상환 자동계산 3방식 분기:
  - 원리금균등: 납입금액 입력 → 이자(잔액×월이율) / 원금(납입-이자) 분리
  - 원금균등: 실행금액÷기간=월원금, 잔액×월이율=이자 자동계산
  - 만기일시: 잔액×월이율=이자만 자동계산, 원금 0
- 자동계산 후 이자·원금 수동 조정 가능

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 00:31:50 +09:00
ByungCheol f8e6c6b0ee feat(account): 대출 계좌 금리/상환방식 저장 + 상환 시 납입금액 자동분리
CI / build (push) Failing after 14m10s
- wallet 테이블에 loan_rate/loan_method/loan_months/loan_start 컬럼 추가(ALTER TABLE)
- 대출 계좌 폼에 연이자율(%), 상환방식(원리금균등/원금균등/만기일시), 기간, 시작일 입력 필드 추가
- 계좌 목록에 금리·상환방식 표시
- 상환 폼: 금리 설정된 대출 계좌 선택 시 납입금액 입력 → 이자/원금 자동계산
  - 원리금균등/원금균등: 잔액 × 연이율/12 = 이자, 납입액-이자 = 원금
  - 만기일시상환: 납입액 전체 이자로 처리
  - 자동계산 후 수동 조정 가능

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 00:02:10 +09:00
ByungCheol e1c077dccd fix(account): 소분류 표시 조건 — template 인라인으로 reactivity 보장
CI / build (push) Failing after 14m42s
rowContainsSelected 함수 대신 template에서 row.some(m => m.id === selectedMajorObj.id) 직접 평가.
함수 호출 시 의존성 추적 불안정 문제 해소.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:41:43 +09:00
ByungCheol 1e9ff0408c feat(account): 내역추가 분류 — 행 삽입형 아코디언 + 대/소분류 추가 버튼 분리
CI / build (push) Failing after 12m46s
- 대분류를 4개씩 행으로 렌더링(majorRows computed)
- 선택된 대분류가 속한 행 바로 아래 소분류 칩 삽입(rowContainsSelected)
- + 소분류 버튼: 소분류 행 내부, 선택된 대분류 하위로 추가
- + 대분류 버튼: 맨 아래, 선택 초기화 후 대분류로 추가
- 입력 placeholder에 추가 맥락 표시(대분류명 > 새 소분류)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:32:59 +09:00
ByungCheol b2f3b156b2 fix: 분류 소분류 영역 배경색 구분 — 회색 박스로 대분류와 시각적 분리
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:19:27 +09:00
ByungCheol 28ebe06767 fix: 분류 선택 UI — 세로 아코디언→가로 칩 그리드로 변경
- 대분류: flex-wrap 그리드(한 줄 4개)로 나열, 분류 수 증가해도 가로로 쌓임
- 대분류 선택 시 소분류가 구분선 아래 동일 그리드로 펼침
- 선택된 항목 초록 테두리·배경 강조

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:13:58 +09:00
ByungCheol afbec5e3c3 fix: 분류 아코디언 화면 교정 — 분류관리 롤백 · 내역추가 적용
- CategoryView(분류 관리): 잘못 적용된 대분류 아코디언 제거, 소분류 항상 표시로 복원
- AccountView(내역 추가/수정): 2단 드롭다운 → 대분류 아코디언으로 교체
  · 대분류 클릭 → 소분류 펼침(없으면 바로 선택)
  · 소분류 클릭 → 즉시 선택 / 선택 항목 초록 하이라이트
  · 수정 진입 시 해당 대분류 자동 펼침 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:23:08 +09:00
ByungCheol ee7a1fb471 docs(brand): 백로그에 AI 기능(입력 보조·재무 코멘트) 추가 — 출시 전 적용 희망 2026-06-30 23:02:41 +09:00
ByungCheol 1a8aa8e13c chore: versionCode 3 / 1.0.2 (v3) + 진행상황 문서 정리
CI / build (push) Failing after 10m53s
- 이번 세션 적용분(외화·월별예산·전체화면모달·아코디언·계좌셀렉트·잠금유예) 정리
- 현재 상태(비공개 테스트 게시·검토통과, 테스터 opt-in 대기)로 갱신

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:45:32 +09:00
ByungCheol e87bc244fe docs(brand): 월별 예산·복사 등 진행상황 갱신 2026-06-30 22:37:09 +09:00
ByungCheol f8369f5108 feat: 예산 월별 분리 + 전월/다음달 복사 버튼
CI / build (push) Failing after 12m14s
- budgets/createBudget에 year/month 전달, copyBudget 추가
- BudgetView: '전월 예산 가져오기' / '다음 달로 복사' 버튼
- 백업 export는 현재 월 예산을 담음

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:35:49 +09:00
ByungCheol 80d7b8d552 fix: 내역추가 계좌 셀렉트 — 안내문 종류별 표시 + 옵션 이름만
CI / build (push) Failing after 12m23s
- 계좌 선택 안내문을 선택한 종류에 맞게('계좌/카드/대출/증권 선택')
- 계좌·카드 옵션을 '이름 (은행)' → 이름만(없으면 은행/카드사) 표시로 간결화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:14:02 +09:00
ByungCheol 764436ebc9 fix: 분류 관리 탭 줄바꿈 — 순서변경 버튼 추가로 '지출분류' 두 줄 깨짐 수정
CI / build (push) Failing after 12m13s
- 탭 텍스트 white-space:nowrap, 탭 영역 flex:none(안 줄어듦)
- tabs-row flex-wrap: 좁으면 액션 버튼이 아래 줄로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:09:12 +09:00
ByungCheol 970e9e14ad feat: 내역 추가/수정 전체화면 모달 + 분류 대분류 아코디언
CI / build (push) Failing after 15m22s
- 내역 추가/수정 폼을 전체화면 모달로(바깥 영역 없음·× 로만 닫힘)
  → 입력 중 바깥 실수 터치로 닫히는 불편 해소. PC는 내용 폭 460px 가운데정렬
- 분류 화면: 대분류 삼각형(▶) 토글로 소분류 펼침/접힘(기본 접힘, 개수 뱃지)
  소분류 추가 시 해당 대분류 자동 펼침. 순서변경 모드에선 모두 펼침

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:06:02 +09:00
ByungCheol f996075da7 fix: 외화 입력폼 다듬기 — 자동 버튼 높이 일치 + 환율 4자리 반올림
CI / build (push) Failing after 15m22s
- 환율 input과 '↻ 자동' 버튼을 한 행(stretch)으로 묶어 높이 일치
- 자동조회 환율을 소수점 4자리로 반올림(입력폼 step·표시 일관)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:56:04 +09:00
ByungCheol 5795f328a1 fix: 앱 잠금 유예시간 도입 — 잦은 PIN 입력 불편 해소
CI / build (push) Failing after 12m14s
- 백그라운드 전환 즉시 잠그던 것을, 1시간 이상 백그라운드였을 때만 잠금
- 잠깐 다른 앱(계산기 등) 다녀오는 정도로는 PIN 안 뜸
- 완전 종료/재시작은 onMounted 에서 그대로 잠금
- 카메라/갤러리/공유 suppress 는 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:44:12 +09:00
ByungCheol 4f08b47fc3 docs(brand): 외화 결제 기능 완료 기록 2026-06-30 21:38:19 +09:00
ByungCheol dab6dc3411 feat: 외화 입/지출 입력 — 통화 선택 + 환율 자동조회 → 원화 환산
CI / build (push) Failing after 15m36s
- 입력폼: 통화 드롭다운(KRW 기본 + 15개), 외화면 외화금액+환율 입력
  → 환산 원화 자동 계산(amount). 환율은 /fx/rate 자동조회('↻ 자동') + 수정 가능
- 저장: 외화면 currency/originalAmount/exchangeRate 전송, amount=환산 원화
- 목록 상세에 'USD 10.00 @1,350' 외화 뱃지 표시
- accountApi.fxRate 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:37:24 +09:00
ByungCheol caa591a16d chore: versionCode 2 / 1.0.1 (v2 비공개 테스트 빌드) + 백로그 갱신 2026-06-29 21:42:05 +09:00
ByungCheol 971e08282f feat: 분류 순서변경 보호 — 기본 잠금 + '순서변경' 버튼으로만 drag
CI / build (push) Failing after 15m14s
- 스크롤 중 실수로 순서가 바뀌는 것 방지
- 기본은 drag&drop 비활성, '순서변경' 토글 켤 때만 Sortable 생성 + 손잡이(≡) 표시
- 끄면(완료) destroy. 안내 문구도 모드별로 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:36:16 +09:00
ByungCheol f42805f96e docs(brand): 백로그 #6 앱잠금-카메라 버그 수정완료 표시 2026-06-29 20:27:30 +09:00
ByungCheol a0aba1d578 fix: 앱 잠금이 카메라/갤러리/공유 복귀 시 오작동하던 문제 수정
CI / build (push) Failing after 15m40s
- 원인: visibilitychange(숨김)로 백그라운드를 감지하는데 카메라/파일선택/공유
  호출 시에도 웹뷰가 숨겨져 잠금이 걸렸음(복귀 시 PIN 화면)
- appLock.suppressLock(): 네이티브 동작 직전 일시중지 창을 열어 그 복귀는 잠그지 않음
- 파일선택(input[type=file])은 App.vue 전역 클릭 리스너로 일괄 처리
- 카메라(OCR)·공유(백업)는 호출부에서 직접 suppressLock 호출
- 복귀(visible) 시 일시중지 창 해제

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:25:50 +09:00
ByungCheol ce7488076a docs(brand): 백로그에 앱잠금-카메라복귀 PIN 오작동 버그 추가 2026-06-29 17:48:19 +09:00
ByungCheol 2eaf8695b0 docs(brand): 출시 후 업데이트 백로그 추가(업데이트안내·신고글관리·포인트정책·분류순서변경보호) 2026-06-29 09:52:42 +09:00
ByungCheol f23b4963a9 feat: 분류 빈 상태에 '기본 분류 불러오기' CTA 추가
CI / build (push) Failing after 12m57s
- 신규 유저 온보딩(홈 시작하기 → 분류) 흐름이 빈 분류 화면에서 끊기던 문제 보완
- 빈 상태에 이모지·안내 + '기본 분류 불러오기' 버튼(importDefaults) 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:33:31 +09:00
ByungCheol b50a81028f fix: 하단 내비 뒤로 버튼도 하드웨어 뒤로와 동일 동작
CI / build (push) Failing after 15m32s
- 통합 핸들러 appBack.handleBack 추출: 오버레이 닫기 → 화면 뒤로 →
  최상위에서 '한 번 더 누르면 종료' 토스트(네이티브)
- 하단 내비 뒤로 버튼이 옛 로직(홈에서 무반응)이라 하드웨어 뒤로와
  동작이 달랐던 문제 해결 — 둘이 같은 핸들러 사용
- capacitor.js 중복 로직 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:25:57 +09:00
ByungCheol 6972cb5a63 docs(brand): 출시 전 다듬기 진행상황 갱신(빈상태·오프라인·앱잠금·에러폴백·뒤로가기) 2026-06-29 09:11:17 +09:00
ByungCheol bd5f1afd47 feat: 전역 에러 폴백 + 안드로이드 뒤로가기 동작 개선
CI / build (push) Failing after 12m27s
전역 에러 처리(흰 화면 방지):
- app.config.errorHandler + onErrorCaptured → 렌더/라이프사이클 오류 시
  빈 화면 대신 '문제가 발생했어요 + 새로고침' 폴백 화면 표시

안드로이드 뒤로가기 우선순위 개선:
- 잠금 화면이면 우회 불가(종료) → 열린 오버레이(회원가입/로그인/비번/사이드바)
  닫기 → 화면 뒤로 → 최상위에서 '한 번 더 누르면 종료'(실수 종료 방지)
- 종료 안내 토스트(커스텀 이벤트, 플러그인 없이 App.vue 렌더)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:09:01 +09:00
ByungCheol bac3ce1ded feat: 앱 잠금(PIN) 추가 — 가계부 보안 강화
CI / build (push) Failing after 10m50s
- 자체 구현(네이티브 플러그인 없음): PIN은 SHA-256 해시만 저장, 기본 OFF 옵트인
- 앱 시작(저장 세션 재진입)·백그라운드 복귀 시 잠금 화면 노출
- 설정에 앱 잠금 토글 + PIN 설정(입력→확인 2단계) 모달
- 갇힘 방지: 잠금 화면에서 'PIN 분실 → 로그아웃'(잠금 해제 후 로그아웃)
- 신규/민감정보 앱 신뢰도 향상

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:45:38 +09:00
ByungCheol 3f9b0daf53 feat: 오프라인·네트워크 오류 안내 배너 추가
CI / build (push) Failing after 13m9s
- http 인터셉터: 응답 없는 네트워크 오류(오프라인/타임아웃/서버다운) 시
  net:error 이벤트 발생 (취소 요청 제외)
- App.vue: navigator.onLine + online/offline + net:error 구독 →
  상단 고정 배너로 안내(+새로고침). 서버 응답 없음은 5초 후 자동 숨김
- 폐쇄 테스트(다양한 네트워크 환경)에서 무반응 대신 명확한 피드백 제공

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:33:20 +09:00
ByungCheol cbe469552d feat: 신규 유저 빈 상태 행동 유도(온보딩) 추가
CI / build (push) Failing after 15m26s
- 홈 대시보드: 데이터 없을 때 '시작하기' 카드(계좌→분류→내역 3단계 안내, 닫기 가능)
- 가계부 내역: 빈 상태를 '첫 내역 추가' CTA로 (필터 결과 없음과 구분)
- 계좌 관리: 빈 상태를 '계좌 추가' CTA로
- 스토어 신규 유저(데이터 0)의 첫인상·활성화 개선

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:26:02 +09:00
ByungCheol 659ea3b2fa docs(brand): Play 스토어 출시 준비 문서·스크린샷 정리
- LAUNCH-PROGRESS.md: 출시 준비 종합 진행 상황 + 남은 로드맵(단일 기준점)
- play-store-listing.md: 등록정보·데이터안전·콘텐츠등급 자료
- production-access-draft.md: 프로덕션 액세스 신청서 초안
- screenshots/: 스토어용 스크린샷 7컷

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:40:19 +09:00
ByungCheol 7633bdc166 docs: 개인정보처리방침 광고 문구 정합화 (광고 OFF 반영)
CI / build (push) Failing after 11m57s
- 광고를 첫 출시에서 비활성화함에 따라 방침의 광고 관련 문구를
  현재 상태(광고 없음) + 미래형(향후 도입 가능)으로 수정
- 4항: '광고가 표시되며' → '현재 광고 미표시, 향후 도입 시…'
- 수집/목적/제3자(카카오) 항목도 '향후 도입 시' 조건부로 완화
- Play 데이터 안전 양식(광고 식별자 미수집)과 정합성 확보

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:29:31 +09:00
ByungCheol 9d60ea8f12 feat: 첫 출시 광고(카카오 애드핏) 비활성화
CI / build (push) Failing after 12m3s
- ADS_ENABLED=false 플래그 추가(features.js)
- 광고단위 미승인 + 데이터 안전 양식 단순화(광고 식별자 수집/제3자 공유 제거)
- AdBanner showAd + App.vue showAds 양쪽 게이팅 → 앱/웹 광고 모두 미노출,
  레이아웃 하단 광고 여백도 함께 제거
- 애드핏 승인 후 true 로 되돌리면 무료 회원 광고 재노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:24:25 +09:00
ByungCheol ee5e9d4adb fix: 둘러보기(데모) 모드에서 메뉴(사이드바) 열기 불가 수정
CI / build (push) Failing after 14m1s
- 사이드바 메뉴 항목은 데모에서도 렌더되지만, 여는 햄버거 버튼이
  로그인 전용(v-if=isAuthenticated)이라 모바일에서 메뉴에 도달 불가했음
- 햄버거 조건에 demo.on 추가 → 데모에서도 가계부/계좌/분류/고정지출 이동 가능

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:57:28 +09:00
ByungCheol 36178572e1 chore(brand): 스토어 그래픽 에셋 생성기 + 산출물
- brand/gen-store-assets.mjs: 1024 마스터에서 스토어 이미지 생성(sharp)
- brand/logo-1024.png: 돈돼지 마스터 아트(1024x1024) 백업
- brand/store/: icon-512, feature-1024x500(텍스트 유/무) — Play Console 업로드용

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:26:10 +09:00
ByungCheol a2cda9b4ee feat: 첫 출시 무료 앱 — 인앱결제(구독) 진입점 비활성화
CI / build (push) Failing after 10m51s
- BILLING_ENABLED=false 플래그 추가(features.js)
- 실연동 전 구매 흐름이 테스트 스텁이라, 활성화 시 결제 없이 프리미엄이
  부여되는 문제가 있어 구매 진입점을 모두 숨김
- UpgradeView: 구매 버튼 대신 '구독 곧 출시 예정' 안내
- AccountInfoView: 정기결제 관리 카드 + 업그레이드 링크 미노출
- 무료 기능은 정상 동작, 실 Google Play Billing 연동은 다음 업데이트

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:20:14 +09:00
ByungCheol e8d3549f02 feat: 계정삭제 안내 페이지 + 카드알림(알림접근) 첫 출시 비활성화
CI / build (push) Failing after 13m7s
- /account-deletion 공개 페이지(웹 접근) — Play 데이터 삭제 정책 URL, 웹 안내 하단 링크
- CARD_NOTIF_ENABLED=false: 카드알림 자동인식 UI 숨김
- AndroidManifest: CardNotifListenerService 선언 주석 처리(알림접근 정책 회피)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:43:22 +09:00
ByungCheol 4838eba9af chore(ads): 앱 배너를 320x50 모바일 단위(임시)로, 웹은 728x90 유지
CI / build (push) Failing after 14m11s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:29:10 +09:00
ByungCheol 29ff09a8d6 feat: 내 글·댓글 모아보기 화면 + 사이드바 메뉴
CI / build (push) Failing after 12m7s
- MyBoardView(탭: 내 글/내 댓글, 페이징, 글로 이동), /my-board 라우트
- 사이드바 게시판 영역에 내 글·댓글 링크, 헤더 타이틀

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:26:16 +09:00
ByungCheol 8be7072614 feat: 댓글 신고 블라인드 관리자 해제 버튼
CI / build (push) Failing after 11m47s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:17:11 +09:00
ByungCheol 59f38b3348 feat: 게시판 신고 UI — 글/댓글 신고 버튼 + 블라인드 표시
CI / build (push) Failing after 12m42s
- 작성자·관리자 외 신고 버튼(글 액션·각 댓글)
- 신고 누적 블라인드 댓글: 비관리자 가림, 관리자만 본문 + 신고 블라인드 뱃지
- 신고 후 새로고침으로 상태 반영

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:05:43 +09:00
ByungCheol 0fa8c95139 chore(android): local-notifications 플러그인 동기화(cap sync)
CI / build (push) Failing after 11m31s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:43:24 +09:00
ByungCheol 127551da12 feat: 로컬 알림 — 가계부 기록 리마인더 + 구독 만료 임박 (앱 전용)
CI / build (push) Failing after 14m29s
- @capacitor/local-notifications 추가, src/native/reminders.js
- 매일 설정 시각(기본 21시) 가계부 미기록 시 알림(오늘 기록하면 오늘자 제외, 7일 롤링)
- 구독 해지 상태 만료 3일 전 알림
- 설정에 알림 받기 토글 + 시간 선택(앱에서만), 권한 요청
- 앱 시작/홈/내역에서 스케줄 보정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:42:00 +09:00
ByungCheol fa3b12b369 feat: 계정정보 구매 복원 + 기프트콘 교환(업데이트 예정)
CI / build (push) Failing after 13m41s
- 정기결제 관리에 구매 복원 버튼(billingApi.restore)
- 포인트 옆 기프트콘 교환 항목(업데이트 예정 안내)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:29:30 +09:00
ByungCheol db68ea1680 build(android): 릴리스 서명 구성 + AAB - key.properties 기반 서명
CI / build (push) Failing after 14m30s
- key.properties(있으면) 로 릴리스 서명, 없으면 디버그 폴백
- 키스토어/key.properties 는 gitignore (커밋 금지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:16:59 +09:00
ByungCheol e8dc6b5568 feat: 개인정보처리방침/이용약관 공개 페이지 + 보강 (스토어 정책)
CI / build (push) Failing after 11m53s
- terms.js 보강: 구글 로그인·인앱결제·광고·OCR·제3자·탈퇴·문의 반영
- /privacy, /terms 공개 라우트 + LegalView (웹 브라우저에서도 접근 = 앱 마켓 URL)
- 웹 안내 페이지 하단·설정 화면에 정책 링크 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:09:35 +09:00
ByungCheol d243600f41 feat: 계정정보에 회원 탈퇴 — 2단계 확인 후 계정·데이터 삭제
CI / build (push) Failing after 11m31s
- authApi.withdraw(), 강한 경고 + 2차 확인 → DELETE /auth/me → 세션 정리·홈 이동

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:59:25 +09:00
ByungCheol d7639225f7 feat: 카카오 애드핏 광고 — 웹 안내 하단 + 앱 하단내비 위 (유료 제외)
CI / build (push) Successful in 36s
- AdBanner 컴포넌트(애드핏 DAN-n0I5XRBOwAdSFbEX, 728x90), 유료 회원에겐 미표시
- 웹 안내 페이지 하단 배치
- 앱 셸 하단 내비 바로 위 고정 배치(본문 하단 여백 보정)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:38:07 +09:00
ByungCheol 1000bf06af fix(board): 수정 화면도 게시판별 태그만 — 단, 글이 가진 기존 태그는 보존
CI / build (push) Failing after 15m27s
- 새글·수정 모두 boardApi.tagGroups(category)로 이 게시판 매핑 그룹만 노출
- 수정 시 전체 그룹으로 기존 태그 id 해석 → 이 게시판에 없는 태그는 별도 배지로 보존
- 다른 게시판 태그가 수정 화면에 모두 뜨던 문제 해결

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:14:43 +09:00
ByungCheol 821aec72b9 feat: 계정정보 정기결제 관리 — 현황·해지/재개·플랜 변경
CI / build (push) Failing after 13m36s
- 결제 플랜/만료일/다음 결제일 표시
- 구독 해지(안내 후 자동갱신 중단, 만료일까지 이용)/재개
- 결제 가능한 플랜 목록 + 변경/구독(Android 결제, PC는 앱 안내)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:04:41 +09:00
ByungCheol 0dda0b6ccd fix: 게시판 목록 태그 필터를 게시판별로 로드(게시판 전환 시 갱신)
CI / build (push) Failing after 13m31s
- boardApi.tags(category), 게시판 변경 시 태그 필터 재로드
- 매핑된 그룹의 태그만 필터 칩으로 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:44:40 +09:00
ByungCheol 09634757f7 style: 태그 관리 - 미매핑 그룹 안내 갱신(미노출), bm-none 표기
CI / build (push) Failing after 13m44s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:24:27 +09:00
ByungCheol 823bdcafcf feat: 업그레이드 화면 인앱 결제 — Android 구매, PC는 앱 유도
CI / build (push) Failing after 15m11s
- billingApi(products/verifyGoogle), native/billing(테스트 구매 토큰 생성)
- Android 앱: 상품(월간/연간) 구매 버튼 → 검증 → 프로필 갱신
- PC(웹/데스크톱): 구매 대신 앱 다운로드 안내
- 프리미엄 이용 중이면 만료일 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:12:59 +09:00
ByungCheol ce5cefe64a style: 분류 화면 대분류 대 뱃지 제거 (분류 관리·기본 분류)
CI / build (push) Failing after 15m33s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:50:24 +09:00
ByungCheol 56d2ad6a8a feat: 계정정보 포인트 클릭 시 적립내역 모달
CI / build (push) Failing after 13m30s
- 포인트 행 클릭 → 적립/차감 내역 모달(사유·일시·증감)
- authApi.pointHistory(), 사유 라벨(BOARD_WRITE=게시판 작성)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:44:59 +09:00
ByungCheol 7e096d5e7b refactor: board_setting 제거에 따른 정리 + 수정 시 태그 유실 방지
CI / build (push) Failing after 12m59s
- adminApi getBoardSetting/setBoardSetting 제거
- 글 수정 화면은 전체 그룹 로드(기존 글 태그 유지), 새 글은 게시판 매핑 그룹만

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:05:12 +09:00
ByungCheol c167875567 feat: 관리자 태그 화면 - 그룹별 게시판 매핑 체크박스
CI / build (push) Failing after 13m38s
- 전역 단일 카테고리 선택 제거 → 각 태그 그룹에 사용 게시판 체크박스(커뮤니티/짠테크/재테크 팁)
- 저장 시 boards 함께 전송, 선택 없으면 전체 게시판
- 글쓰기 화면은 현재 게시판에 매핑된 그룹 태그만 노출(boardApi.tagGroups(category))

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:34:40 +09:00
ByungCheol 7e38eae2b7 feat: 게시판 인기/블라인드/자기글 차단·헤더 아바타·댓글 새로고침 등
CI / build (push) Failing after 11m35s
- 헤더에 내 아바타(계정정보 링크) + 앱 시작 시 프로필 동기화(재로그인 없이 아바타/포인트)
- 목록: 인기 글 상단 배지(🔥), 비추천 20+ 블라인드 표시
- 상세: 본인 글/댓글 추천·비추천 비활성, 비추천 20+ 글/댓글 블라인드(그래도 보기)
- 댓글: 인기 댓글 배지, 본문 작성자에 작성자 뱃지, 목록 하단 새로고침 버튼(입력폼 위)
- 추천자 목록 가로 줄바꿈(아바타+이름 단위, 이름 안 잘리게)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:21:37 +09:00
ByungCheol 7699cfc55f refine(account): 프로필 사진 - 되돌리기 버튼을 삭제로 변경
CI / build (push) Failing after 12m47s
- 구글사진으로/기본으로 버튼 제거, 삭제 버튼으로 단순화
- 등록한 사진 삭제 시 구글 계정 아바타로 표시(없으면 이니셜)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:48:43 +09:00
ByungCheol 1636c35aef refine(board): 목록 추천 열·상세 추천수·추천자 인라인·아바타 축소
CI / build (push) Failing after 11m2s
- 목록: 제목 옆 추천 표시 제거 → 작성일 옆 추천 열로 표기(모바일도 라벨)
- 상세: 메타에 조회수 옆 추천수 표기
- 추천자: 클릭 시 모달 대신 하단에 아바타+이름 목록 펼침(토글)
- 아바타 크기를 본문 폰트 크기에 맞춰 16px 로 축소(목록/상세/댓글/추천자)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:45:27 +09:00
ByungCheol 206364d2f1 fix(board): 상세→목록 이동 시 페이지·검색조건 유지
CI / build (push) Failing after 14m43s
- 목록 행 클릭 시 현재 쿼리(page/tag/keyword/searchType)를 상세로 전달
- 상세 목록 버튼·삭제 후 이동도 같은 쿼리로 복귀 → 1페이지 초기화 방지
  (브라우저 뒤로가기는 기존에도 URL 쿼리로 복원됨)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:36:48 +09:00
ByungCheol ca66c1d53e feat: 게시판 추천/비추천·작성자 아바타·추천자·포인트 (프론트)
CI / build (push) Failing after 13m27s
- UserAvatar 컴포넌트(커스텀>구글>이니셜, 로드 실패 시 이니셜 폴백)
- 목록: 작성자 아바타 + 추천 수 표기
- 상세: 작성자 아바타, 추천/비추천 토글 버튼(내 투표 강조),
  추천자 본문 하단 아바타 10개 + 초과 시 (+N명이 추천했습니다), 클릭 시 전체 목록 모달
- 댓글: 작성자 아바타 + 추천/비추천 버튼
- 계정정보: 포인트 표기(GET /auth/points 최신값)
- boardApi.votePost/voteComment/recommenders, authApi.points 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:28:20 +09:00
ByungCheol 7855298eac feat: 계정정보 프로필 사진 — 구글 아바타 우선 + 변경/되돌리기
- 아바타 표시 우선순위: 사용자 지정(profileImage) > 구글(googlePicture) > 이니셜
- 사진 변경: 이미지 선택 → 정사각형 크롭·192px 축소(JPEG) → PUT /auth/profile-image
- 되돌리기: 사용자 지정 사진 해제 → 구글 사진(없으면 이니셜)으로 폴백
- authApi.updateProfileImage 추가, utils/avatar.js(표시 소스/이니셜/다운스케일)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:39:48 +09:00
ByungCheol 24ab91b257 fix(electron): 중복 실행 방지 + 로딩 스플래시 — 재시작 빈 화면 해소
- 단일 인스턴스 잠금(requestSingleInstanceLock): 두 번째 실행 시 새 창 대신 기존 창 활성화
  (중복 실행 시 라이브 사이트를 다시 로드하며 흰 화면이 오래 보이던 현상 해결)
- 시작 시 전체 캐시 비우기 제거 → index.html 만 캐시버스트(?_=t), 해시 자산은 캐시 유지(빠른 재시작)
- 로드되는 동안 스플래시(로딩) 창 표시, ready-to-show 후 메인 창 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:25:00 +09:00
ByungCheol 866abee860 fix: 무료 회원 가계부 내역 진입 시 유료 안내로 튕기는 버그
- onMounted 의 runRecurrings(고정지출 자동반영)는 유료 전용 경로(/recurrings/run)라
  무료 회원이 403 → premium:required → /upgrade 로 리다이렉트되던 문제
- isPremium 일 때만 runRecurrings 호출, '이 내역을 고정지출로 등록' 버튼도 유료에만 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:24:59 +09:00
ByungCheol 9861ba90ae feat: 멤버십(무료/유료) 게이팅 — 프론트
CI / build (push) Failing after 15m5s
- auth 스토어 isPremium/isAdmin 추가 (plan===PREMIUM 또는 관리자)
- 사이드바: 통계·고정지출·예산·태그 메뉴에 무료 회원 잠금 배지(🔒)
- 라우트 가드: 유료 전용 화면(requiresPremium) → 무료 회원은 /upgrade
- UpgradeView: 무료/유료 기능 비교 + 업그레이드 안내
- http 인터셉터: 403 PREMIUM_REQUIRED → premium:required 이벤트 → 업그레이드 화면
- 무료 화면의 유료 API 호출 차단(홈 예산·내역 태그·OCR·카드알림 게이팅)
- 회원관리: 멤버십 컬럼 + 무료/유료 토글(관리자)
- 설정: 데이터 백업/복구 유료 게이팅, 계정정보에 멤버십 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:02:44 +09:00
ByungCheol 2755d118c2 fix(electron): 기본 메뉴(File/Edit/View) 전역 제거 — 구글 로그인 팝업 메뉴바 제거
Menu.setApplicationMenu(null) 로 메인·자식 창 모두 메뉴바 비표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:40:18 +09:00
ByungCheol c9b58f4ad0 ci: main 푸시 시 CI 중복 실행 제거(Deploy만) — 배포 시간 단축
CI / build (push) Successful in 38s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:22:48 +09:00
ByungCheol 9aa21696d9 fix: 백업/복구 - 계좌 ID 컬럼 추가, 내역/고정/자주내역을 계좌ID 기준으로
CI / build (push) Failing after 11m48s
- 내보내기: 계좌 시트 ID 컬럼, 내역/고정지출/자주내역에 계좌ID·입금계좌ID
- 가져오기: 계좌ID 기준 파싱 (이름 충돌 무관 정확 복구)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:03:24 +09:00
ByungCheol 7d8f947156 feat: 설정 - 엑셀 가져오기(복구) + 창 크기 가로·세로 분리 조절
CI / build (push) Failing after 12m18s
- backup.js importBackup: .xlsx 파싱 → 이름 기준 payload → POST /account/restore
- SettingsView: '가져오기' 파일 선택 + 파괴적 작업 확인(이중) + 완료 후 새로고침
- accountApi.restore (데모에선 차단)
- 창 크기: 가로/세로 각각 선택(세로 700~1200), 한 축 변경 시 나머지 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:38:38 +09:00
ByungCheol 457fd8127a feat: 설정 - 엑셀 데이터 내보내기(백업)
CI / build (push) Successful in 44s
- utils/backup.js: 가계부 전체(내역·계좌·분류·고정지출·예산·태그·자주내역)를
  이름 기준 참조로 .xlsx 다중 시트 생성. xlsx 동적 import(별도 청크)
- 웹/PC=다운로드, 앱(네이티브)=Filesystem 저장 + Share
- SettingsView: '데이터 백업' 카드(엑셀로 내보내기 동작 / 가져오기 버튼=준비중)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:14:19 +09:00
ByungCheol 4ffc01f484 feat: 둘러보기(데모)를 실제 화면 그대로 — 데모 모드 + API 더미 응답
- src/demo: demo.on 플래그 + DTO 형태 더미데이터 + mock API(읽기 더미/쓰기 차단)
- accountApi: Proxy 로 데모 시 읽기→더미, 쓰기→안내. 실제 뷰 그대로 렌더
- App: authed=인증||데모 → 실제 사이드바/셸 표시 + 데모 배너(로그인/나가기)
- 라우터: 데모 허용(가계부/고정/계좌/분류) 외 보호화면은 DemoLockedView(로그인 유도)
- 사이드바: 데모 시 잠금(🔒) 배지, 메뉴는 showMenu(인증||데모)
- 로그인/로그아웃 시 데모 자동 해제, 랜딩 '둘러보기'→enterDemo
- 기존 독립 DemoView 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 07:49:37 +09:00
ByungCheol 80e1faec09 docs: 멤버십(무료/유료) 메뉴·기능 구분 설계 (추후 구현)
- 계좌 개수 제한 없음, 핵심 기록 무료
- 유료: OCR·카드알림 자동인식·통계·예산·고정지출·투자·태그·백업

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:52:02 +09:00
ByungCheol cee5b661e7 feat: 로그인 없이 둘러보기(데모) — 가계부/고정지출/계좌/분류 더미 + 나머지 로그인 유도
CI / build (push) Failing after 11m36s
- DemoView: 자체 메뉴로 섹션 전환, 4개 화면 샘플 데이터,
  통계·예산·태그·커뮤니티는 잠금(로그인 유도) 안내
- 공개 라우트 /demo, 랜딩에 '로그인 없이 둘러보기' 버튼, 헤더 타이틀 '둘러보기'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:42:48 +09:00
ByungCheol 54a285a3b6 docs: 변경 내역 갱신 — 구글 로그인·다크모드·UI 개편 (2026-06-28)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:12:59 +09:00
ByungCheol e4e1be49ca feat: PC 화면 해상도 변경 — 설정에서 창 크기 프리셋 선택
CI / build (push) Failing after 14m21s
- electron/preload.cjs: contextBridge 로 window.desktop.setWindowSize 노출
- main.cjs: preload 연결 + ipcMain 'window:setSize'/'getSize' 핸들러
- SettingsView: PC(window.desktop 존재 시)에서 해상도 선택 추가
  (1024×720 ~ 1920×1080), 선택 시 창 크기 변경+가운데 정렬

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:57:41 +09:00
ByungCheol f34980048b feat: 계정정보 단순화 + 구글 계정 이름 변경 허용
- 계정정보: 아이디·가입유형 행 제거 (이름/이메일/권한만), '이름 변경' 버튼
- ProfileEdit: 소셜(구글) 계정은 비밀번호 재인증 없이 바로 이름만 변경
  (아이디/이메일 편집/비밀번호 변경 섹션은 LOCAL 전용으로 숨김)
- 백엔드 updateProfile 은 provider 무관하게 동작(변경 없음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:53:32 +09:00
ByungCheol 85670a1b9f fix: PC 레이아웃 — 사이드바를 좌측 상단까지 확장, 헤더는 우측 영역만
- App.vue 그리드 'top top' → 'left top' (사이드바가 헤더 행까지 차지)
- 사이드바 브랜드 영역 높이 56px 로 헤더와 정렬(보더 일치)
- 모바일은 기존 드로어 구조 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:49:02 +09:00
ByungCheol 5bfa45ed30 feat: 자주 쓰는 내역 UX 정리 + 빠른등록 견고화
- 등록 모달의 '자주 쓰는 내역 불러오기' 버튼·피커 모달 제거(번거로운 3단계 해소)
  (저장 기능은 유지)
- 가계부 quick-bar 에 ' 자주 쓰는 내역' 라벨 추가
- useQuick: 등록 성공과 목록 갱신 분리 — 갱신 단계 오류가 '등록 실패'로
  오인되던 문제 방지(앱에서 등록 안 됨처럼 보이던 케이스 대응)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:43:57 +09:00
ByungCheol c4c58d2e24 fix: 월 네비 가운데 유지·액션 우측 정렬 + 검색 모달 버튼 넘침 수정
- 가계부/예산 month-nav: 좌 spacer + 우 mn-actions(flex:1)로 네비 중앙 고정,
  추가/검색·필터 버튼 우측 정렬
- 커뮤니티 검색 모달: input min-width:0 + select/버튼 flex:none 으로
  검색 아이콘이 모달 밖으로 밀리는 문제 해결

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:35:24 +09:00
ByungCheol 59b17051a4 fix: 화면별 액션 아이콘 위치 정리(타이틀 제거 후 빈 헤더 해소)
CI / build (push) Failing after 12m2s
- 가계부: 내역 추가 → month-nav(검색·필터 옆), 헤더는 pending 있을 때만
- 예산: 예산 추가 → month-nav(월 변경 옆)
- 고정지출: 지금 반영/추가 → 안내문구 아래
- 커뮤니티: 글쓰기 → 태그 필터와 같은 줄(list-top)
- 분류 관리: 기본 분류 불러오기 → 지출/수입 탭과 같은 줄(tabs-row)
- 미사용 boardTitle/boardLabel 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:18:42 +09:00
ByungCheol c4872c93a8 feat: 페이지 타이틀 제거(헤더만 표기) + 게시글 상세 목록아이콘/공지 체크박스화
CI / build (push) Failing after 11m0s
- 각 화면 내 <h1> 페이지 타이틀 제거 → 헤더 타이틀로 일원화
  (가계부 pending 카운트는 유지, 액션 헤더는 우측 정렬)
- 게시글 상세: 공지 등록/해제 버튼 제거 + '목록' 아이콘 추가(list 아이콘 신규)
- 글 작성/수정 공지 체크박스로 일원화(편집 시 기존값 로드)
- ProfileEdit/AccountInfo 화면 내 뒤로가기 제거, 미사용 router 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:59:20 +09:00
ByungCheol b737720d29 fix: 전 화면 본문 폭 통일 + 화면 내 뒤로가기 버튼 제거
CI / build (push) Failing after 13m55s
- 본문 폭: --page-max(760px) 변수 + .layout-body>* 전역 규칙으로 일관화
  (각 뷰가 제각각 두던 root max-width 대체)
- 화면 내 '뒤로가기/가계부로/목록' 버튼 제거(하단 내비 뒤로가기는 유지)
  대상: 계좌/분류/예산/고정지출/태그/기본분류/게시글상세/계정정보
- BoardDetail actions 우측 정렬, AccountInfo 미사용 goBack 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:26:25 +09:00
ByungCheol 75228cbb64 feat: 사이드바 브랜드/헤더 타이틀 + 라이트·다크 테마 설정
1) 사이드바 상단에 돼지 로고 + '돈돼지 가계부'('메뉴' 텍스트 대체, 데스크톱/모바일 공통)
2) 헤더에서 돼지 로고 제거 → 현재 화면 타이틀 표시(라우트별)
3) 설정에 화면 테마(시스템/라이트/다크) 선택 추가
   - base.css: data-theme 기반 수동 다크 + 시스템 설정 폴백
   - theme.js 유틸 + ui 스토어 theme 상태, main.js 시작 시 적용(깜빡임 방지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:15:27 +09:00
ByungCheol 63b4792e58 fix: 웹 안내문 줄바꿈을 절 단위로 (inline-block 묶음)
- 'PC는 설치형 앱으로,' / '모바일은 앱으로 이용해요.' 등 절 경계에서만 줄바꿈

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:08:51 +09:00
ByungCheol f07cb349e2 feat: 게시판 공지 UI (관리자) — 작성 체크박스/상세 토글/목록 배지
- BoardWriteView: 관리자가 커뮤니티 새 글 작성 시 '공지로 등록' 체크박스
- BoardDetailView: 관리자 공지 등록/해제 버튼 + 제목 공지 배지
- BoardListView: 공지 배지 + 행 강조(최상단은 백엔드 정렬)
- boardApi: setNotice/unsetNotice

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:07:03 +09:00
ByungCheol ef1d718fb8 feat: 통계 분류별 집계를 대분류 기준으로 (소분류 펼침 제거로 스크롤 단축)
- catByMajor: 소분류를 부모 대분류로 합산해 파이/범례 표기
- 중복되던 '대분류 합계' 섹션 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:38:07 +09:00
ByungCheol a92063dd64 chore: PC 설치를 사용자 폴더로 변경 (UAC 프롬프트 제거)
- installer.nsh: 기본 설치경로를 %LOCALAPPDATA%\Programs\DonDwaeji 로
  (perMachine=false 와 함께 관리자 권한 불필요 → '디바이스 변경 허용' UAC 안 뜸)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:09:34 +09:00
ByungCheol 6aa3590c5a chore: PC 설치파일명 DonDwaeji-Setup.exe + 기본 설치경로 DonDwaeji
CI / build (push) Failing after 12m51s
- electron-builder artifactName: SlimBudget-Setup → DonDwaeji-Setup
- build/installer.nsh: 기본 설치경로를 $PROGRAMFILES\DonDwaeji 로 고정
  (productName 한글이라 name='sb_pt' 로 폴백되던 문제 회피)
- WebOnlyNotice 다운로드 URL 도 DonDwaeji-Setup.exe 로 변경

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:47:46 +09:00
ByungCheol 4916d28604 fix: 웹 안내 페이지 한국어 줄바꿈(word-break: keep-all) — 'O요.' 고아줄 방지
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:34:24 +09:00
ByungCheol dbe255cf1c feat: 사이드바 메뉴에 아이콘 추가 (lucide 스타일 인라인 SVG)
CI / build (push) Failing after 15m5s
- 가계부/게시판/관리자 각 메뉴 항목에 18px 아이콘, 하단 내비와 톤 통일
- .menu-item flex 정렬, 활성 항목 아이콘 강조

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:56:01 +09:00
ByungCheol 579ee3e4e6 feat: 게시판 작성일 1일 이후 표기 YYYYMMDD → YYYY.MM.DD
- formatRelative: 1시간 이내 N분 전 / 1일 이내 N시간 전 / 그 이상 YYYY.MM.DD
- formatYmdDot 헬퍼 추가(기존 formatYmd 컴팩트 형식은 유지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:51:49 +09:00
ByungCheol 8254460751 feat: 구글 로그인 전용 — 아이디 로그인 폼·회원가입 UI 숨김
CI / build (push) Failing after 14m50s
- src/config/features.js ID_LOGIN_ENABLED 플래그(기본 false)로 게이트
- LoginModal: 아이디/비번 폼·'또는' 구분선·회원가입 링크 숨김(구글 버튼만)
- HomeView: 랜딩 회원가입 버튼 숨김, 안내문구 정리
- 백엔드 /api/auth/login·signup 은 비상용으로 유지(플래그 true 로 복구 가능)
- LoginModal.spec: 아이디 로그인 플로우는 플래그 mock 으로 계속 검증

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:41:04 +09:00
ByungCheol b8c1440e4d fix: 앱 헤더 로고만(모바일)·런처 라벨 한 줄·Electron 구글 로그인 팝업
CI / build (push) Successful in 39s
- AppHeader: 좁은 화면(폰)은 브랜드명 텍스트 숨기고 로고만 → 두 줄 줄바꿈 방지
- 안드로이드 런처 라벨 '돈돼지가계부'(한 줄), 앱 내부 브랜드명은 유지
- Electron: accounts.google.com 팝업을 앱 내부 자식 창으로 열어
  GIS 로그인 결과(credential)가 앱 창으로 돌아오게 함(외부 브라우저 X)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:01:57 +09:00
ByungCheol a310c7a471 feat: 구글 로그인/가입 — 웹(GIS)·앱(네이티브 Credential Manager)
CI / build (push) Failing after 13m30s
- LoginModal: 서버에서 구글 클라이언트 ID 수신 시에만 버튼 노출
  - 웹/PC: Google Identity Services 렌더 버튼
  - 앱(안드로이드): @capawesome/capacitor-google-sign-in 네이티브 버튼
- 네이티브/웹 모두 ID 토큰을 /api/auth/google 로 전달(aud=웹 클라이언트 ID)
- auth 스토어/authApi 에 googleLogin·googleClientId 추가
- src/native/googleAuth.js 네이티브 래퍼 신규

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:02:19 +09:00
ByungCheol 1ca09b7439 feat: 앱명을 '돈돼지 가계부'로 통일 (가계부 인식 + 마스코트)
CI / build (push) Failing after 11m38s
- 헤더·웹안내·홈·약관 서비스명·푸터/설정 저작권·타이틀 → 돈돼지 가계부
- productName/capacitor appName/안드로이드 app_name·창제목/Electron app.setName·window title → 돈돼지 가계부
- 기술 식별자(appId, 설치파일명)는 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:51:29 +09:00
ByungCheol 4ef5850613 fix: 아이콘 배경 회색박스 제거 — 원본 체커판(불투명) 키잉해 진짜 투명 돼지로
CI / build (push) Failing after 13m27s
- 저장된 piggy-fg.png 는 투명이 아니라 체커판이 회색/흰색으로 박힘 → 헤더·파비콘·PC에 회색 박스
- 회색/흰색 배경 키잉 + 돼지 본체 bbox 크롭 → assets/piggy-src.png(투명)
- 파비콘·헤더·PC ico = 투명 + 돼지만, 안드로이드는 금색 배경 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:29:42 +09:00
ByungCheol d706339649 feat: 파비콘·PC 아이콘 금색배경(외곽선) 제거 → 투명 + 돼지 크게
CI / build (push) Failing after 11m23s
- favicon.png / apple-touch-icon.png / build/icon.ico: 투명 배경 + 돼지 ~95%
- 안드로이드: 금색 배경 유지(적응형 특성) + 전경/단일 돼지 크게
- 헤더 로고도 동일 재생성

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:21:43 +09:00
ByungCheol dc62d9883a feat: 헤더 로고를 '돈돼지' 이미지로 변경
CI / build (push) Failing after 11m36s
- AppHeader brand-mark(초록 막대 SVG) → public/logo-piggy.png(투명 돼지)
- 텍스트는 SlimBudget 유지, 비율 유지(height 28 / width auto)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:16:26 +09:00
ByungCheol e4d7df2554 fix(electron): 설치파일 손상 방지 — win.icon 을 표준 .ico(≤256)로 지정
CI / build (push) Failing after 11m20s
- 1024px PNG 자동변환 ico(1024 프레임)가 일부 환경에서 손상 인식되던 문제
- build/icon.ico(16~256px, png-to-ico) 생성해 win.icon 으로 지정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:06:48 +09:00
ByungCheol 4e4c28bafe feat: 앱 아이콘·파비콘을 '돈돼지' 이미지로 변경
CI / build (push) Failing after 10m56s
- piggy-fg.png(투명 전경)에서 icon-foreground/background(금색)/only 합성
- @capacitor/assets generate --android 로 안드로이드 런처/적응형 아이콘 재생성
- 파비콘: public/favicon.png·apple-touch-icon.png 생성, index.html 갱신(구 logo.svg→png)
- PC(Electron) 설치 아이콘은 icon-only.png 사용(설치파일 재빌드 시 반영)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:52:11 +09:00
ByungCheol 0eb3eaaa67 feat: 영수증·문자푸시 버튼 같은 행 + 문자/푸시 붙여넣기 모달화
CI / build (push) Failing after 11m48s
- 영수증 스캔·문자·푸시 버튼을 상단 한 행(rb-actions)에 나란히, 안내문구는 아래
- 문자·푸시 붙여넣기를 하단 인라인 → 모달로(텍스트영역+감지+분석·채우기)
- 팝업 열 때 클립보드 자동감지 시 버튼에 ● 배지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:31:20 +09:00
ByungCheol 5a5959a26e feat: 자주 쓰는 내역 불러오기 모달 피커 + 붙여넣기 버튼 영수증 행으로
CI / build (push) Failing after 13m52s
- 불러오기: 셀렉트 → 모달 리스트(시인성↑), 항목 탭하면 폼 채움
- 문자·푸시 붙여넣기 버튼을 영수증 스캔과 같은 행(우측)으로 이동, 감지/수동입력은 아래로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:19:13 +09:00
ByungCheol eeea19e20b fix: 자주 쓰는 내역 저장 안내를 수정 모달 안에 표시
CI / build (push) Failing after 14m9s
- 페이지 상단 flash → 모달 내 formInfo(성공 메시지)로 변경(모달에 가려 안 보이던 문제)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:08:59 +09:00
ByungCheol ca803272c4 fix: 신규 추가는 '자주 쓰는 내역 불러오기', 수정은 '저장'으로 분기
CI / build (push) Failing after 14m0s
- 신규 추가 폼: 자주 쓰는 내역 선택해 폼 채우기(불러오기) 셀렉트
- '자주 쓰는 내역으로 저장' 버튼은 수정(editId) 시에만 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:59:09 +09:00
ByungCheol 8e651c786f feat: 자주 쓰는 내역 칩(바로 등록) + 문자/푸시 붙여넣기 자동채움
CI / build (push) Failing after 15m32s
- 가계부 상단 자주 쓰는 내역 칩: 탭하면 오늘 날짜로 즉시 등록(+안내), 편집 모드로 삭제
- 내역 추가/수정 폼: ' 자주 쓰는 내역으로 저장' 버튼(수입/지출)
- 추가 팝업 열 때 클립보드 자동감지 배너 + '📋 문자·푸시 붙여넣기' 버튼
  (실패 시 수동 붙여넣기 입력칸) → 구분/금액/메모/날짜 자동 채움
- accountApi: parseText / quickEntries CRUD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:50:10 +09:00
ByungCheol dd17fae5f4 feat: 기본 분류 — 관리자 설정 화면 + 사용자 '기본 분류 불러오기'
CI / build (push) Failing after 14m11s
- 관리자 전용 기본 분류 관리 화면(DefaultCategoryView, 계층 드래그) + 라우트/사이드바 링크
- adminApi 기본분류 CRUD/reorder
- CategoryView '기존 분류 불러오기' → '기본 분류 불러오기'(관리자 정의 분류 복사)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:03:52 +09:00
ByungCheol f533df0024 feat: 네이티브 alert/confirm/prompt → 공용 인앱 다이얼로그 (PC 'sb_pt' 제거)
CI / build (push) Failing after 15m30s
- composables/dialog + components/ui/AppDialog(App 루트 마운트)
- window.alert 전역 오버라이드로 모든 alert 인앱화
- confirm/prompt(동기)는 각 호출부를 await dialog.confirm/prompt 로 교체
  (가계부·예산·고정지출·계좌·태그·관리자·게시판·설정 — 삭제/열람제한 등)
- 웹/PC/APK 일관 UI, 의미있는 제목 부여

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:57:50 +09:00
ByungCheol 643bb8011b fix: 분류 관리 — 삭제 확인 인앱 모달 / 행별 대분류 select 제거 / 추가옵션 대분류명만
CI / build (push) Failing after 11m34s
- 삭제·불러오기 네이티브 confirm('sb_pt' 제목) → 인앱 확인 모달로 교체, 오류도 인라인 표시
- 각 행 아래 '대분류' 선택 박스 제거(불필요) — 계층은 추가 폼·드래그로 관리
- 추가 폼 대분류 옵션 'OOO의 소분류' → 'OOO'(대분류명만)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:31:39 +09:00
ByungCheol 8add41d94f fix: 고정지출 등록 확인 인앱 모달 + 분류 관리 계층 드래그
CI / build (push) Successful in 36s
1. PC 네이티브 confirm 제목('sb_pt') 노출 → '고정지출 등록' 인앱 확인 모달로 교체
   (성공 시 토스트 안내). electron app.setName('Slim Budget')로 다른 네이티브
   대화상자 제목도 개선(다음 Electron 빌드 시 반영).
2. 분류 관리 드래그를 계층형으로: 대분류는 블록 단위(소분류 함께) 이동,
   소분류는 그 대분류 안에서만 이동(고유 group). 로컬 즉시반영으로 스냅백 방지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:02:09 +09:00
ByungCheol cad1aba0ca feat: 가계부 분류를 대분류→소분류 2단 선택으로 변경
CI / build (push) Failing after 11m9s
- 단일 긴 드롭다운(대분류 많으면 스크롤 과다) → 대분류 선택 후 소분류 선택
- 자식 없는 대분류는 그 자체가 분류(소분류 select 숨김)
- 편집 진입 시 현재 분류로부터 대분류 자동 추론, 구분 변경 시 초기화
- 인라인 분류 추가는 선택된 대분류의 소분류로 생성

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:57:19 +09:00
ByungCheol 27beaf28d4 fix: 분류 관리 행 가로 오버플로우 — 이름+버튼 윗줄 / 대분류 select 아랫줄
CI / build (push) Failing after 13m50s
- 좁은 화면에서 버튼이 화면 밖으로 밀리던 문제 해결
- 행을 2단(이름·저장·삭제 / 대분류 선택)으로 분리, 버튼 sm 크기
- 추가 폼도 대분류 선택은 윗줄, 이름+추가는 아랫줄로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:49:39 +09:00
ByungCheol f55443b823 fix: 폼 셀렉트 높이를 input·버튼과 통일 (font 상속 리셋)
CI / build (push) Successful in 36s
- 네이티브 select 가 폰트 미상속으로 input 과 높이 달라 보이던 이질감 해소
- 전역 input/select/textarea { font: inherit } + select 기본 padding/border 정렬
- CategoryView 대분류 select 폰트 오버라이드 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:39:37 +09:00
ByungCheol 6ab8d331e3 feat: 분류 대/소분류 UI — 관리·입력·예산·고정지출·통계
CI / build (push) Failing after 12m0s
- 분류 관리: 대분류 선택·소분류 들여쓰기(드래그 정렬 유지)
- 입력/예산/고정지출 분류 드롭다운 대분류 optgroup 그룹핑(값은 소분류명)
- 대시보드: 파이는 소분류 기준 + 대분류 합계 롤업 표시
- 릴리스 노트 33

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:16:46 +09:00
ByungCheol b1f85cee52 feat: 통계·가계부 UX 개선 + 계좌 탭 한 줄
CI / build (push) Failing after 14m57s
- 통계: 순자산 추이 주간(최근 3개월), 이번달 예상 지출(run-rate), 지난달 대비 지출
- 가계부: 내역→고정지출 바로 등록, 날짜별 접기/펴기(기본 접힘·오늘만 펼침·모두 토글)
- 계좌 관리 탭 한 줄(넘치면 가로 스크롤)
- 릴리스 노트 갱신(23~32)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:38:22 +09:00
ByungCheol 8cc9cbb383 fix(android): 키보드(IME)가 입력폼 가리는 문제 — WebView를 키보드 위로 리사이즈
CI / build (push) Failing after 14m59s
- MainActivity 인셋 리스너에 IME 인셋 반영: 하단 패딩 = max(시스템바, 키보드 높이)
  → 키보드가 뜨면 WebView가 키보드 위로 줄어들어 입력 모달이 재배치됨
- AndroidManifest activity에 windowSoftInputMode=adjustResize 추가
- 모달은 기존 max-height+overflow로 스크롤되어 모든 필드 확인 가능
- 네이티브 전용(웹/PC 무영향) → APK 재빌드 필요

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:53:22 +09:00
ByungCheol 43b01da1ff docs: 고정지출 안내 — '확인 필요'로 생성 후 확정 안내
CI / build (push) Failing after 15m14s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:13:26 +09:00
ByungCheol 8133963f89 feat: 이체에도 현금 지원 — ATM 출금(은행→현금) 등 현금 계좌 이체 가능
CI / build (push) Failing after 14m41s
- WALLET_KINDS에 현금 추가, 중복되던 별도 '현금' 라디오 제거
- 출금/입금 종류 모두에서 현금 선택 가능(이체 포함)
- 현금 계좌 선택 셀렉트 상시 노출, 수입/지출 현금은 '미지정'(무계좌) 허용 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:53:56 +09:00
ByungCheol a4a4aec314 feat: 가계부·계좌관리에 현금 계좌 추가
CI / build (push) Failing after 14m41s
- 계좌 관리: '현금' 탭 + 폼(이름·초기 잔액, 발급사/계좌번호 등 미노출)
- 가계부 입력: 현금 선택 시 등록된 현금 계좌를 고를 수 있음(없으면 종전처럼 미지정)
- 고정 지출: 현금 종류 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:43:45 +09:00
ByungCheol 9aa5f5d690 feat: 상환/납부 연회비 입력(카드 대상 시) + 원금/이자 안내
CI / build (push) Failing after 15m31s
- 대상이 카드일 때만 연회비 입력란 노출(repayTargetIsCard)
- 원금=이체·이자/연회비=지출 안내 placeholder, 검증 '원금·이자·연회비 중 하나'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:52:34 +09:00
ByungCheol 9f079fcead docs: 릴리스 노트 갱신 — 투자 단순화·알림 입출금 분류·광고차단 강화·분류 학습·상환 수정
CI / build (push) Failing after 15m13s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:33:01 +09:00
ByungCheol d88854d11b fix: 상환/납부를 수정(편집) 시에도 선택 가능하게
CI / build (push) Failing after 11m45s
- 구분 드롭다운의 '상환/납부'가 신규 등록에서만 보이던 제약(v-if="!editId") 제거
- 수정 중 상환/납부로 전환 시: 상환 2건(원금=이체, 이자=지출) 생성 후
  원본 1건 삭제 — 생성 성공 후 삭제 순서로 중간 실패 시 원본 보존
- openEdit에서 principal/interest 초기화(이전 값 잔존 방지)
- 잘못 입력한 단순 이체/지출을 원금·이자 분리 상환으로 정정 가능

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:06:16 +09:00
ByungCheol dba0741cca feat: 투자 계좌를 투자금·평가액 입력형으로 단순화 (종목 관리 제거)
CI / build (push) Successful in 34s
- 종목(매수/매도/현재가) 관리 UI(InvestPortfolio) 제거 — 퇴직연금·연금처럼
  투자금(투입원금)과 현재 평가액만 입력하면 손익·수익률 자동 계산
- 계좌 펼침: 종목 포트폴리오 → 투자금/평가액/손익 요약 카드
- 폼: '현재 평가액'을 기본 입력 필드로, 안내 문구 정리
- 투자 탭 진입 시 자동 시세갱신 로직 제거(불필요)
- 백엔드 변경 없음(평가액 직접입력 경로 그대로 사용)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:12:22 +09:00
ByungCheol 9807472474 feat: 알림 입금 인식 + 미확인 수입 기본 은행계좌
CI / build (push) Successful in 34s
- 네이티브 알림 필터에 입금/이체/송금 키워드 추가(입금 알림도 수집)
- 미확인(pending) 내역: 수입(입금)이면 기본 계좌종류 은행, 지출이면 카드

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:50:23 +09:00
ByungCheol 2cde893d55 docs: 릴리스 노트 갱신 — 데스크톱 클라이언트·브랜드 아이콘·캘린더 팝업·월별 내역·카드 매칭·이미지 업로드·에디터 팝업
CI / build (push) Successful in 34s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:05:47 +09:00
ByungCheol c67c2ba7ed fix: 모바일 에디터 팝업 좌측 잘림 — Toast UI margin-left:-150px 취소
CI / build (push) Failing after 13m37s
- Toast UI 가 max-width:480px 에서 popup 에 margin-left:-150px 를 줘 좌측으로 밀려 잘렸음
- 중앙 모달 규칙에 margin:0 !important 추가 → 표/링크 팝업도 화면 중앙에 온전히 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:00:00 +09:00
ByungCheol 7276d878dd fix: 모바일에서 에디터 팝업(표/링크 등) 화면 중앙 모달로 — 좌측 잘림 해결
CI / build (push) Failing after 14m9s
- 좁은 화면(댓글창 등)에서 Toast UI 팝업이 좌측으로 넘쳐 OK/입력칸이 잘리던 문제
- @media(max-width:768px)에서 .toastui-editor-popup 을 fixed 중앙 모달로(항상 보임)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:49:28 +09:00
ByungCheol 2e2c784f4c fix: 에디터 이미지 버튼 = 바로 업로드(팝업 제거) — 댓글창 OK 잘림 해결
CI / build (push) Failing after 12m20s
- Toast UI 기본 이미지 팝업(File/URL/Description/OK)이 작은 댓글창/하단 위치에서 잘려
  OK 를 못 누르던 문제 → 커스텀 툴바 이미지 버튼으로 교체(클릭 시 바로 파일선택→업로드→삽입)
- 서버 업로드(URL) 그대로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:16:14 +09:00
ByungCheol f7f75b7fb1 revert: 게시판 에디터를 Toast UI 위지윅으로 통일(PC 포함)
CI / build (push) Failing after 14m53s
- PC 전용 textarea/썸네일 방식 제거 — 글 흐름(이미지 아래 설명 등)이 불편하다는 피드백 반영
- 모든 플랫폼 위지윅(이미지 인라인). PC 한글 첫 자음 중복은 감수(tui.editor Windows IME 업스트림 버그)
- 서버 이미지 업로드(addImageBlobHook)는 유지 — 화면 변화 없는 개선(base64 미사용)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:03:44 +09:00
ByungCheol f45014f827 feat: PC 에디터 이미지를 첨부(썸네일)로 분리 — 본문 텍스트에 URL 미노출 + 썸네일 확대
CI / build (push) Failing after 11m37s
- 이미지 마크다운(![](url))을 본문 textarea 에서 분리해 별도 첨부 목록으로 관리
  · 저장 시 본문 끝에 합쳐 emit, 편집 로드 시 본문/이미지 분리
  · 썸네일 120px 확대 + 제거(×) 버튼
- 텍스트는 깨끗하게(긴 URL 미노출), 이미지는 썸네일로 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:57:00 +09:00
ByungCheol cbc6245cc7 feat: PC 에디터 이미지 썸네일 — 편집창 안에서 올린 이미지 확인
CI / build (push) Successful in 35s
- 본문의 ![](url) 이미지를 파싱해 툴바 아래 썸네일 줄로 표시(서버URL·데이터URI 모두)
- 미리보기 탭/패널 제거(한글 IME 위해 textarea 유지), 썸네일로 업로드 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:47:19 +09:00
ByungCheol bfba296b91 feat: 에디터 이미지 = 서버 업로드(URL) + 업로드 안내 메시지
CI / build (push) Successful in 35s
- PC 에디터: 🖼 → 압축 → 서버 업로드 → 본문에 ![](/api/images/{id}) 삽입(base64 제거)
- Toast UI(웹/APK): addImageBlobHook 으로 동일하게 서버 업로드(base64 대체)
- 업로드 중/실패 안내 메시지 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:17:44 +09:00
ByungCheol 0238e0b22f feat: PC 마크다운 에디터에 이미지 추가(파일 선택 → 압축 → base64 임베드)
CI / build (push) Failing after 13m52s
- 이미지 버튼: 파일 선택 → imageToBlob(1280px·JPEG0.85)로 압축 → ![alt](data:...) 삽입
- 웹 에디터처럼 본문에 임베드(별도 서버 저장 없음), 단 압축으로 용량 절감

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:14:40 +09:00
ByungCheol b4aa5c2ad0 feat: PC용 툴바 마크다운 에디터 (한글 IME 정상)
CI / build (push) Successful in 34s
- Toast UI 는 Windows IME 와 충돌(업스트림 버그)하므로 PC(Electron)에선 textarea 기반
  마크다운 에디터 사용: 툴바(제목·굵게·기울임·취소선·인용·목록·체크·링크·코드·구분선) + Write/Preview 탭
- textarea(v-model)라 한글 IME 정상. 웹/APK 는 Toast UI 위지윅 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:08:23 +09:00
ByungCheol 9c61f888a5 fix: PC(Electron) 본문 입력을 일반 textarea로 — 한글 IME 중복 완전 해결
CI / build (push) Failing after 12m11s
- Toast UI 위지윅·마크다운(CodeMirror) 둘 다 Electron 한글 IME 첫 자음 중복 발생
  (제목 등 일반 input 은 정상) → PC 에선 본문을 일반 textarea(v-model, 네이티브 IME)로 입력
- 웹/APK 는 기존 Toast UI 위지윅 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:56:20 +09:00
ByungCheol 7f6a4c62e5 fix: PC(Electron) 위지윅 한글 IME 중복 → 에디터를 마크다운 모드로 전환
CI / build (push) Failing after 12m48s
- 진단: 제목(일반 입력)은 정상, 본문 위지윅(contenteditable)만 첫 자음 중복 → ProseMirror+Electron IME 버그
- MarkdownEditor: Electron 감지 시 initialEditType='markdown'(텍스트 기반 입력) → IME 정상. 웹/APK는 위지윅 유지
- 앞서의 불필요한 조합(composition) 처리 코드 제거(원인이 아니었음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:50:43 +09:00
ByungCheol 561c56a8e0 fix: PC(Electron) 한글 IME 첫 글자 중복 입력 수정
CI / build (push) Failing after 15m13s
- webPreferences spellcheck:false — Chromium 맞춤법 검사기가 한글 조합을 방해해
  첫 자음이 중복('ㅅ사')되던 Electron 알려진 버그 회피 (APK/모바일은 정상이라 데스크톱 전용 이슈)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:43:17 +09:00
ByungCheol f86f8343ef fix: 게시판 에디터 한글 IME 첫 자음 중복 입력 수정
CI / build (push) Failing after 13m5s
- MarkdownEditor: IME 조합(compositionstart~end) 중에는 getMarkdown/emit 보류,
  조합 완료 시 한 번만 반영 → 조합 중 직렬화로 자음이 중복되던('ㅅ사') 문제 해소
- 조합 중에는 watch 의 setMarkdown 도 건너뜀

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:30:26 +09:00
ByungCheol dc31cf4bfa fix: 데스크톱 캐시로 인한 최신 화면 미반영 해결 + HTML no-cache
CI / build (push) Failing after 13m13s
- electron/main.cjs: 시작 시 HTTP 캐시 클리어 → 라이브 사이트 최신 프론트 항상 로드
  (루트 '/' HTML 이 캐시돼 옛 번들이 뜨던 문제 — '로그인 전 사이드바' 미반영 원인)
- nginx: location / 에 Cache-Control no-cache 추가(웹/PWA 진입 HTML 항상 최신)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:25:21 +09:00
ByungCheol 0f9f7b4507 feat: PC 로그인 전 빈 사이드바 제거(중앙 정렬) + 데스크톱 창 크기 기억
CI / build (push) Failing after 10m45s
- App.vue: 비로그인 시 사이드바 숨김·단일 컬럼 그리드 → 콘텐츠가 창 중앙 정렬
  (프론트 변경이라 데스크톱은 재설치 없이 자동 반영)
- electron/main.cjs: 기본 창 1024x720 + 마지막 크기/위치/최대화 상태 저장·복원

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:17:42 +09:00
ByungCheol 786f7aae61 feat: 웹 안내 페이지에 PC 버전(Windows) 다운로드 링크 추가
CI / build (push) Failing after 14m5s
- WebOnlyNotice: PC 버전 다운로드 버튼(/download/SlimBudget-Setup.exe) + 모바일(준비 중)
- nginx: /download/ 위치 추가 — 배포(dist 교체)에 안 지워지는 /var/www/sb-downloads 에서 서빙
- 설치파일 이름 고정(SlimBudget-Setup.exe)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:54:28 +09:00
ByungCheol 9de26cf417 feat: 데스크톱 클라이언트가 라이브 사이트 직접 로드 — 프론트 변경 자동 반영(재설치 불필요)
CI / build (push) Failing after 14m5s
- electron/main.cjs: dist 번들 → https://app.sblog.kr 직접 로드
  · 같은 출처라 webSecurity:false(CORS 우회) 제거, 오프라인 시 '다시 시도' 안내 화면
  · 앱 외 도메인 링크만 기본 브라우저로
- package.json: 설치파일에 dist 미포함·build:electron 제거(원격 로드라 불필요)
- .env.electron 삭제

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:39:28 +09:00
ByungCheol c128948d39 feat: 계좌 이용내역 월별 표시 — 길어지는 스크롤 해소
CI / build (push) Successful in 34s
- 계좌(은행/카드) 펼침 시 전체 평면 리스트 대신 월 단위로 표시
  · ◀ YYYY.MM ▶ 월 네비게이션(데이터 있는 월만, 빈 달 건너뜀) + 그 달 합계
  · 기본은 내역이 있는 가장 최근 월, 월 필터는 클라이언트단(walletEntries 재사용, 백엔드 무변경)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:29:54 +09:00
ByungCheol cb5cf1f0bf feat: 브랜드 앱 아이콘 + Windows 데스크톱(Electron) 클라이언트
CI / build (push) Failing after 12m35s
- 앱 아이콘: 브랜드(초록 #00bc7e + 흰 막대 차트) 적응형 아이콘 생성(@capacitor/assets)
  · assets/ 에 SVG 소스 + 전 해상도 mipmap 재생성, 적응형 XML 인셋 제거(배경 풀블리드)
- 데스크톱: Electron 로 dist 를 감싼 Windows 설치파일(.exe)
  · vite electron 모드(상대 base)·라우터 해시 히스토리·App 게이트 Electron 감지
  · webSecurity:false 로 file:// CORS 우회, 백엔드는 https://app.sblog.kr/api
  · scripts: build:electron / electron:dev / electron:build(electron-builder NSIS)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:20:55 +09:00
ByungCheol 095ba942eb feat: 홈 캘린더 일별 내역 팝업(모달)화 + 릴리스 노트 갱신
CI / build (push) Failing after 10m39s
- HomeView: 날짜 탭 시 아래 인라인 패널 대신 중앙 팝업으로 내역 표시(시인성)
  · @mouseenter 제거로 터치 '두 번 탭' 버그 해소(단일 탭 동작)
- docs/release-2026-06-06.md: 이어진 세션 변경(하단 내비·설정/계정·매매수정·보안·테스트/배포·안드로이드) 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:09:05 +09:00
ByungCheol 006fc87f70 feat: 투자 매매내역 최근N+전체보기 모달·매매 수정 + 안드로이드 인셋 보정
CI / build (push) Successful in 26s
- InvestPortfolio: 인라인은 최근 5건만, 초과분은 '전체 보기' 스크롤 모달
- 매매 행에 수정(✏)/삭제 버튼 — PUT /trades/{id} 로 매매 수정
- MainActivity: 콘텐츠 루트에 시스템 바 인셋 패딩(CONSUMED) — 에지투에지 소프트키/상태바 겹침 보정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:42:47 +09:00
ByungCheol 16b4bce136 feat: 바텀 내비 하단 고정(소프트키 위)·아이콘화 + 비밀번호 변경 정보변경 화면 통합
CI / build (push) Failing after 14m36s
- AppBottomNav: position fixed 하단 고정(스크롤 무관 상시 노출), 아이콘 전용(28px)
- MainActivity: 시스템 바 인셋만큼 웹뷰 패딩 — 에지투에지(targetSdk36)에서 소프트키 겹침 해소
- ProfileEditView: 정보변경 화면에 비밀번호 변경 섹션 흡수(현재 비밀번호 재사용)
- AppHeader 비밀번호 아이콘·App.vue ChangePasswordModal 마운트 제거(진입점 일원화)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:49:07 +09:00
ByungCheol e76b5383d3 fix: 바텀 내비게이션 라벨 가독성 개선
CI / build (push) Failing after 14m36s
- 라벨 0.68→0.8rem·굵기 500·불투명도 0.92, 아이콘 22→25px, 바 높이 56→62px
- 텍스트가 너무 작아 메뉴 구분이 안 되던 문제 해소

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:29:56 +09:00
ByungCheol 15222c1971 @
CI / build (push) Failing after 14m35s
chore: 번들 청크 경고 한도 상향 — 마크다운 에디터는 라우트 지연 로딩

- toast-ui 에디터(~1MB)는 게시판 작성/상세에서만 동적 로딩되어 메인 번들과 분리됨
- 의도된 지연 청크이므로 chunkSizeWarningLimit 1200 으로 현실화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-06-06 15:04:15 +09:00
ByungCheol 43e2de6c3c ci: 배포 워크플로에 테스트 게이트 추가 — 실패 시 배포 중단
CI / build (push) Failing after 11m1s
- deploy.yaml 은 CI와 별개로 push:[main]에 트리거되며 테스트를 건너뛰고 있었음
  (백엔드 bootJar -x test, 프론트 테스트 스텝 없음) → 게이트 무력
- 배포 잡 안에서 테스트를 먼저 실행하도록 변경(Gitea는 워크플로 간 needs 불안정)
  · 백엔드: clean bootJar -x test → clean build (test 포함)
  · 프론트: Install → Test(npm test) → Build 순으로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:27:37 +09:00
ByungCheol c9ff605ac9 feat: 앱 하단 내비게이션·설정/계정정보·가입정보 변경 + 프론트 테스트·CI 게이트
CI / build (push) Failing after 10m29s
- 하단 내비게이션(뒤로/앞으로/홈/새로고침/설정), 사이드바 홈 제거
- 설정 화면: 계정정보·앱 버전(package.json)·App Data 삭제
- 가입정보 변경: 비밀번호 재인증 → 이름/이메일 수정
- 로그인 후 대시보드(/) 이동, 로그인 전 햄버거·네이버 로그인 버튼 숨김
- Vitest 도입(32): authApi/accountApi 와이어링, auth/ui 스토어, 로그인 플로우
- CI(.gitea): npm test 게이트 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:08:01 +09:00
ByungCheol c5e4c2dad7 feat: 웹 안내페이지(앱전용)·회원가입 제한/봇차단·카드 인식 보정
- 웹(브라우저)은 안내 페이지만, 실제 이용은 앱(Capacitor). 개발모드는 예외
- 회원가입: 약관동의, 관리자 제한 토글, 진입부터 차단, 허니팟
- 카드 알림: pending 건 계좌종류 '카드' 기본선택(현금 X), 확인실패 시 HTTP 상태 표시
- 네이티브 알림필터: '카드' 키워드 제거+광고성 표현 차단
- docs/release-2026-06-06.md 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 00:16:50 +09:00
ByungCheol 4e6a89fd7a feat: 홈 일별 캘린더·시세 갱신·게시판 카테고리·약관동의·계좌 마스킹
- 홈: 6월 예산 아래 일별 수입/지출 캘린더(마우스오버/탭 시 내역 목록)
- 투자: 시세 자동조회 버튼/진입 시 갱신, 보유종목 2줄 표기, 평가액 직접입력
- 게시판: 커뮤니티/짠테크 수다방/재테크 팁 분리, 사이드바 영역 구분선
- 회원가입: 이용약관·개인정보 수집 동의(필수 체크)
- 보안: 계좌번호 화면 마스킹(끝 4자리+눈 토글)
- 정기→고정지출, 내역/정기 라디오·sticky 저장, 태그 드래그 정렬
- fix: 모바일웹 새로고침 시 로그인 팝업(restore localStorage 우선)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:01:33 +09:00
ByungCheol 67aa635dd8 refactor: 메뉴 평탄화 + 홈/가계부 대시보드 통합 + 통계 분리
CI / build (push) Failing after 12m23s
- 사이드바: '가계부' 상위 메뉴 제거, 단일 레벨로(홈/가계부 내역/통계/정기거래/계좌/분류/예산/태그/자유게시판)
- 자유게시판을 가계부 메뉴들 아래로 이동
- 홈(/)을 통합 대시보드로, 기존 가계부 대시보드 차트는 통계(/account/stats)로 이동
- /account → /account/entries 리다이렉트, 대시보드 제목 '통계'로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:03:56 +09:00
ByungCheol 3be1d8792c feat: 카드 결제 알림 자동인식 네이티브 플러그인
CI / build (push) Failing after 11m11s
- CardNotifListenerService: 결제 알림(금액+승인/결제/카드) 가로채 백엔드로 전송
  (토큰은 Capacitor Preferences에서, 결제성 알림만 전송)
- CardNotifPlugin: 알림 접근 권한 확인/설정 열기
- AndroidManifest 서비스 등록, MainActivity 플러그인 등록
- 가계부 화면: 네이티브 미허용 시 '알림 접근 권한' 안내 배너

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:55:48 +09:00
ByungCheol 633aa54274 feat: 카드 알림 자동인식 - 확인 필요 배지/확정 UI/카운트
CI / build (push) Failing after 14m50s
- 미확인(pending) 내역에 '확인필요' 배지 + 행 강조 + '확인' 버튼(즉시 확정)
- 수정 저장 시 미확인 건은 확정 처리
- 가계부 헤더에 '확인 필요 N건' 카운트 배지
- accountApi: pendingCount/confirmEntry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:51:43 +09:00
ByungCheol 8f8744ba4b fix: 모바일 웹 가계부 내역 진입 불가(청크 로드 실패) 해결
CI / build (push) Failing after 12m17s
- @capacitor/camera 정적 import 제거 → 네이티브에서만 동적 import
  (웹은 카메라를 호출하지도 않는데 정적 import가 AccountView 청크 평가를 깨뜨려
   모바일 웹에서 진입 시 청크 로드 실패 → onError 새로고침 → 로그인 루프 발생)
- router.onError 경로별 1회 새로고침으로 제한(무한 루프 방지) + 성공 시 플래그 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:29:00 +09:00
ByungCheol f45be09ff1 fix: 배포 후 청크 로드 실패 시 자동 새로고침 + index.html no-cache
CI / build (push) Failing after 14m26s
- 모바일 웹에서 옛 index.html 캐시가 삭제된 옛 청크를 불러 메뉴(가계부 내역 등)가
  안 열리는 문제 → router.onError 로 청크 실패 감지 시 목적지로 전체 새로고침
- nginx 설정에 index.html no-cache 추가(재발 방지) — 서버 적용 필요

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:11:51 +09:00
ByungCheol 7acd2163fb feat: 영수증 등록 방식 선택을 커스텀 레이어 팝업으로 변경
CI / build (push) Failing after 14m14s
- OS 기본 프롬프트 대신 앱 톤의 모달(카메라 촬영 / 갤러리에서 선택 카드)
- 네이티브: CameraSource.Camera / Photos 직접 호출, 웹: file input(capture 토글) 폴백

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:07:01 +09:00
ByungCheol f50859140d feat: 영수증 인식 중 무한 로딩 바 표시
CI / build (push) Failing after 14m26s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:01:51 +09:00
ByungCheol 537cea31b0 feat: 영수증 OCR 상호명→메모, 카드사 감지→카드 자동선택
CI / build (push) Failing after 15m1s
- 상호 추출 개선(GS25 등 숫자 포함 상호 허용, 날짜·전화·사업자·주소 제외)
- 카드 결제 영수증의 카드사 감지 → 등록된 카드(issuer/name 매칭) 자동 선택
- 매칭 실패해도 카드결제면 계좌종류를 카드로 좁혀줌
- 결과 표시에 💳카드명 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:18 +09:00
ByungCheol 41d6f11531 fix: 투자 소수점 매수 금액 입력 모드 추가
CI / build (push) Failing after 15m19s
- 매매 모달에 '입력 방식: 단가/금액' 선택 추가
- 금액 모드: 수량+총금액 입력 → 단가=round(금액/수량) 자동계산 (소수점 매수)
- 예) 0.6주를 10,000원에 매수 시 6,000원이 아니라 10,000원으로 정확히 기록
- 단가/금액 상호 미리보기 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:44:01 +09:00
ByungCheol 68dd926cce feat: 영수증 OCR을 Google Vision(백엔드) 방식으로 전환
CI / build (push) Failing after 14m1s
- 이미지를 백엔드(/account/ocr/receipt)로 업로드 → Vision 텍스트 → 금액·날짜·상호 파싱
- 업로드 전 이미지 축소(최대 1600px JPEG), Tesseract.js 의존성 제거
- 진행률 표시 제거(인식 중…), 정확도·속도 대폭 개선

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:37:35 +09:00
ByungCheol 715dae70ab perf: 영수증 OCR 속도·정확도 개선
CI / build (push) Failing after 13m42s
- Tesseract 워커 1회 생성 후 재사용(매 스캔 모델 재로드 제거) → 2번째부터 빠름
- 인식 전 이미지 축소(최대 1600px)+Otsu 이진화로 숫자 인식률 향상
- 금액 파싱 개선: 합계 키워드 다음 줄까지 탐색, 콤마 금액 우선, 전화/사업자/카드번호·시간 제외

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:02:34 +09:00
ByungCheol e1d2ec5bd7 feat: 영수증 스캔 네이티브 카메라(@capacitor/camera)
CI / build (push) Failing after 15m10s
- 앱에서 촬영/갤러리 선택 프롬프트로 영수증 입력 (웹은 파일선택 폴백)
- AndroidManifest CAMERA 권한 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:50:54 +09:00
ByungCheol efbe298a1f feat: 홈 대시보드 + 영수증 OCR + 카드 할부 + 소수점 매수 등 UI 개선
CI / build (push) Failing after 13m56s
- 홈: 로그인 시 요약(이번달 수입/지출, 순자산, 예산대비지출 바)+바로가기, 비로그인 랜딩
- 영수증 OCR(온디바이스 Tesseract.js): 금액·날짜·상호 추출해 내역 폼 자동입력
- 내역 카드 할부(2~24개월) 입력/표시
- 투자 소수점 수량 입력(step=any)
- 계좌 관리 등록버튼 탭 라인 우측 이동, 정기거래 3단 레이아웃
- 목록 좌측 여백 제거(분류/예산/태그/계좌), 대시보드 일/주 버튼 제거, 도넛/파이 차트 잘림 수정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:42:07 +09:00
ByungCheol 483cf755ed feat: 예산 예상수입 월별 + 계좌 관리 드래그앤드랍 정렬
CI / build (push) Failing after 12m7s
- 예산: 예상 수입을 해당 월별로 조회/저장(월 이동 시 갱신)
- 계좌 관리: SortableJS 드래그 정렬(터치 지원), 타입별 순서 저장

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 22:54:41 +09:00
ByungCheol 252f00e527 feat: 분류 드래그앤드랍 정렬 + 예산화면 예상수입/예산 비교
CI / build (push) Failing after 13m46s
- 분류 관리: SortableJS 드래그 정렬(터치 지원), 순서는 내역 추가 화면에도 반영
- 예산: 월 예상 수입 입력 + 수입 대비 총 예산 비교(여유/초과)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 22:37:56 +09:00
ByungCheol 63a8a71e3b feat: 로그인 영속화 + 내역 계좌선택 분리 + 일별 그룹 레이아웃
CI / build (push) Failing after 11m2s
- 로그인 유지: Capacitor Preferences 로 토큰 영속화(앱 재실행 시 로그아웃 방지),
  시작 시 세션 복원 후 마운트
- 내역 추가: 계좌 종류(계좌/카드/대출/증권) 콤보 선택 → 해당 종류만 목록에
- 가계부 내역: 일별 그룹 + 일 수입/지출 합계, 항목은 분류·계좌·금액 / 메모·태그(2줄),
  per-row 날짜 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 22:10:23 +09:00
ByungCheol 385f6c970e feat: 로그인 모달 '로그인 상태 유지' 체크박스 (자동 로그인)
CI / build (push) Failing after 15m6s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:47:03 +09:00
ByungCheol 964e615c54 feat: 홈 대시보드 임시 히어로 이미지 추가
CI / build (push) Failing after 14m19s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:32:08 +09:00
ByungCheol 3cc3328b23 fix: 운영 웹 API를 상대경로(/api)로 교정 (localhost 박힘 문제)
CI / build (push) Failing after 10m31s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:25:57 +09:00
ByungCheol 772bc85bae fix: 토스트 에디터 한글(IME) 조합 끊김 수정
CI / build (push) Successful in 22s
부모가 되돌린 echo 값이면 setMarkdown 생략 → 문자 조합 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:52:23 +09:00
ByungCheol 840ec84e0d feat: 안드로이드 하이브리드 앱(Capacitor) 구성
CI / build (push) Failing after 13m47s
- capacitor.config + android/ 네이티브 프로젝트
- 앱 전용 빌드(.env.capacitor, build:app), 뒤로가기·safe-area
- docs/ANDROID.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:13:12 +09:00
ByungCheol 245c026e50 feat: 사용자 관리를 회원 관리(관리자)로 전환
CI / build (push) Failing after 14m56s
데모 user 대신 member 기반. 목록·검색·역할·상태 변경·삭제, 관리자 전용·모바일 대응

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:02:05 +09:00
ByungCheol 510eb0f4f8 feat: 비밀번호 변경 UI (헤더 잠금 아이콘 + 모달)
CI / build (push) Failing after 14m54s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:46:57 +09:00
ByungCheol 9e81029a7d fix(ci): SSH 개인키 파일에서 CR 제거 (Windows CRLF 시크릿 대응)
CI / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:40:41 +09:00
ByungCheol 09cfb8e562 ci: 프론트 자동 배포 구현 (Nginx 정적호스팅, main push)
CI / build (push) Failing after 10m30s
- deploy.yaml: dist 빌드 → SSH(tar)로 웹 루트 교체 → nginx reload
- deploy/nginx-sb-front.conf: SPA 폴백 + /api 프록시 샘플
- deploy/README.md: 시크릿·서버 준비 안내

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:55:23 +09:00
187 changed files with 24439 additions and 893 deletions
+10
View File
@@ -0,0 +1,10 @@
# 앱(Capacitor) 빌드 전용 환경변수 — `vite build --mode capacitor` 가 로드합니다.
# 앱 안에서는 상대경로(/api)가 서버로 가지 않으므로 백엔드 절대 URL 을 지정해야 합니다.
#
# [운영 서버 HTTPS] app.sblog.kr 의 /api 를 Nginx 가 백엔드로 프록시
VITE_API_BASE_URL=https://app.sblog.kr/api
#
# [로컬 PC + 실기기] 같은 Wi-Fi
# VITE_API_BASE_URL=http://172.30.1.43:8080/api
# [에뮬레이터]
# VITE_API_BASE_URL=http://10.0.2.2:8080/api
+2 -2
View File
@@ -1,2 +1,2 @@
# 운영 환경: 실제 백엔드 주소로 교체하세요 # 운영 웹 빌드(`npm run build`) — Nginx 가 같은 도메인의 /api 를 백엔드(8080)로 프록시하므로 상대경로 사용
VITE_API_BASE_URL=http://localhost:8080/api VITE_API_BASE_URL=/api
+6 -1
View File
@@ -1,8 +1,9 @@
name: CI name: CI
on: on:
# main 은 Deploy 워크플로가 테스트+빌드를 수행하므로 CI 중복 실행 제외(배포 시간 단축).
push: push:
branches: [main, dev] branches: [dev]
pull_request: pull_request:
branches: [main, dev] branches: [main, dev]
@@ -29,6 +30,10 @@ jobs:
npx oxlint . npx oxlint .
npx eslint . npx eslint .
# 단위/컴포넌트 테스트 — 실패 시 빌드/배포 차단(게이트)
- name: Test
run: npm test
- name: Build - name: Build
run: npm run build run: npm run build
+45 -21
View File
@@ -1,12 +1,17 @@
name: Deploy (manual) name: Deploy
# 배포 대상이 정해지면 아래 단계의 주석을 풀고 시크릿을 등록하세요. # main 푸시 시 자동 배포: dist 빌드 → 서버 Nginx 루트로 업로드 → nginx reload
# 저장소 Settings > Actions > Secrets 에 등록 필요: #
# DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_PATH # 저장소 Settings > Actions > Secrets 에 등록:
# DEPLOY_HOST 배포 서버 호스트/IP
# DEPLOY_USER SSH 계정 (nginx reload 무비밀번호 sudo 필요 — deploy/README.md 참고)
# DEPLOY_SSH_KEY SSH 개인키 (PEM 전체)
# DEPLOY_PATH Nginx 웹 루트 (예: /var/www/sb-front)
# DEPLOY_PORT SSH 포트 (선택, 기본 22)
on: on:
push:
branches: [main]
workflow_dispatch: {} workflow_dispatch: {}
# push:
# tags: ['v*']
jobs: jobs:
deploy: deploy:
@@ -21,22 +26,41 @@ jobs:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- name: Install
run: npm ci
# 테스트 게이트 — 실패 시 이후 빌드/배포 스텝이 실행되지 않음
- name: Test
run: npm test
- name: Build - name: Build
run: npm run build
- name: Deploy to Nginx (SSH)
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: | run: |
npm ci set -euo pipefail
npm run build PORT="${DEPLOY_PORT:-22}"
DEST="${DEPLOY_PATH:-}"
if [ -z "$DEST" ] || [ "$DEST" = "/" ]; then
echo "::error::DEPLOY_PATH 시크릿이 비어 있거나 잘못되었습니다."; exit 1
fi
# ===== 배포 예시 (택1) — 사용 시 주석 해제 ===== mkdir -p ~/.ssh && chmod 700 ~/.ssh
# 1) 정적 파일을 원격 서버로 rsync (Nginx 등 정적 호스팅) printf '%s\n' "$DEPLOY_SSH_KEY" | tr -d '\r' > ~/.ssh/id_deploy
# - name: Deploy via SSH/rsync chmod 600 ~/.ssh/id_deploy
# uses: appleboy/scp-action@v0.1.7 ssh-keyscan -p "$PORT" "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
# with: SSH="ssh -p $PORT -i $HOME/.ssh/id_deploy -o StrictHostKeyChecking=yes"
# host: ${{ secrets.DEPLOY_HOST }}
# username: ${{ secrets.DEPLOY_USER }}
# key: ${{ secrets.DEPLOY_SSH_KEY }}
# source: "dist/*"
# target: ${{ secrets.DEPLOY_PATH }}
# strip_components: 1
- name: Notice echo "Deploy dist/ -> $DEPLOY_USER@$DEPLOY_HOST:$DEST"
run: echo "dist 빌드 완료. 배포 대상 지정 후 위 단계를 활성화하세요." $SSH "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEST'"
# 기존 파일 정리 후 새 빌드 업로드 (tar 파이프 — 추가 의존성 없음)
tar -czf - -C dist . | $SSH "$DEPLOY_USER@$DEPLOY_HOST" "find '$DEST' -mindepth 1 -delete && tar -xzf - -C '$DEST'"
# Nginx 설정 검증 후 reload (실패해도 배포 자체는 유지)
$SSH "$DEPLOY_USER@$DEPLOY_HOST" "sudo nginx -t && sudo nginx -s reload" || echo "nginx reload 생략됨"
echo "✅ 배포 완료"
+3
View File
@@ -14,6 +14,9 @@ dist-ssr
coverage coverage
*.local *.local
# Electron desktop build output
release-desktop
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
+106
View File
@@ -0,0 +1,106 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
# 릴리스 서명 (절대 커밋 금지)
*.jks
*.keystore
key.properties
+2
View File
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
+74
View File
@@ -0,0 +1,74 @@
apply plugin: 'com.android.application'
// 릴리스 서명: android/key.properties 가 있으면 사용, 없으면 디버그 서명으로 폴백.
def keystorePropsFile = rootProject.file("key.properties")
def keystoreProps = new Properties()
if (keystorePropsFile.exists()) {
keystoreProps.load(new FileInputStream(keystorePropsFile))
}
def hasReleaseKeystore = keystorePropsFile.exists()
android {
namespace = "kr.sblog.slimbudget"
compileSdk = rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "kr.sblog.slimbudget"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 3
versionName "1.0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
signingConfigs {
if (hasReleaseKeystore) {
release {
storeFile rootProject.file(keystoreProps['storeFile'])
storePassword keystoreProps['storePassword']
keyAlias keystoreProps['keyAlias']
keyPassword keystoreProps['keyPassword']
}
}
}
buildTypes {
release {
// 키스토어가 있으면 릴리스 서명, 없으면 디버그 서명(로컬 테스트용)
signingConfig hasReleaseKeystore ? signingConfigs.release : signingConfigs.debug
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
+25
View File
@@ -0,0 +1,25 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-camera')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-preferences')
implementation project(':capacitor-share')
implementation project(':capawesome-capacitor-google-sign-in')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
<!-- 카드 결제 알림 자동인식(알림 접근 리스너) — 스토어 첫 출시에서는 비활성화.
Google Play NotificationListenerService(알림 접근) 정책 심사 회피용으로 선언 제거.
추후 정당화/선언 후 재활성화하려면 아래 service 블록 주석을 해제할 것. -->
<!--
<service
android:name=".CardNotifListenerService"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
-->
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 영수증 OCR: 카메라 촬영 / 갤러리 선택 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>
@@ -0,0 +1,95 @@
package kr.sblog.slimbudget;
import android.app.Notification;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import org.json.JSONObject;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* 카드사 결제 푸시 알림을 가로채 백엔드로 전달한다.
* - 결제로 보이는 알림(금액+승인/결제/카드)만 전송 (개인정보 최소화)
* - 인증 토큰은 Capacitor Preferences(SharedPreferences "CapacitorStorage")의 token 사용
* - 백엔드가 파싱·중복제거·미확인 내역 생성
*/
public class CardNotifListenerService extends NotificationListenerService {
private static final String API_URL = "https://app.sblog.kr/api/account/entries/notification";
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
try {
if (sbn == null || sbn.getNotification() == null) return;
Bundle ex = sbn.getNotification().extras;
if (ex == null) return;
CharSequence titleCs = ex.getCharSequence(Notification.EXTRA_TITLE);
CharSequence textCs = ex.getCharSequence(Notification.EXTRA_TEXT);
CharSequence bigCs = ex.getCharSequence(Notification.EXTRA_BIG_TEXT);
String title = titleCs == null ? "" : titleCs.toString();
String text = (bigCs != null ? bigCs : (textCs == null ? "" : textCs)).toString();
if (!looksLikePayment(title + " " + text)) return;
String token = getSharedPreferences("CapacitorStorage", MODE_PRIVATE)
.getString("token", null);
if (token == null || token.isEmpty()) return;
final String pkg = sbn.getPackageName();
new Thread(() -> post(pkg, title, text, token)).start();
} catch (Exception ignore) {
// 알림 처리 실패는 무시 (앱 동작에 영향 없도록)
}
}
/**
* 결제/이체 알림 후보 판별: 금액(####원) + 승인/결제/출금/입금/이체/취소 키워드.
* '카드'는 광고에도 흔해 제외. 강한 신호 없이 광고성 표현이 있으면 제외.
* (1차 필터 — 최종 판별/분류는 백엔드 파서)
*/
private boolean looksLikePayment(String s) {
boolean money = s.matches("(?s).*\\d[\\d,]{2,}\\s*원.*");
boolean kw = s.contains("승인") || s.contains("결제") || s.contains("출금")
|| s.contains("입금") || s.contains("이체") || s.contains("송금");
if (!money || !kw) return false;
boolean strong = s.contains("승인") || s.contains("출금") || s.contains("입금")
|| s.contains("이체") || s.contains("송금") || s.contains("취소") || s.contains("환불");
boolean adLike = s.matches("(?s).*(이벤트|광고|혜택|쿠폰|할인|적립|포인트|캐시백|마일리지|리워드|당첨|응모|추첨|프로모션|배너|무료|증정|모집).*");
return strong || !adLike;
}
private void post(String pkg, String title, String text, String token) {
HttpURLConnection conn = null;
try {
URL url = new URL(API_URL);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setDoOutput(true);
JSONObject body = new JSONObject();
body.put("app", pkg);
body.put("title", title);
body.put("text", text);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.toString().getBytes(StandardCharsets.UTF_8));
}
conn.getResponseCode(); // 요청 전송 (응답은 무시)
} catch (Exception ignore) {
// 네트워크 실패는 무시
} finally {
if (conn != null) conn.disconnect();
}
}
}
@@ -0,0 +1,39 @@
package kr.sblog.slimbudget;
import android.content.Intent;
import android.provider.Settings;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
/**
* 카드 결제 알림 자동인식 플러그인.
* - isEnabled: 알림 접근 권한(NotificationListener) 허용 여부
* - openSettings: 알림 접근 설정 화면 열기
* 실제 알림 수신/전송은 CardNotifListenerService 가 담당한다.
*/
@CapacitorPlugin(name = "CardNotif")
public class CardNotifPlugin extends Plugin {
@PluginMethod
public void isEnabled(PluginCall call) {
String pkg = getContext().getPackageName();
String flat = Settings.Secure.getString(
getContext().getContentResolver(), "enabled_notification_listeners");
boolean enabled = flat != null && flat.contains(pkg);
JSObject ret = new JSObject();
ret.put("enabled", enabled);
call.resolve(ret);
}
@PluginMethod
public void openSettings(PluginCall call) {
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
call.resolve();
}
}
@@ -0,0 +1,35 @@
package kr.sblog.slimbudget;
import android.os.Bundle;
import android.view.View;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
registerPlugin(CardNotifPlugin.class);
super.onCreate(savedInstanceState);
// targetSdk 35+ 의 에지투에지 강제 환경에서 콘텐츠가 상태바/소프트키 뒤로 깔리는 것을 방지.
// 구글 권장 패턴: 콘텐츠 루트에 시스템 바 인셋만큼 패딩을 적용하고 인셋을 소비(CONSUMED)한다.
// → 헤더는 상태바 아래, 하단 고정 내비는 소프트키 위에 위치.
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
final View content = findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
// 키보드(IME)가 올라오면 그 높이만큼 하단 패딩 → WebView가 키보드 위로 줄어든다.
// (입력 모달이 키보드에 가려지지 않고, 포커스된 입력칸이 보이도록 재배치됨)
int bottom = Math.max(bars.bottom, ime.bottom);
v.setPadding(bars.left, bars.top, bars.right, bottom);
return WindowInsetsCompat.CONSUMED;
});
ViewCompat.requestApplyInsets(content);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">돈돼지가계부</string>
<string name="title_activity_main">돈돼지가계부</string>
<string name="package_name">kr.sblog.slimbudget</string>
<string name="custom_url_scheme">kr.sblog.slimbudget</string>
</resources>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
@@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
+29
View File
@@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.google.gms:google-services:4.4.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+24
View File
@@ -0,0 +1,24 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
include ':capawesome-capacitor-google-sign-in'
project(':capawesome-capacitor-google-sign-in').projectDir = new File('../node_modules/@capawesome/capacitor-google-sign-in/android')
+22
View File
@@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+5
View File
@@ -0,0 +1,5 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'
+16
View File
@@ -0,0 +1,16 @@
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
androidxActivityVersion = '1.11.0'
androidxAppCompatVersion = '1.7.1'
androidxCoordinatorLayoutVersion = '1.3.0'
androidxCoreVersion = '1.17.0'
androidxFragmentVersion = '1.8.9'
coreSplashScreenVersion = '1.2.0'
androidxWebkitVersion = '1.14.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.3.0'
androidxEspressoCoreVersion = '3.7.0'
cordovaAndroidVersion = '14.0.1'
}
+5
View File
@@ -0,0 +1,5 @@
// 앱 아이콘은 이제 '돈돼지' 래스터 이미지(assets/piggy-fg.png) 기반입니다.
// 재생성: piggy-fg.png → sharp 합성으로 icon-foreground/background/only.png 만든 뒤
// `npx @capacitor/assets generate --android` 로 안드로이드 아이콘 생성.
// (구 SVG 래스터화는 더 이상 사용하지 않음 — 실행해도 PNG 를 덮어쓰지 않도록 비활성화)
console.log('skip: icons are raster (piggy). use piggy-fg.png + @capacitor/assets generate')
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<rect width="1024" height="1024" fill="#00bc7e"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<!-- 적응형 아이콘 전경: 안전영역(중앙 ~66%) 안에 흰 막대 차트 -->
<g fill="#ffffff">
<rect x="262" y="492" width="120" height="280" rx="60"/>
<rect x="452" y="372" width="120" height="400" rx="60"/>
<rect x="642" y="252" width="120" height="520" rx="60"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<!-- 레거시(비적응형) 아이콘: 초록 라운드 사각 + 흰 막대 차트 (브랜드 마크) -->
<rect width="1024" height="1024" rx="224" fill="#00bc7e"/>
<g fill="#ffffff">
<rect x="240" y="544" width="108.8" height="256" rx="54.4"/>
<rect x="457.6" y="400" width="108.8" height="400" rx="54.4"/>
<rect x="675.2" y="256" width="108.8" height="544" rx="54.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

+132
View File
@@ -0,0 +1,132 @@
# 돈돼지 가계부 — Play 스토어 첫 출시 진행 상황
> 최종 업데이트: 2026-06-28. 첫 정식 출시(무료 앱) 준비 로그 + 남은 로드맵.
> 관련 문서: [play-store-listing.md](play-store-listing.md) · [production-access-draft.md](production-access-draft.md)
---
## ✅ 완료된 작업
### 출시 전략 결정
- **무료 앱**으로 첫 출시 (수익화 기능은 순차 도입)
- 첫 출시 기능 플래그 (`src/config/features.js`, 전부 `false`):
- `BILLING_ENABLED=false` — 인앱결제 스텁이라 결제 없이 프리미엄 부여되는 악용 차단
- `ADS_ENABLED=false` — 애드핏 미승인 + 데이터 안전 양식 단순화(광고 식별자 미수집)
- `CARD_NOTIF_ENABLED=false` — NotificationListenerService 정책 심사 회피
- 패키지명 `kr.sblog.slimbudget` **유지**(출시 후 영구 고정. 사용자 비노출이라 무방)
### 인프라 / 정책 페이지
- **www.sblog.kr** 도메인 + 인증서 확장 (app.sblog.kr와 동일 dist 서빙)
- 공개 URL: `/privacy`, `/terms`, `/account-deletion` (모두 200 확인)
- 개인정보처리방침 광고 문구 → "현재 광고 없음 + 향후 도입 가능" 미래형으로 정합화
### 빌드 / 서명
- **AAB**: `android/app/build/outputs/bundle/release/app-release.aab`
- targetSdk 36, versionCode 1, versionName 1.0
- 업로드 키(CN=Don Dwaeji) 서명 확인 (디버그 아님)
- 결제·광고·카드알림 전부 OFF 클린 (NotificationListener 권한 없음 검증)
- 키스토어: `android/upload-keystore.jks` + `android/key.properties` (gitignore, **이 PC에만 존재 — 분실 시 업데이트 불가, 반드시 백업**)
### 스토어 그래픽 (`brand/store/`)
- `icon-512.png` (512×512 불투명)
- `feature-1024x500.png` / `feature-1024x500-text.png` (피처그래픽, 택1)
- 생성기 `brand/gen-store-assets.mjs` + 마스터 `brand/logo-1024.png`
### 스크린샷 (`brand/screenshots/`, 7컷, 실기기 1080×2340)
- shot0 홈 대시보드(리드) / shot1 가계부 내역 / shot2 통계 / shot3 고정지출 / shot4 계좌 / shot5 게시판 / shot6 게시글 상세
### 등록 문서
- `brand/play-store-listing.md` — 등록정보(제목·설명)·데이터안전 양식·콘텐츠등급 답안
- `brand/production-access-draft.md` — 프로덕션 액세스 신청서 초안(4문항)
### 코드 수정 (배포 완료)
- 인앱결제 진입점 비활성화 (UpgradeView "곧 출시 예정", AccountInfoView 정기결제 카드 숨김)
- 광고 비활성화 (AdBanner + App.vue 게이팅, 하단 광고 여백 제거)
- 둘러보기(데모) 모드 메뉴 접근 버그 수정 (AppHeader 햄버거 `demo.on` 노출)
- 계정삭제 공개 페이지 추가
### 테스트 데이터 (캡처용, `sb_bt/scripts/`, gitignore 로컬 전용)
- 테스트 계정 `kenneth73510@gmail.com` (DB로 PREMIUM 부여)
- `seed-test-kenneth.sql`(계정+가계부), `seed-test-board.sql`(게시판 더미)
- `cleanup-test-board.sql`(게시판 더미 삭제 — **캡처 후 정리 완료**)
---
## 🚧 현재 상태 (2026-06-30)
- ✅ 개발자 계정 신원확인 완료 → 앱 생성 완료(`kr.sblog.slimbudget`)
- ✅ 필수 설정(데이터안전·콘텐츠등급(전체이용가)·앱액세스(둘러보기)·광고없음·비거래자·방침URL) 완료
- ✅ 스토어 등록정보·이미지(아이콘512·피처·스크린샷7) 완료
-**비공개 테스트 트랙에 v2(AAB) 게시 → 검토 통과**
- ✅ 구글 로그인 SHA-1 이슈 해결(Play 앱서명 SHA-1 등록) → 로그인 정상
-**테스터 12명 opt-in 대기**(업체 의뢰) → 14일 → 프로덕션 신청
- 📌 조직 계정 전환은 보류(사업자등록 → DUNS 필요. 기프트콘 기능 때 함께)
---
## 📋 남은 로드맵 (프로덕션까지)
1.**테스터 12명 opt-in** (업체 의뢰) — 14일 카운트 시작 조건
2.**14일 연속** 12명 유지 → **프로덕션 액세스 신청** (자료: production-access-draft.md)
3. **v3 AAB 업로드** — 이번 세션 변경(외화·월별예산·전체화면모달 등) 반영 (14일 카운트 리셋 안 됨)
4. 승인 → **프로덕션 출시**
### ⚠️ 폐쇄 테스트 의무 (개인 계정 + 2023-11-13 이후 개설)
- **테스터 12명 이상 · 14일 연속** 폐쇄 테스트 후 프로덕션 신청 가능
- 실기기·실계정 필수 (에뮬·봇·중복 불인정)
- 14일 카운트 = "릴리스 승인 + 12명 opt-in" 둘 다 충족 시점부터
- 테스터 확보: 지인 비공개 방침 → 품앗이 커뮤니티(무료·안전) 또는 평판 좋은 유료 서비스(실사용 보장형만)
### 익명 셋업 체크리스트
- [ ] 전용 Gmail 생성 (개발자 연락처 + 품앗이용)
- [ ] 공개 개발자 이름 → 브랜드명 (법적이름은 구글 내부 전용, 비공개)
- [ ] 연락처 이메일(계정+앱) → 전용 메일
- [ ] 거래자 상태 → **비거래자(Non-trader)** (주소 비공개)
- [ ] 품앗이 노출은 전용 Gmail로만
---
## ✅ 적용 완료 (전부 웹/소스 배포됨)
**v2(versionCode 2, 1.0.1, 06-29)에 포함 — 비공개 테스트 게시·검토통과**
- 신규 유저 빈 상태 CTA(홈 '시작하기'·내역·계좌) / 오프라인·네트워크 오류 배너
- 앱 잠금(PIN, 옵트인) + 카메라/갤러리 복귀 오작동 수정
- 전역 에러 폴백(흰 화면 방지) / 안드로이드 뒤로가기 통합(오버레이→뒤로→종료토스트)
- 분류 순서변경 보호('순서변경' 토글)
**v2 이후 추가 — 웹 배포됨, v3 빌드 대기**
- 앱 잠금 **1시간 유예**(잦은 PIN 입력 해소)
- **외화 입/지출** — 통화+환율 자동조회(open.er-api.com 무료)→원화 환산, 원본 보존. `amount`는 환산 원화라 통계·예산 무변경
- **월별 예산 + 전월/다음달 복사** — budget에 year/month 추가, 기존 예산은 현재 월로 1회 이관
- 내역추가 **전체화면 모달**(바깥 닫힘 방지) / 분류 **아코디언**(대분류 삼각형) / 계좌 셀렉트 정리(종류별 안내·이름만)
- 구글 로그인 **Play 서명 SHA-1 등록**(Google Cloud Android OAuth 클라 추가)
---
## 📌 남은 백로그 (출시 후 업데이트)
1. **앱 업데이트 안내** — 새 버전 알림 + 구글플레이 바로가기
2. **관리자 신고글 목록 페이지**
3. **가계부 작성 포인트** — 건당 10P, 일 50P 제한
4. **게시판 포인트 정책 개편** — 작성 50P(도배 제외)·키워드필터·추천+5/비추-1(일50)·남글추천+1(일10)/취소-1
5. (선택) 첫 실행 온보딩 / Play 인앱 리뷰 / 생체 인증 / 외화 환율 자동 고도화
6. **조직 계정 전환 + 기프트콘 교환** — 사업자등록→DUNS→앱이전, B2B 기프트콘 매입
### 🤖 AI 기능 (★ 정식 출시 전 적용 희망)
- 전제: **LLM API 키**(Claude Haiku 4.5 권장 — 학습 미사용·톤 우수·저렴 / 또는 Gemini 무료쿼터) + **개인정보처리방침·데이터안전 갱신**(제3자 LLM 추가). 백엔드 프록시·키 서버보관·실패 시 폴백.
- **AI 입력 보조** (편의성, 1순위)
-**자연어 한 줄 입력** — "어제 스타벅스 5천원" → 폼 자동 채움(기존 parseText를 LLM 기반으로). 음성=기기STT+①
-**영수증 사진 → AI 구조화** — 멀티모달 LLM이 총액·날짜·가맹점·분류 추천(기존 Vision OCR 보강/대체)
-**자동 분류 추천** — 가맹점/메모→분류 제안
- 원칙: AI는 폼 prefill만, **사용자 확인 후 저장**
- **AI 재무 코멘트** — 자산·수입·예상지출 기반 한마디("이러다 파산각" 등). 일1회 캐시·집계수치만 전송.
---
## 🔨 빌드 상태
- **v2** (versionCode 2, 1.0.1): 비공개 테스트 게시·검토통과
- **v3** (versionCode 3): 위 'v2 이후 추가' 반영 — 빌드 후 비공개 트랙 업로드 (테스터에게 최신본 제공, 14일 카운트 리셋 안 됨)
---
## 다음 세션 재개 지점
**테스터 12명 opt-in 진행상황 확인**(업체 의뢰) → 12명 + 14일 연속 충족되면 **프로덕션 액세스 신청**(`production-access-draft.md`).
v3 AAB를 비공개 트랙에 업로드해 테스터가 최신본을 받게.
+93
View File
@@ -0,0 +1,93 @@
// Play Console 스토어 그래픽 생성기.
// 입력: brand/logo-1024.png (사용자가 저장한 1024x1024 돈돼지 마스터 아트)
// 출력: brand/store/icon-512.png (스토어 고해상도 아이콘 512x512, 불투명)
// brand/store/feature-1024x500.png (피처 그래픽 — 텍스트 없음, 항상 안정)
// brand/store/feature-1024x500-text.png(피처 그래픽 — 한글 텍스트 포함, best-effort)
//
// 실행: node brand/gen-store-assets.mjs [입력경로]
import sharp from 'sharp'
import { mkdirSync, existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join, resolve } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
const SRC = resolve(process.argv[2] || join(__dirname, 'logo-1024.png'))
const OUT = join(__dirname, 'store')
// 브랜드 색 (아이콘 배경의 크림/옐로 계열)
const CREAM = '#FBF7E9'
const YELLOW = '#F2C94C'
const BROWN = '#B8860B'
if (!existsSync(SRC)) {
console.error(`\n[!] 마스터 아트가 없습니다: ${SRC}`)
console.error(` 1024x1024 돈돼지 PNG 를 위 경로에 저장한 뒤 다시 실행하세요.\n`)
process.exit(1)
}
mkdirSync(OUT, { recursive: true })
const log = (f) => console.log(' ✓', f)
// 1) 스토어 아이콘 512x512 — 불투명 크림 배경 위에 마스터를 합성(투명 PNG 대비)
async function icon512() {
const logo = await sharp(SRC).resize(512, 512, { fit: 'contain', background: CREAM }).png().toBuffer()
const out = join(OUT, 'icon-512.png')
await sharp({ create: { width: 512, height: 512, channels: 4, background: CREAM } })
.composite([{ input: logo }])
.flatten({ background: CREAM })
.png()
.toFile(out)
log('store/icon-512.png (512x512, 불투명)')
}
// 공통: 1024x500 배경 — 피그가 놓일 우측(크림)에서 좌측(옐로 살짝)으로.
// 중심을 피그 쪽 크림으로 두어 마스터의 크림 배경 사각이 자연스럽게 녹도록 한다.
function bgSvg(cx = '80%') {
return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
<defs>
<radialGradient id="g" cx="${cx}" cy="48%" r="95%">
<stop offset="0%" stop-color="${CREAM}"/>
<stop offset="62%" stop-color="${CREAM}"/>
<stop offset="100%" stop-color="${YELLOW}" stop-opacity="0.55"/>
</radialGradient>
</defs>
<rect width="1024" height="500" fill="url(#g)"/>
</svg>`)
}
// 2) 피처 그래픽 (텍스트 없음) — 가운데 큼직하게, 박스 없이 녹아들게
async function featurePlain() {
const bg = await sharp(bgSvg('50%')).png().toBuffer()
const pig = await sharp(SRC).resize(420, 420, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
const out = join(OUT, 'feature-1024x500.png')
await sharp(bg).composite([{ input: pig, top: 40, left: 302 }]).png().toFile(out)
log('store/feature-1024x500.png (텍스트 없음)')
}
// 3) 피처 그래픽 (한글 텍스트) — 좌측 텍스트 / 우측 피그, 겹침 없도록 간격 조정.
async function featureWithText() {
const bg = await sharp(bgSvg('82%')).png().toBuffer()
const pig = await sharp(SRC).resize(380, 380, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
const text = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
<style>
.t { font-family: 'Malgun Gothic','맑은 고딕',sans-serif; fill:${BROWN}; }
.title { font-size: 80px; font-weight: 800; }
.sub { font-size: 29px; font-weight: 500; fill:#8a6d10; }
</style>
<text x="64" y="232" class="t title">돈돼지 가계부</text>
<text x="68" y="290" class="t sub">슬림하게 관리하는</text>
<text x="68" y="332" class="t sub">가계부 · 자산 · 예산</text>
</svg>`)
const out = join(OUT, 'feature-1024x500-text.png')
await sharp(bg)
.composite([{ input: pig, top: 60, left: 630 }, { input: text }])
.png()
.toFile(out)
log('store/feature-1024x500-text.png (한글 텍스트, 폰트 렌더 확인 필요)')
}
console.log(`\n[스토어 에셋 생성] 입력: ${SRC}\n`)
await icon512()
await featurePlain()
await featureWithText()
console.log(`\n완료 → ${OUT}\n`)
Binary file not shown.

After

Width:  |  Height:  |  Size: 908 KiB

+127
View File
@@ -0,0 +1,127 @@
# Play Console 등록 자료 — 돈돼지 가계부
> 첫 출시(무료 앱, 인앱결제·광고 모두 비활성화) 기준. Play Console 각 항목에 그대로 붙여넣기.
> 광고(카카오 애드핏)를 껐으므로 광고 식별자 수집/제3자 공유가 없어 데이터 안전이 단순함.
---
## 1. 스토어 등록정보 (Main store listing)
### 앱 이름 (30자 이내)
```
돈돼지 가계부
```
### 간단한 설명 / Short description (80자 이내)
```
가계부·자산·예산을 슬림하게. 고정지출 자동, 통계 차트, 영수증 인식까지.
```
### 자세한 설명 / Full description (4000자 이내)
```
🐷 돈돼지 가계부 — 슬림하게 관리하는 가계부 · 자산 · 예산
복잡한 가계부는 그만! 수입·지출을 빠르게 기록하고, 자산과 예산을 한눈에 관리하세요.
군더더기 없는 깔끔한 화면으로 매일 쓰기 부담 없는 가계부입니다.
▪ 간편한 가계부
· 수입·지출·이체를 몇 번의 터치로 기록
· 자주 쓰는 내역을 칩으로 등록해 1초 입력
· 월별 수입/지출/잔액 요약과 일별 내역 정리
▪ 자산 · 부채 관리
· 은행·현금·신용/체크카드·대출·투자 계좌를 한 곳에서
· 총자산 · 총부채 · 순자산을 자동 집계
▪ 고정 지출 자동 반영 (프리미엄)
· 월세·구독료·급여 등 반복 거래를 등록해두면 주기에 맞춰 자동 생성
· 실제 결제만 '확인'으로 확정 — 빠뜨림 없는 가계부
▪ 통계와 예산 (프리미엄)
· 분류별 지출, 수입 대비 지출, 지난달 대비, 월말 예상까지 차트로 분석
· 카테고리별 예산을 정하고 예산 대비 지출을 한눈에
▪ 영수증 인식 (프리미엄)
· 영수증을 촬영하면 금액·내역을 자동 인식해 입력 시간을 단축
▪ 커뮤니티
· 짠테크·재테크 노하우를 나누는 게시판
· 추천·댓글로 소통하고 좋은 글을 발견
▪ 그 외
· 다크 모드 지원
· 엑셀 백업/복구로 내 데이터를 안전하게 (프리미엄)
· 로그인 없이 '둘러보기'로 먼저 체험
무료로 핵심 가계부 기능을 모두 사용할 수 있으며, 통계·예산·고정지출 자동화·영수증 인식·백업 등은 프리미엄에서 제공됩니다. (구독 기능은 순차적으로 제공될 예정입니다.)
오늘부터 돈돼지와 함께 슬림한 가계 관리를 시작해보세요!
문의: sbsb401824@gmail.com
```
### 기타 등록정보 필드
- **앱 카테고리**: 금융 (Finance)
- **태그**: 가계부, 예산, 자산관리
- **연락처 이메일**: sbsb401824@gmail.com
- **개인정보처리방침 URL**: https://www.sblog.kr/privacy
- **그래픽 에셋**:
- 앱 아이콘: brand/store/icon-512.png
- 그래픽 이미지(피처): brand/store/feature-1024x500-text.png (또는 -text 없는 버전)
- 휴대전화 스크린샷: brand/screenshots/shot1~6 (6장)
---
## 2. 데이터 안전 (Data safety) 양식
> "앱 콘텐츠 → 데이터 보안" 에서 작성. 아래 기준으로 답하면 개인정보처리방침과 일치함.
### 개요 질문
- 데이터를 수집하나요? → **예**
- 데이터를 제3자와 공유하나요? → **아니요** (광고 끔. OCR은 처리 위탁이며 별도 공유 아님)
- 전송 중 데이터가 암호화되나요? → **예** (HTTPS)
- 사용자가 데이터 삭제를 요청할 수 있나요? → **예** (앱 내 '회원 탈퇴' + 이메일 요청, https://www.sblog.kr/account-deletion)
### 수집하는 데이터 유형 (각 항목: 수집됨 / 목적 / 공유 여부)
| 데이터 유형 | 수집 | 공유 | 목적 |
|---|---|---|---|
| **개인 정보 → 이름** | 예 | 아니요 | 앱 기능, 계정 관리 |
| **개인 정보 → 이메일 주소** | 예 | 아니요 | 앱 기능, 계정 관리 |
| **금융 정보 → 기타 금융 정보** (사용자가 입력한 수입·지출·자산 등 가계부 데이터) | 예 | 아니요 | 앱 기능 |
| **사진 및 동영상 → 사진** (프로필·영수증·게시판 이미지) | 예 | 아니요 | 앱 기능 |
| **앱 활동 → 사용자가 생성한 콘텐츠** (게시판 글·댓글) | 예 | 아니요 | 앱 기능 |
| **앱 활동 → 앱 상호작용** | 예 | 아니요 | 앱 기능, 부정 이용 방지 |
각 항목 공통:
- **수집이 필수인가요?** → "데이터 수집이 필수임" (서비스 제공에 필요)
- **데이터가 암호화되나요(전송 중)?** → 예
- **삭제 요청 가능?** → 예
> ✅ 광고를 껐으므로 **광고 식별자(기기 ID) 수집/공유가 없습니다** — 데이터 안전 양식이 단순해지고 심사 불일치 리스크가 사라졌습니다.
---
## 3. 콘텐츠 등급 설문 (IARC)
> "앱 콘텐츠 → 콘텐츠 등급" 설문. 핵심 답안:
- 앱 카테고리: **유틸리티/생산성/기타** (또는 금융)
- 폭력/성적/혐오/약물/도박 콘텐츠: **모두 없음**
- **사용자 간 상호작용/사용자 생성 콘텐츠(게시판)**: **있음** → "사용자가 콘텐츠를 만들고 공유할 수 있음" 으로 정직하게 표시
- 신고 기능(5건 누적 블라인드)·관리자 차단 등 **중재 수단 보유** → 관련 질문에 "콘텐츠 신고/차단 기능 있음"으로 답변
- 위치 공유 / 개인정보 공유 기능: **없음**
- 디지털 구매(인앱결제): 첫 출시는 **비활성화** → "없음" (추후 활성화 시 등급 갱신)
- **광고 포함 여부**: **아니요** (광고 껐음 — 추후 활성화 시 갱신)
- 예상 등급: 대체로 **만 3세 이상(전체이용가)** 수준, UGC로 인해 일부 지역에서 Teen 가능 — 설문 결과 따름
---
## 4. 기타 출시 전 체크
- **앱 액세스 권한**: 로그인 필요 → 심사용 테스트 계정 제공 (Play Console "앱 액세스 권한"에 kenneth73510 계정 또는 데모 안내). 데모(둘러보기)로도 핵심 화면 확인 가능함을 메모.
- **타겟 출시 국가**: 대한민국 (필요 시 확대)
- **데이터 삭제 URL**: https://www.sblog.kr/account-deletion (데이터 안전 양식 + 앱 콘텐츠)
- **앱 카테고리/연락처/방침 URL** 위 1번 참고
- 광고 포함 여부 질문: **아니요(광고 없음)** — 광고를 껐으므로 "광고 없음"으로 표시
- ⚠️ **개인정보처리방침 정합성**: 현재 방침 4항이 "무료 회원에게 광고가 표시되며…"로 적혀 있어 광고 OFF 와 불일치. 심사자 교차확인 대비해 방침의 광고 문구를 "향후 광고가 도입될 수 있으며…"(미래형)로 완화 권장. (원하면 수정·배포 도와드림)
```
+57
View File
@@ -0,0 +1,57 @@
# 프로덕션 액세스 신청서 초안 — 돈돼지 가계부
> 14일 폐쇄 테스트 완료 후 Play Console "프로덕션 액세스 신청" 폼에 입력.
> ⚠️ [대괄호] 부분은 실제 테스트 후 본인 경험으로 교체하세요. 거짓·과장 금지 — 실제로 테스트한 내용을 적어야 합니다.
---
## Q1. 테스터를 어떻게 모집했나요? (How did you recruit your testers?)
```
실제 Android 기기와 Google 계정을 보유한 [12]명의 테스터를 모집해 비공개 테스트를 진행했습니다.
테스터는 안드로이드 사용자 지인 및 개발자 커뮤니티의 베타 테스터 모임을 통해 모았으며,
모두 실제 기기에 앱을 설치하고 14일간 사용했습니다.
```
> 유료/품앗이 서비스를 쓴 경우에도 "실제 기기·실제 계정 테스터"라는 사실 위주로 적되, 허위로 쓰지 마세요.
## Q2. 테스터와 어떻게 소통하고 피드백을 수집했나요?
```
이메일과 메신저(오픈채팅)로 테스터에게 설치 안내와 점검 항목을 전달했습니다.
점검 항목: 회원가입/구글 로그인, 가계부 수입·지출 기록, 계좌·자산 관리, 예산·통계 화면,
고정지출 자동 반영, 커뮤니티 게시판(글·댓글·추천), 다크 모드.
테스터가 사용 중 발견한 버그·불편 사항을 회신받아 정리했습니다.
```
## Q3. 어떤 피드백을 받았고, 무엇을 개선했나요?
```
주요 피드백과 개선:
- 둘러보기(로그인 없이 체험) 모드에서 일부 화면의 메뉴 접근이 불편 → 메뉴 진입 동선 수정
- 특정 기기에서 한글 입력 시 자동완성/맞춤법 검사로 입력이 끊김 → 입력 필드 설정 수정으로 해결
- 무료/유료 기능 구분 및 화면 흐름이 헷갈린다는 의견 → 안내 문구·접근 동선 정리
- [본인 테스트 중 실제로 발견한 버그/불편과 그에 대한 수정 내용을 추가 기재]
크래시나 데이터 유실 등 치명적 문제는 발견되지 않았습니다.
```
## Q4. 앱이 프로덕션에 준비되었다고 보는 이유는?
```
돈돼지 가계부는 웹·Windows 데스크톱에서 이미 운영 중인 개인 가계부 서비스이며,
이번 비공개 테스트로 Android 실기기에서 핵심 기능(가계부 기록, 계좌·자산, 예산·통계,
고정지출 자동 반영, 커뮤니티, 구글 로그인)의 안정성을 14일간 검증했습니다.
다양한 기기에서 크래시 없이 정상 동작하며, 테스트 중 제기된 개선 사항을 반영했습니다.
개인정보처리방침·데이터 삭제 안내 등 정책 요건도 갖추어 정식 출시 준비가 되었다고 판단합니다.
```
---
## (선택) 영어 버전 — 폼이 영어로 표시될 경우
**Q1 Recruit:** I recruited [12] testers who own real Android devices with genuine Google accounts, through acquaintances and a developer beta-tester community. All installed the app and used it over the 14-day period.
**Q2 Feedback collection:** I communicated via email and messenger, sharing install instructions and a checklist (Google sign-in, expense/income tracking, accounts & assets, budget & statistics, recurring transactions, community board, dark mode), and collected bug reports and usability feedback.
**Q3 Findings & changes:** Key fixes included improving menu navigation in the guest "explore" mode, resolving a Korean-input (IME) issue on certain devices, and clarifying free/premium feature flow. [Add the real bugs you found.] No crashes or data-loss issues were found.
**Q4 Readiness:** Don Dwaeji budget app already runs in production on web and Windows desktop. The closed test validated the Android build's core features on real devices over 14 days with no crashes, and feedback was addressed. Privacy policy and data-deletion pages are in place, so the app is ready for production.
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

+9
View File
@@ -0,0 +1,9 @@
; 기본 설치 경로 지정 (사용자 폴더 = 관리자 권한 불필요 → UAC 프롬프트 없음).
; productName 이 한글이라 electron-builder 가 기본 경로를 package.json name(sb_pt)으로 폴백하는 문제도 함께 회피.
; → %LOCALAPPDATA%\Programs\DonDwaeji 로 고정 (사용자는 설치 시 변경 가능).
!macro preInit
SetRegView 64
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LOCALAPPDATA\Programs\DonDwaeji"
SetRegView 32
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LOCALAPPDATA\Programs\DonDwaeji"
!macroend
+11
View File
@@ -0,0 +1,11 @@
{
"appId": "kr.sblog.slimbudget",
"appName": "돈돼지 가계부",
"webDir": "dist",
"server": {
"cleartext": true
},
"android": {
"allowMixedContent": true
}
}
+39
View File
@@ -0,0 +1,39 @@
# 프론트엔드 배포 (Nginx 정적 호스팅)
`main` 푸시 시 [.gitea/workflows/deploy.yaml](../.gitea/workflows/deploy.yaml) 가
`dist` 를 빌드해 서버 Nginx 웹 루트로 업로드하고 nginx 를 reload 합니다.
## 1. Gitea 저장소 시크릿 (Settings → Actions → Secrets)
| 시크릿 | 설명 | 예시 |
|---|---|---|
| `DEPLOY_HOST` | 서버 호스트/IP | `10.0.0.5` |
| `DEPLOY_USER` | SSH 계정 | `deploy` |
| `DEPLOY_SSH_KEY` | SSH 개인키(PEM 전체) | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
| `DEPLOY_PATH` | Nginx 웹 루트 | `/var/www/sb-front` |
| `DEPLOY_PORT` | SSH 포트(선택) | `22` |
## 2. 서버 1회 준비
```bash
sudo apt-get install -y nginx
sudo mkdir -p /var/www/sb-front && sudo chown deploy:deploy /var/www/sb-front
# Nginx 서버블록 설치 (deploy/nginx-sb-front.conf 참고)
sudo cp deploy/nginx-sb-front.conf /etc/nginx/sites-available/sb-front
sudo ln -sf /etc/nginx/sites-available/sb-front /etc/nginx/sites-enabled/sb-front
sudo nginx -t && sudo systemctl reload nginx
# deploy 계정에 nginx 검증/reload 무비밀번호 sudo 허용
echo 'deploy ALL=(root) NOPASSWD: /usr/sbin/nginx -t, /usr/sbin/nginx -s reload' \
| sudo tee /etc/sudoers.d/sb-front
# 러너 공개키를 deploy 계정 authorized_keys 에 등록 (백엔드와 동일 키 사용 가능)
```
## 3. 동작
- main 푸시 → CI(빌드) + Deploy(빌드·업로드·reload) 가 각각 실행
- 업로드는 웹 루트를 비우고 새 `dist` 로 교체 (해시 자산명이라 안전)
- 수동 배포: 저장소 Actions → Deploy → Run workflow
## 참고
- API 는 같은 도메인의 `/api/` 로 Nginx 가 백엔드(8080)에 프록시합니다.
별도 도메인/포트로 띄운다면 빌드시 `VITE_API_BASE_URL` 환경변수를 지정하세요.
+47
View File
@@ -0,0 +1,47 @@
# /etc/nginx/sites-available/sb-front (심볼릭 링크로 sites-enabled 에 연결)
server {
listen 80;
server_name _; # 도메인으로 교체
root /var/www/sb-front; # DEPLOY_PATH 와 동일
index index.html;
# SPA: 새로고침/직접 진입 시 index.html 로 폴백 (vue-router history 모드)
# 진입 HTML(루트·폴백)은 캐시 금지 — 배포로 청크 해시가 바뀌어도 항상 최신 진입점을 받도록.
# (루트 '/' 는 아래 '= /index.html' 매칭을 안 타므로 여기에 헤더를 둬야 함)
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
}
# index.html 은 캐시 금지 — 배포로 청크 해시가 바뀌어도 항상 최신 진입점을 받도록.
# (캐시되면 옛 index.html 이 삭제된 옛 청크를 불러 메뉴가 안 열리는 문제 발생)
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
# 해시 자산 장기 캐시 (파일명에 해시가 있어 immutable 안전)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 설치파일 다운로드 — 배포(dist 교체)에 지워지지 않도록 root 밖의 영속 디렉터리에서 서빙.
# 서버에 /var/www/sb-downloads/ 를 만들고 설치파일(SlimBudget-Setup.exe)을 올려두세요.
# 예) sudo mkdir -p /var/www/sb-downloads && sudo cp SlimBudget-Setup.exe /var/www/sb-downloads/
location /download/ {
alias /var/www/sb-downloads/;
add_header Content-Disposition "attachment";
autoindex off;
}
# API 프록시 → 백엔드(8080)
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+49
View File
@@ -0,0 +1,49 @@
# 안드로이드 하이브리드 앱 (Capacitor)
Vue `dist` 를 네이티브 WebView로 패키징해 Android 앱으로 빌드합니다.
## 구성
- `capacitor.config.json` — appId `kr.sblog.slimbudget`, appName `Slim Budget`, webDir `dist`
- `android/` — 네이티브 프로젝트(커밋됨, 빌드 산출물은 .gitignore)
- `.env.capacitor`**앱 전용 환경변수** (`vite build --mode capacitor` 가 로드)
- `src/capacitor.js` — 하드웨어 뒤로가기 → 라우터 back, safe-area 클래스(`is-native`)
- npm 스크립트: `build:app`, `cap:sync`, `cap:android`
## ⚠️ 빌드 전 반드시 설정
**`.env.capacitor` 의 백엔드 절대 URL 을 운영 도메인으로 교체** (앱 안에선 `/api` 상대경로가 안 됨):
```
VITE_API_BASE_URL=https://yourdomain.com/api
```
백엔드는 이미 CORS에 Capacitor 오리진(`https://localhost`, `capacitor://localhost`, `http://localhost`)을 허용합니다. **백엔드는 HTTPS** 여야 합니다(안드로이드 평문 차단).
## 사전 준비 (PC)
- **Android Studio** + Android SDK + **JDK 17**
- 에뮬레이터 또는 실기기(USB 디버깅 on)
## 빌드 & 실행
```bash
# 1) 웹 자산 빌드 + 네이티브로 동기화
npm run cap:sync # = vite build --mode capacitor && cap sync
# 2) Android Studio 열기 → Run ▶ 으로 에뮬레이터/기기 실행
npm run cap:android # = cap open android
```
코드 변경 시마다 `npm run cap:sync` 후 Android Studio에서 다시 실행.
## (선택) 아이콘 · 스플래시
```bash
npm i -D @capacitor/assets
# 1024x1024 아이콘을 assets/icon.png 등으로 준비 후
npx capacitor-assets generate --android
```
## 배포 (Play Store)
1. Android Studio → Build → **Generate Signed Bundle/APK****Android App Bundle(.aab)**
2. **keystore 생성·보관**(분실 시 업데이트 불가) → 서명
3. **Google Play Console**(계정 1회 $25) → 앱 생성 → `.aab` 업로드 → 심사
## 자주 막히는 부분
- **네트워크 안 됨/빈 화면**: `.env.capacitor``VITE_API_BASE_URL` 이 실제 HTTPS 백엔드인지, 재빌드(`cap:sync`) 했는지
- **CORS 오류**: 백엔드가 앱 오리진 허용하는지(`CorsConfig`), HTTPS 인지
- **평문 차단**: 백엔드가 http면 안드로이드가 차단 → HTTPS 사용
- **로그인 유지**: 토큰은 WebView `localStorage` 에 저장되어 유지됨(정상)
+51
View File
@@ -0,0 +1,51 @@
# 멤버십(무료/유료) 메뉴·기능 구분 — 설계 (추후 구현)
> 상태: **설계만** (미구현). 결제·게이팅은 추후 작업.
> 방향: **핵심 기록은 무료로 개방**(진입장벽 ↓), **과금 기능·자동화·고급 분석·무제한·백업은 유료**.
> 계좌 개수 제한은 **두지 않음**(1~소수 제한은 핵심 기능을 막아 거부감 → 제외).
---
## 1. 무료 / 유료 구분
| 메뉴 / 기능 | 무료 | 유료(프리미엄) | 비고 |
|---|:--:|:--:|---|
| **가계부 내역**(수입·지출 기록) | ✅ | ✅ | 핵심 — 무료 개방 |
| **계좌 관리**(은행·현금·카드·대출) | ✅ **무제한** | ✅ 무제한 | 개수 제한 없음 |
| 자주 쓰는 내역(빠른 등록) | ✅ | ✅ | 편의, 무료 |
| 월별 요약(수입/지출/잔액) | ✅ | ✅ | 기본 요약은 무료 |
| 분류 관리 | ✅ 기본+소수 | ✅ 무제한·소분류(2단계) | 무료는 기본 분류 위주 |
| 태그 관리 | — | ✅ | |
| **통계**(순자산 추이·예상 지출·지난달 대비·분류 분석) | — | ✅ | 고급 분석은 유료 |
| 예산 설정 | — | ✅ | |
| 고정 지출(자동 생성·확인) | — | ✅ | 자동화 = 유료 |
| **영수증 OCR** | — | ✅ | **Google Vision 과금** → 유료 필수 |
| **카드 결제 알림 자동인식** | — | ✅ | 강력한 유료 훅(편의·자동화) |
| 투자/자산(평가액) 관리 | — | ✅ | |
| 데이터 백업/복구(엑셀·JSON) | — | ✅ | 추후 백업 기능과 연계 |
| 게시판(커뮤니티/짠테크/재테크 팁) | ✅ | ✅ | 커뮤니티는 전체 개방 |
| 다크모드·테마 | ✅ | ✅ | 기본 제공 |
| 광고 | (도입 시) 표시 | 제거 | 광고 모델은 미정 |
## 2. 구분 기준
- **무료** = 기본 기록 + (무제한)계좌 + 기본 분류 + 월 요약 → "충분히 써볼 만한" 수준
- **유료** = ① 과금 기능(OCR) · ② 자동화(카드 알림 인식 · 고정지출) · ③ 고급 분석(통계 전체 · 예산) · ④ 무제한(태그/소분류) · ⑤ 백업/복구
- 원칙: **무료를 일부러 불편하게 만들지 않고, 유료에 "확실히 편한 것"을 몰아준다**(전환율 우선).
## 3. 구현 시 필요한 것 (추후)
- **데이터**: `member.plan`(`FREE`/`PREMIUM`) + `plan_expires_at`(만료일) 컬럼.
- **게이팅(이중)**:
- 프론트: 유료 메뉴에 **잠금 배지 + 업그레이드 유도**(데모의 잠금 화면 재사용 가능), 비유료는 진입 차단.
- 백엔드: 해당 API에 **plan 권한 체크**(프론트 우회 방지). OCR·알림 인식·통계·예산·고정지출·투자·백업 엔드포인트.
- **결제 연동**: 안드로이드 인앱결제(Google Play Billing) / 웹·PC는 PG. 정기결제 시 만료일 갱신.
- **기존 데이터 보호**: 유료→무료 강등(만료) 시 데이터는 보존하되 **유료 메뉴만 잠금**(읽기 정도는 허용 검토).
## 4. 추후 결정 필요(미정)
- 가격·주기(월/연), 무료체험 기간.
- 광고 모델 도입 여부(무료=광고 / 유료=제거).
- 무료 "기본 분류" 범위(개수·기본 템플릿만 허용 여부).
- 강등 시 잠금 정책(완전 차단 vs 읽기 허용).
---
*참고: 데모(`/demo`)의 잠금 안내 화면을 유료 게이팅 UI 의 기반으로 재사용할 수 있음.*

Some files were not shown because too many files have changed in this diff Show More