diff --git a/app/README.md b/app/README.md index 77bbc08..d382db4 100644 --- a/app/README.md +++ b/app/README.md @@ -23,7 +23,7 @@ cd app flutter pub get flutter run # 실기기·시뮬레이터 flutter analyze # 정적 분석 -flutter test # 스모크 테스트 3종 +flutter test # 스모크·회귀 테스트 9건 flutter build apk --debug # Android 디버그 APK ``` @@ -117,7 +117,7 @@ bool get isMockSessionAvailable => !FirebaseBoot.isLive && !_releaseMode; | Q3 | 네이버 client ID / secret | 미정 | 네이버 버튼이 「키 미투입」 안내를 반환 | | Q5 | 스토어 등록 명의 | — | 3주차 TestFlight·Play 내부트랙 제출 불가 | | Q14 | 앱 식별자 확정 | `android/app/build.gradle.kts` · `ios/Runner.xcodeproj` | 현재 `kr.co.sweech.fancake` 로 잠정. **스토어 등록 후에는 변경 불가** | -| Q6 | 확정 로고 원본(벡터·알파 PNG) | `assets/brand/` | 로그인 화면 워드마크를 텍스트로 대체 중 | +| ~~Q6~~ | 공식 FANUZU 심볼 원본 | `assets/brand/` | **2026-08-13 반영 완료.** 제공본이 심볼 전용이라 앱 아이콘은 공식 자산으로 생성했고 로그인 워드마크는 `FANUZU` 텍스트를 유지 | | Q15 | 하단 탭 아이콘 자산 | `assets/icons/` | Material 기본 아이콘 5종으로 잠정 | ### 5-3. 폰트 @@ -206,12 +206,12 @@ app/ ## 9. 검증 결과 -2026-08-08 실행. macOS 27.0 · Flutter 3.41.7 (stable) · Dart 3.11.5 · Android SDK 36 · Xcode 26.6 +2026-08-14 재실행. macOS 27.0 · Flutter 3.41.7 (stable) · Dart 3.11.5 · Android SDK 36 · Xcode 26.6 ``` $ flutter analyze Analyzing app... -No issues found! (ran in 1.3s) +No issues found! (ran in 3.2s) $ flutter test 00:00 +0: loading /Users/Kinos/Projects/p-fancake/app/test/app_smoke_test.dart @@ -221,13 +221,14 @@ $ flutter test 00:00 +3: 릴리스 빌드에서는 목 우회 입구가 열리지 않는다 릴리스에서 signInWithMockSession 을 불러도 로그인되지 않는다 00:00 +4: 릴리스 빌드에서는 목 우회 입구가 열리지 않는다 릴리스 빌드 로그인 화면에 목 세션 버튼이 없다 00:00 +5: 릴리스 빌드에서는 목 우회 입구가 열리지 않는다 디버그 빌드에서는 목 세션 버튼이 있다 (가드가 과하게 잠그지 않았는지) -00:00 +6: 화면에 띄우는 사유에 내부 파일 경로가 들어가지 않는다 -00:00 +7: 3. 탭을 전환하면 각 탭 루트 화면이 바뀐다 -00:00 +8: All tests passed! +00:00 +6: 제공자 4종의 미투입 안내에 내부 파일 경로가 들어가지 않는다 +00:00 +7: 화면에 띄우는 사유에 내부 파일 경로가 들어가지 않는다 +00:00 +8: 3. 탭을 전환하면 각 탭 루트 화면이 바뀐다 +00:00 +9: All tests passed! $ flutter build apk --debug Running Gradle task 'assembleDebug'... 219.5s (첫 빌드 — NDK 28.2 자동 설치 포함) -✓ Built build/app/outputs/flutter-apk/app-debug.apk (142.4 MB · debug) +✓ Built build/app/outputs/flutter-apk/app-debug.apk (164,432,730 bytes · 약 156.8 MiB · debug) 10.6s (증분 재빌드) $ flutter build ios --debug --no-codesign @@ -241,7 +242,7 @@ $ python3 tool/gen_tokens.py 전 쌍 AA 통과. (최저 color-on-neon-violet / color-neon-violet = 5.09:1) ``` -**테스트 8종은 Firebase 설정 파일이 없는 상태(목 모드)에서 통과한 결과다.** +**테스트 9종은 Firebase 설정 파일이 없는 상태(목 모드)에서 통과한 결과다.** 음성 검증 2회 — 테스트가 실제로 게이트 역할을 하는지 직접 깨뜨려 확인했다. @@ -292,11 +293,10 @@ I flutter : [FirebaseBoot] detail :: PlatformException(java.lang.Exception: Fail | L2 | `com.google.gms.google-services` Gradle 플러그인 **미적용** | 지금은 이게 정상이다. 설정 파일 없이 플러그인만 적용하면 Gradle 빌드가 실패한다. 파일 투입과 **동시에** `android/settings.gradle.kts` · `android/app/build.gradle.kts` 에 추가해야 한다 | S2 | | L3 | 카카오·네이버 SDK 미도입 | 스텁만 있다. 실 연동 시 `kakao_flutter_sdk_user` · 네이버 SDK 추가와 함께 `KeylessSocialProvider` 를 실 구현체로 교체 | Q3 회신 후 | | L4 | 폰트 파일 미투입 | 시스템 폰트로 폴백. 크기·굵기·행간·자간은 토큰 값대로 적용됨 | 라이선스 원본 확보 시 | -| L5 | 로고 원본 미확정(Q6) | 로그인 화면 워드마크가 텍스트다 | 2주차 M1 | | L6 | 탭 아이콘이 Material 기본(Q15) | 시안 탭바에는 아이콘이 없어 대조 대상이 없다 | 3주차 | | L7 | **iOS 최소 지원 버전을 13.0 → 15.0 으로 올렸다** | `firebase_auth` 6.x 가 15.0 이상을 요구해 13.0 으로는 `pod install` 이 실패했다. 기준 단말 iPhone 11(S4)은 iOS 18까지 지원하므로 범위 영향 없음. `ios/Podfile` · `Runner.xcodeproj` 3곳 수정 | 반영 완료 | | L8 | **릴리스 서명 키 없음 → `flutter build apk --release` 는 의도적으로 실패한다** | 서명 키는 스토어 등록 명의(Q5·S3)가 정해져야 만들 수 있다. Flutter 기본 템플릿은 이 상태에서 **debug 키로 조용히 서명**해 Play 가 거부하는 산출물을 "성공"으로 내놓는다 — 그 경로를 막고 사유를 띄우도록 `android/app/build.gradle.kts` 를 고쳤다. 키가 생기면 `android/key.properties`(storeFile·storePassword·keyAlias·keyPassword)만 놓으면 된다. 해당 파일과 `*.jks`/`*.keystore` 는 `app/android/.gitignore` 가 이미 막고 있다(확인함) | Q5·S3 | -| L8-1 | debug APK 142.4 MB | debug 빌드 특성(미난독화·전 ABI 포함). release + `--split-per-abi` 로 크게 줄어들지만 **L8 때문에 아직 실측하지 못했다** | Q5·S3 | +| L8-1 | debug APK 164,432,730 bytes(약 156.8 MiB) | debug 빌드 특성(미난독화·전 ABI 포함). release + `--split-per-abi` 로 크게 줄어들지만 **L8 때문에 아직 실측하지 못했다** | Q5·S3 | | L9 | 첫 Gradle 빌드에서 Kotlin 데몬 경고 1회 | `e: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0` — 자동 재시도로 성공했고 산출물에 영향 없음. 증분 빌드에서는 재현되지 않음 | — | | L9-1 | iOS 런치 화면 색 변경은 **컴파일 검증만** 했다 | `LaunchScreen.storyboard` 배경을 흰색 → `color-cosmic-void`(#000)로 바꿨다. Android 는 에뮬레이터에서 눈으로 확인했지만 iOS 는 시뮬레이터 실행을 하지 않아 빌드 성공까지만 확인했다 | 3주차 실기기 빌드 | | L10 | 온보딩 6화면 라우트 없음 | S-A-INTRO · S-A-TERMS · S-A-PROFILE · S-A-ARTIST · S-A-PLANET-NEW · S-A-PERMS | 3주차 | diff --git a/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png index 81e5225..208c463 100644 Binary files a/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png and b/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png index a2c1349..fcbe1bb 100644 Binary files a/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png and b/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png index 0afad32..8b4032f 100644 Binary files a/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png and b/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png index 45f9d61..e087f15 100644 Binary files a/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png and b/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png index 347cc15..2335df2 100644 Binary files a/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png and b/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index b2ea007..55e5086 100644 Binary files a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 4de6792..c36c95e 100644 Binary files a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index ed6a369..b251b73 100644 Binary files a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 1647a50..baddfe7 100644 Binary files a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index dfcd425..908fea6 100644 Binary files a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/assets/brand/app-icon-foreground.png b/app/assets/brand/app-icon-foreground.png index 6a1a95e..0103502 100644 Binary files a/app/assets/brand/app-icon-foreground.png and b/app/assets/brand/app-icon-foreground.png differ diff --git a/app/assets/brand/app-icon.png b/app/assets/brand/app-icon.png index 277b549..0103502 100644 Binary files a/app/assets/brand/app-icon.png and b/app/assets/brand/app-icon.png differ diff --git a/app/firebase/firestore.indexes.json b/app/firebase/firestore.indexes.json index e19ac50..8e2456c 100644 --- a/app/firebase/firestore.indexes.json +++ b/app/firebase/firestore.indexes.json @@ -74,23 +74,6 @@ { "fieldPath": "createdAt", "order": "DESCENDING" } ] }, - { - "collectionGroup": "popLots", - "queryScope": "COLLECTION", - "fields": [ - { "fieldPath": "popType", "order": "ASCENDING" }, - { "fieldPath": "status", "order": "ASCENDING" }, - { "fieldPath": "grantedAt", "order": "ASCENDING" } - ] - }, - { - "collectionGroup": "popLots", - "queryScope": "COLLECTION_GROUP", - "fields": [ - { "fieldPath": "status", "order": "ASCENDING" }, - { "fieldPath": "expiresAt", "order": "ASCENDING" } - ] - }, { "collectionGroup": "activityLogs", "queryScope": "COLLECTION", diff --git a/app/firebase/firestore.rules b/app/firebase/firestore.rules index 226c62d..3c67982 100644 --- a/app/firebase/firestore.rules +++ b/app/firebase/firestore.rules @@ -8,7 +8,7 @@ rules_version = '2'; // - 주간보고/1주차/src/1주차_정책설계서_초안.md P-02 원장 무결성 · P-06 어뷰징 방어 // // 3대 원칙 -// 1) POP · FPI · 등급 · 원장 필드는 클라이언트가 쓸 수 없다. +// 1) POP · FPI 집계 · 원장 필드는 클라이언트가 쓸 수 없다. // 전부 Cloud Functions(Admin SDK)가 쓰며, Admin SDK는 본 규칙을 우회한다. // → 별첨1 ② 판정 기준 「클라이언트 단독 지급 0건」을 규칙 층에서 물리적으로 보장. // 2) 개인 리소스는 본인만 읽고, 쓰기는 화이트리스트 필드로만 허용한다. @@ -58,13 +58,13 @@ service cloud.firestore { } // 무인증 열람을 허용하는 캠페인 상태 — draft·review 는 제외 (교차검증 F-08) function campaignIsPublic() { - return resource.data.status in ['voting', 'funding', 'success', 'fail', 'report']; + return resource.data.status in ['funding', 'success', 'fail', 'report']; } // ───────────────────────── 1. 운영 파라미터 ───────────────────────── // 미확정 수치 원격 설정. 하드코딩 금지 대상 전부가 여기 들어온다. - // 근거: 결정 D-07·D-13·D-14·D-21·D-22·D-23 · FPI 가중치(P-03) · 이클립스 수치(CD-91) + // 근거: 결정 D-07·D-13·D-14 · FPI 가중치(P-03) · 이클립스 수치(CD-91) // 화면: S-AD-POP · S-AD-FPI · S-AD-QUEST · S-AD-ECLIPSE match /config/{configId} { allow read: if true; // 스플래시·로그인 전 화면도 읽어야 해 공개 읽기 @@ -92,14 +92,14 @@ service cloud.firestore { // ───────────────────────── 2. 사용자 ───────────────────────── - // 계정·프로필. 등급(grade)·누적 기여(totalContributedPop)는 서버 산출값이라 쓰기 목록에서 뺀다. + // 계정·프로필. 누적 기여(totalContributedPop)는 서버 산출값이라 쓰기 목록에서 뺀다. // 화면: S-A-PROFILE · S-M-HOME · S-M-SETTINGS · S-AD-USER match /users/{uid} { allow get: if isSelf(uid) || isViewer(); allow list: if isViewer(); // 전체 사용자 목록은 운영자만(S-AD-USER) // 교차검증 F-06 — 클라이언트 생성 경로를 닫는다. // 기존에는 클라이언트 생성도 허용해 ① fn-auth-oncreate(§8.5)와 생성 경합이 생기고 - // ② 클라이언트가 먼저 만들면 grade·totalContributedPop 이 허용 목록에 없어 필드가 빈 + // ② 클라이언트가 먼저 만들면 totalContributedPop 등 서버 산출 필드가 빈 // 사용자 문서가 남았다(별첨1 조건부적합 제외 ④ 로그인 불가와 닿는 지점). // 생성 주체를 Auth 트리거 하나로 고정한다. allow create: if false; @@ -119,14 +119,14 @@ service cloud.firestore { allow write: if false; // 별첨1 ② 「클라이언트 단독 지급 0건」 } - // POP Lot — 획득 단위별 잔량·만료일. FIFO 차감의 계산 단위. - // 화면: S-M-WALLET(소멸 예정 POP) + // POP Lot — 획득 단위별 잔량. 원장 대사와 차감의 계산 단위. + // 화면: S-M-WALLET match /users/{uid}/popLots/{lotId} { allow read: if isSelf(uid) || isViewer(); - allow write: if false; // 잔량·만료 조작 차단 + allow write: if false; // 잔량 조작 차단 } - // POP 원장. 지급·차감·환불·소멸 전건. 수정·삭제 불가(append-only)는 Functions에서도 지킨다. + // POP 원장. 지급·차감·환불·보류 무효 전건. 수정·삭제 불가(append-only)는 Functions에서도 지킨다. // 화면: S-M-WALLET(거래 내역) · S-AD-LEDGER match /users/{uid}/popTransactions/{txId} { allow read: if isSelf(uid) || isViewer(); @@ -147,7 +147,7 @@ service cloud.firestore { allow write: if false; } - // 보유 배지·등급 이력. 부여는 서버 판정. + // 보유 배지 이력. 부여는 서버 판정. // 화면: S-M-BADGE(본인 도감) · S-M-IDCARD(본인 카드) // // 교차검증 G-04 — 예전 주석은 「타인 프로필의 배지 노출 허용」이라 적고 signedIn() 으로 열어 뒀는데, @@ -326,7 +326,7 @@ service cloud.firestore { && request.resource.data.keys().hasOnly([ 'fandomId','creatorUid','creatorName','title','summary','body', 'coverImageUrl','campaignType','targetPop','endsAt','status', - 'raisedPop','achievedRate','donorCount','votedPop','isAiDraft', + 'raisedPop','achievedRate','donorCount', 'refundState','createdAt','updatedAt' ]) && request.resource.data.creatorUid == request.auth.uid @@ -336,8 +336,6 @@ service cloud.firestore { && request.resource.data.raisedPop == 0 // 개설 시 모금액 0 고정 && request.resource.data.achievedRate == 0 // 달성률 위조 차단(웹 브릿지 노출 대상) && request.resource.data.donorCount == 0 // 후원자 수 위조 차단 - && request.resource.data.votedPop == 0 // D-22 승격 임계치 판정 입력 - && request.resource.data.isAiDraft == false // 「AI 초안」 배지 사칭 차단 && request.resource.data.refundState == 'none'; // 개설자는 심사 전 초안만 고칠 수 있다. allow update: if signedIn() @@ -618,7 +616,7 @@ service cloud.firestore { allow read: if signedIn(); } - // popLots(IDX-11)·환불 배치 donations(IDX-21)는 스케줄 Function 전용이다. + // 환불 배치 donations(IDX-21)는 스케줄 Function 전용이다. // Admin SDK는 규칙을 우회하므로 컬렉션 그룹 블록을 두지 않는다 — 열면 권한만 넓어진다. // ───────────────────────── 13. 기본 거부 ───────────────────────── diff --git a/app/firebase/test/support/environment.mjs b/app/firebase/test/support/environment.mjs index e16a94b..f81d8c8 100644 --- a/app/firebase/test/support/environment.mjs +++ b/app/firebase/test/support/environment.mjs @@ -49,11 +49,9 @@ export function userDocument(uid, overrides = {}) { uid, displayName: uid, fandomId: 'fandom-a', - grade: 'SILVER', totalContributedPop: 0, status: 'active', updatedAt: '2026-08-08T00:00:00Z', ...overrides, }; } - diff --git a/app/firebase/test/tests/firestore.rules.test.mjs b/app/firebase/test/tests/firestore.rules.test.mjs index 5f13d6b..dc4740a 100644 --- a/app/firebase/test/tests/firestore.rules.test.mjs +++ b/app/firebase/test/tests/firestore.rules.test.mjs @@ -43,7 +43,7 @@ afterAll(async () => { await testEnvironment.cleanup(); }); -describe('원칙 1 — POP·FPI·등급·원장은 클라이언트 쓰기 불가', () => { +describe('원칙 1 — POP·FPI·원장은 클라이언트 쓰기 불가', () => { test('본인 지갑 잔액 직접 증액을 거부한다', async () => { await seedDocuments(testEnvironment, { 'users/alice/wallet/main': { balance: 100 }, @@ -61,14 +61,14 @@ describe('원칙 1 — POP·FPI·등급·원장은 클라이언트 쓰기 불가 ); }); - test('POP Lot 만료일 연장을 거부한다', async () => { + test('POP Lot 잔량 조작을 거부한다', async () => { await seedDocuments(testEnvironment, { - 'users/alice/popLots/lot-1': { remaining: 100, expiresAt: '2026-08-09T00:00:00Z' }, + 'users/alice/popLots/lot-1': { remaining: 100 }, }); await assertFails( updateDoc(doc(dbFor('alice'), 'users/alice/popLots/lot-1'), { - expiresAt: '2099-12-31T00:00:00Z', + remaining: 999_999, }), ); }); @@ -82,12 +82,14 @@ describe('원칙 1 — POP·FPI·등급·원장은 클라이언트 쓰기 불가 ); }); - test('사용자 등급 직접 승격을 거부한다', async () => { + test('누적 기여 통계 직접 조작을 거부한다', async () => { await seedDocuments(testEnvironment, { 'users/alice': userDocument('alice'), }); - await assertFails(updateDoc(doc(dbFor('alice'), 'users/alice'), { grade: 'DIAMOND' })); + await assertFails( + updateDoc(doc(dbFor('alice'), 'users/alice'), { totalContributedPop: 999_999 }), + ); }); test('다른 사용자의 지갑 쓰기를 거부한다', async () => { @@ -125,11 +127,11 @@ describe('원칙 2 — 개인 리소스와 쓰기 필드 제한', () => { await assertFails(updateDoc(doc(dbFor('alice'), 'users/bob'), { displayName: 'Forged' })); }); - test('허용 필드와 등급을 섞은 본인 업데이트를 전부 거부한다', async () => { + test('허용 필드와 서버 통계를 섞은 본인 업데이트를 전부 거부한다', async () => { await assertFails( updateDoc(doc(dbFor('alice'), 'users/alice'), { displayName: 'New Alice', - grade: 'DIAMOND', + totalContributedPop: 999_999, }), ); }); @@ -251,15 +253,13 @@ describe('F-02 — 캠페인 집계 필드 위조 회귀', () => { summary: 'Summary', body: 'Body', coverImageUrl: 'https://example.test/cover.png', - campaignType: 'support', + campaignType: 'other', targetPop: 1000, endsAt: '2026-09-01T00:00:00Z', status: 'draft', raisedPop: 0, achievedRate: 0, donorCount: 0, - votedPop: 0, - isAiDraft: false, refundState: 'none', createdAt: '2026-08-08T00:00:00Z', updatedAt: '2026-08-08T00:00:00Z', @@ -275,14 +275,12 @@ describe('F-02 — 캠페인 집계 필드 위조 회귀', () => { await assertSucceeds(setDoc(doc(dbFor('alice'), 'campaigns/valid'), validCampaign)); }); - test('집계·투표·AI 초안 필드 위조를 거부한다', async () => { + test('집계 필드 위조를 거부한다', async () => { await assertFails( setDoc(doc(dbFor('alice'), 'campaigns/forged'), { ...validCampaign, achievedRate: 1, donorCount: 99_999, - votedPop: 999_999, - isAiDraft: true, }), ); }); @@ -389,7 +387,10 @@ describe('F-14 — posts 허용 필드 회귀', () => { describe('F-06 — users 클라이언트 생성 경로 회귀', () => { test('일반 사용자의 users 문서 생성을 거부한다', async () => { await assertFails( - setDoc(doc(dbFor('alice'), 'users/alice'), userDocument('alice', { grade: 'DIAMOND' })), + setDoc( + doc(dbFor('alice'), 'users/alice'), + userDocument('alice', { totalContributedPop: 999_999 }), + ), ); }); diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 48a69fd..1f1a6e1 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 197a92c..59afb2e 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 12bf02e..1ff2abf 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 018e998..039dd18 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index c7e2838..2e95c38 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 02dfbc3..338100f 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 4ae0668..df914cd 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 12bf02e..1ff2abf 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index f7a30f3..700db3c 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index eca8848..4398ee1 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index ffb7804..0bf383a 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index de11331..4c2dde1 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 796315e..34431ca 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index d84dd71..02e600a 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index eca8848..4398ee1 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 1e76791..b33dd84 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 4bbc6c1..ec72ee8 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 790405d..8c774e8 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 2ec8363..98a2d0d 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index e1a2778..86d064b 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index bb55394..057714c 100644 Binary files a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 1d4cbbb..dac69e0 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -33,14 +33,14 @@ dev_dependencies: # 재생성: dart run flutter_launcher_icons flutter_launcher_icons: ^0.14.4 -# 앱 아이콘 — 원본은 시안/assets/app-icon.png (제안 자산). -# 발주사 확정 로고(Q6)가 오면 assets/brand/ 의 두 파일만 교체하고 위 명령을 다시 돌린다. +# 앱 아이콘 — 2026-08-13 수령한 공식 `fanuzu logo only.png`의 중앙 1:1 영역을 +# 픽셀 변경 없이 사용한다. `assets/brand/` 두 원본은 같은 공식 마크다. flutter_launcher_icons: android: true ios: true image_path: "assets/brand/app-icon.png" # Android 8+ 적응형 아이콘 — 마스크가 바깥을 잘라내므로 마크만 담은 전경을 따로 쓴다. - adaptive_icon_background: "#120E25" # 원본 배경 실측값 + adaptive_icon_background: "#120E25" # 투명 공식 마크 바깥의 브랜드 다크 배경 adaptive_icon_foreground: "assets/brand/app-icon-foreground.png" # iOS 는 알파 채널을 허용하지 않는다(App Store 심사 거부 사유). remove_alpha_ios: true diff --git a/design/craft-allowlist.yaml b/design/craft-allowlist.yaml index f1ae1b8..f204679 100644 --- a/design/craft-allowlist.yaml +++ b/design/craft-allowlist.yaml @@ -8,10 +8,6 @@ CAPS_OK: - {term: STATION, reason: "견적서 v2.1 5탭 라벨 · 한글 대체 불가(계약 표기)"} - {term: INSIDE, reason: "견적서 v2.1 5탭 라벨 · 한글 대체 불가(계약 표기)"} - {term: GALAXY, reason: "견적서 v2.1 5탭 라벨 · 한글 대체 불가(계약 표기)"} - # ── 등급명 — 인수 데모가 확정한 제품 어휘(FANUZU_prototype.tsx:627 'DIAMOND TIER'). - - {term: SILVER, reason: "등급 체계 고유명 · 인수 데모 확정 어휘(결정 D-21)"} - - {term: GOLD, reason: "등급 체계 고유명 · 인수 데모 확정 어휘(결정 D-21)"} - - {term: DIAMOND, reason: "등급 체계 고유명 · 인수 데모 확정 어휘(결정 D-21)"} # ── 지표·재화 약어 — 화면 전반에서 단위처럼 쓰인다. - {term: FPI, reason: "Fandom Planet Index 약어 · PRD v9.0 PRD-03 공식 명칭"} diff --git a/design/design-tokens.md b/design/design-tokens.md index a235a9c..22a65e1 100644 --- a/design/design-tokens.md +++ b/design/design-tokens.md @@ -17,16 +17,12 @@ 한 곳에만 쓰고 그 외에는 쓰지 않는다. ## 로고 결정 -- 발주사 인수 자산 `고객사 준비 데모/LOGO/*.png` 4종은 모두 **회색 배경 위 네온 글로우 목업**이다. - 알파 채널이 없어 헤더에 회색 사각형이 박히고, 글로우·발광은 계약 §5.1 이 금지한 신호다. - 또한 워드마크가 **FANUZU(팬우주)** 인데 견적서·별첨의 서비스명은 **FANUZU** 다. -- 그래서 시안에는 **FANUZU 플랫 워드마크를 제안본으로 만들어** 썼다(첫 `a` 의 카운터를 고리 행성으로 치환, - 단색 플럼, 알파 PNG). 인수 원본은 `sources/logo-발주사원본-fanuzu.png` 로 보존했다. -- **서비스명은 `FANUZU` 로 확정됐다 (CD-01).** 데모 최신 소스 3종이 전부 FANUZU 표기이고 - 견적서·별첨의 서비스명과도 일치한다. **FANUZU(팬우주)는 인수 원본에만 남은 옛 표기이므로 신규 산출물에 쓰지 않는다.** - 인수 자산의 FANUZU 워드마크는 이력 보존용으로만 둔다. -- **남은 확인 항목은 로고 원본 하나뿐**: 이 제안 워드마크를 쓸지, 발주사가 별도 로고 원본(벡터·알파)을 - 제공할지 (확인요청항목 **Q6**). +- 발주사 공식 자산 `고객사 준비 데모/LOGO/fanuzu logo only.png`를 수령했고, PNG의 알파 채널도 확인했다. +- 시안은 이 공식 심볼을 `시안/assets/logo.png`에 반영했다. 공식 앱 아이콘 원천도 같은 심볼이며 + `app/assets/brand/app-icon.png`와 `app/assets/brand/app-icon-foreground.png`에 반영돼 있다. +- 따라서 현재 로고는 AI 생성 제안본이나 임시 Fancake 브랜드가 아니다. 별도 제안 로고 승인 대기도 없다. +- **서비스명은 `FANUZU`, 국문 병행 표기는 `팬우주`로 확정됐다.** 발주사 공식 검토 회신서의 + 표지 지시에 따라 종전 CD-01(`Fancake` 확정)은 철회·대체했다(W-01). ## 컬러 운용 규율 (60-30-10) - 60 배경/서피스 · 30 중립 텍스트·보더 · **10 악센트**. 악센트는 **행동·선택·포커스에만** 쓴다. @@ -242,4 +238,6 @@ contrast_checks: | 일자 | 내용 | |---|---| -| 2026-08-08 | **서비스명 확인 질문 삭제 — CD-01 로 이미 확정된 사항이었음.** 「① 서비스명 표기가 FANUZU 인지 팬우주(FANUZU) 인지」를 확인 필요 항목으로 남겨 두었으나, 변경 대장 **CD-01(서비스명 FANUZU 확정)** 으로 닫힌 건이라 질문을 지우고 확정 문구로 교체했다. **디자인 결정 변경은 없고**, 이미 확정된 것을 미확정처럼 적어 둔 표기를 바로잡은 것이다. 로고 원본 제공 여부(**Q6**)는 실제 미확정이라 그대로 남겼다 | +| 2026-08-13 | **공식 자산 반영.** `고객사 준비 데모/LOGO/fanuzu logo only.png`를 공식 FANUZU 심볼 원천으로 확정하고 시안 로고·앱 아이콘에 반영했다. 제안 로고/Q6 대기는 종료했다 | +| 2026-08-11 | **W-01 브랜드 정정.** 발주사 공식 검토 회신서 표지에 따라 서비스명을 `FANUZU`(국문 `팬우주`)로 교체하고, 종전 CD-01(`Fancake` 확정)을 철회했다 | +| 2026-08-08 | **당시 CD-01 기준 정리.** 인수 데모를 근거로 `Fancake`를 확정값으로 보아 서비스명 확인 질문을 삭제했다. 이후 발주사 공식 회신으로 이 판단이 철회됐으며 위 2026-08-11 행이 현재값이다 |