# Color By Number — Báo Cáo Audit Toàn Diện (Executive Summary)

**Ngày:** 2026-05-07 | **Branch:** main | **Phiên bản:** versionCode 1, versionName 1.0
**Quy mô:** 1 module Compose Android, 135 file `.kt`, 30 file `.xml`, ~13K LOC
**Stack:** Kotlin + Compose + Koin + Room + Retrofit + Coil + WorkManager + Apero FO SDK + Firebase

> Báo cáo này tổng hợp 5 audit chuyên sâu (5 reviewer chạy song song). Đọc chi tiết tại:
> - `audit-group-a-startup-architecture-260507-1130.md`
> - `audit-group-b-compose-ui-ux-260507-1130.md`
> - `audit-group-c-coroutines-crash-memory-260507-1130.md`
> - `audit-group-d-data-network-db-260507-1130.md`
> - `audit-group-e-security-ads-playstore-260507-1130.md`

---

## TL;DR

App có **kiến trúc Clean + MVI bài bản**, design system token-based đầy đủ, offline-first pattern implement đúng spec. Tuy nhiên **KHÔNG đủ điều kiện ship Play Store** vì:

1. **Release keystore + password committed vào git** — mất toàn quyền sở hữu publisher.
2. **AdMob test ad units ở `appProd` flavor** — ship = $0 doanh thu + ban risk.
3. **API key + Artifactory + Facebook ClientToken plaintext** trong source/git history.
4. **Main thread blocking đa điểm** — `runBlocking` ở `attachBaseContext`, `ColoringView` parse SVG + region + distance transform main thread → ANR thực sự với painting nhiều region.
5. **Dark mode hoàn toàn không hoạt động** dù tokens đã định nghĩa đầy đủ.
6. **Schema migration `fallbackToDestructiveMigration(true)`** — bump v1→v2 = wipe toàn bộ progress + favorites của user.

**Tổng số issue:** ~115 (~24 Critical, ~37 High, ~46 Medium, ~31 Low).

---

## Severity Distribution

| Group | Critical | High | Medium | Low | Total |
|---|---:|---:|---:|---:|---:|
| A. Startup + DI + Architecture | 3 | 6 | 6 | 3 | 18 |
| B. Compose UI + UX + a11y | 6 | 12 | 13 | 15 | 46 |
| C. Coroutines + Crash + Memory | 5 | 6 | 6 | 3 | 20 |
| D. Data + Network + DB + Offline | 4 | 7 | 9 | 5 | 25 |
| E. Security + Ads + PlayStore | 5 | 6 | ~12 | ~5 | ~28 |
| **Total** | **23** | **37** | **~46** | **~31** | **~137** |

---

## CRITICAL ISSUES — Chặn release (P0)

### 🔐 Security & Compliance (release-blocking)

| # | Issue | File | Group | Effort |
|---|---|---|---|---:|
| **S1** | Release keystore `.jks` + plaintext password commit vào git history; `.gitignore` không exclude | `app/build.gradle.kts:14-22`, `app/keystore/upload-keystore.jks` | E | 4-8h |
| **S2** | API_KEY (jsonsilo) + Apero Artifactory creds (`apero-terasofts:Apero@1234`) hardcoded trong source + git | `core/common/AppConstants.kt:12`, `settings.gradle.kts:22-24`, commit `450ac70` | A, E | 3h |
| **S3** | Facebook ClientToken plaintext trong AndroidManifest | `AndroidManifest.xml:46` | E | 1h |

### 💰 Revenue & Play Store policy (release-blocking)

| # | Issue | File | Group | Effort |
|---|---|---|---|---:|
| **S4** | AdMob TEST ad unit IDs (`ca-app-pub-3940256099942544/...`) ở **cả** `appDev` **và** `appProd` flavor + `fo_config_ads.xml` 28 slot | `app/build.gradle.kts:55-68`, `fo_config_ads.xml` | A, E | 2h + Apero coord |
| **S5** | AdMob `APPLICATION_ID` cũng dùng test ID ở 2 flavor | `build.gradle.kts:44, 58` | E | 30min |
| **S6** | UMP/GDPR consent flow VẮNG MẶT — chỉ work nếu FO SDK handle internally (cần verify) | App init flow | E | 1-3 ngày |
| **S7** | Crashlytics + Analytics auto-init trước khi user consent | `ColorByNumberApplication.kt`, `AndroidManifest.xml` | E | 4h |

### 🐛 Stability & Data integrity (release-blocking)

| # | Issue | File | Group | Effort |
|---|---|---|---|---:|
| **D1** | `fallbackToDestructiveMigration(true)` ở production — bump schema = wipe `coloring_progress` + `favorites` | `core/di/LazyModule.kt` | D | 1 ngày |
| **D2** | `TemplateSyncWorker` xóa toàn bộ catalog khi API trả empty `[]` (200 OK + empty body) | `data/sync/TemplateSyncWorker.kt`, `TemplateRepositoryImpl.kt` | C, D | 2h |
| **D3** | `saveThumbnail` + `extractZip` không atomic — process kill giữa chừng để lại file partial mà `existsAndNonEmpty()` không phát hiện → asset stuck corrupt | `data/coloring/ColoringDownloader.kt`, `ColoringCache.kt` | D | 4h |
| **D4** | `ColoringView` main-thread parse SVG Path + `Region.setPath()` + `computePoleOfInaccessibility` (Bitmap ALPHA_8 96×96 + distance transform 2-pass) → 1.5–2.5s blocking → **ANR thực sự** | `coloring/engine/ColoringView.kt`, `ColoringRegion.kt` | C | 1-2 ngày |
| **D5** | `revealBitmap` (~16MB) + `activePatternBitmap` không cleanup khi `setAsset` gọi lại; shader giữ ref qua `Paint` → memory bloat / OOM | `coloring/engine/ColoringView.kt` | C | 4h |
| **D6** | `runBlocking { dataStore.first() }` trong `MainActivity.attachBaseContext` — block main thread mỗi cold start + config change (~50-200ms) | `MainActivity.kt:22`, `PreferencesManager.kt:32` | A, C, E | 4h |
| **D7** | Zip-slip prefix check thiếu trailing separator → có thể write file out-of-cache-dir | `ColoringDownloader.kt` `extractZip()` | C | 30min |

### 🎨 UX-blocking

| # | Issue | File | Group | Effort |
|---|---|---|---|---:|
| **U1** | Dark mode hoàn toàn không hoạt động — `AppTheme` hardcode `LightAppColors` dù có đủ `DarkAppColors` 49 fields | `designsystem/theme/AppTheme.kt:36-43` | B | 1-2 ngày |
| **U2** | Hardcoded `Color(0xFF...)` ngoài DS ở 6+ điểm (Palette, Calendar, RetryButton, ColoringScreen) → dark mode regression | nhiều file | B | 1 ngày |
| **U3** | `HomeBannerPager` render painter tĩnh `ic_setting_share` thay vì load `banner.imageUrl` qua AsyncImage → banner backend vô dụng | `home/component/HomeBannerPager.kt:97-103` | B | 30min |
| **U4** | Localization vi thiếu nhiều strings (Daily, Home, MyFeed, Coloring fallback English) + onboarding en/vi nội dung **lệch hoàn toàn** | `res/values-vi/strings.xml` | B | 4h |
| **U5** | `MainBottomBar` thiếu `Role.Tab` semantic, contentDescription đọc 2 lần, active state chỉ dùng alpha 0.6f | `navigation/MainBottomBar.kt` | B | 2h |
| **U6** | `ColoringView` AndroidView state race: `var view by mutableStateOf` + `LaunchedEffect` đọc null view → completed update mất khi recompose trước factory chạy | `coloring/ColoringScreen.kt:57-86` | B, C | 4h |

### 🏗️ Architecture/Init bugs

| # | Issue | File | Group | Effort |
|---|---|---|---|---:|
| **A1** | `HomeViewModel` đăng ký double trong cả `EagerModule:56` + `LazyModule:129` → race | `core/di/EagerModule.kt`, `LazyModule.kt` | A, C | 5min |
| **A2** | `AperoAd.init` + `FirstOpenSDK.init` synchronous trong `Application.onCreate` — block trước Splash render | `ColorByNumberApplication.kt` | A | 4h |
| **A3** | `BaseViewModel._effect` dùng `Channel.BUFFERED + repeatOnLifecycle + collectLatest` → ghost navigation / lost effects khi background | `presentation/base/BaseViewModel.kt` | C | 2h |

---

## HIGH ISSUES (P1) — Cần fix trước milestone tiếp theo

### Performance / Threading
- `snapshotBitmap()` 1MB (ARGB_8888 512×512) main thread mỗi `ON_STOP` → 30-100ms UI block + GC pressure (B-H2, C-07)
- Compose `BodySection.drawBehind` allocate Path mỗi frame (B-H8)
- `MyFeedUiState.visibleItems` computed property không `derivedStateOf` → O(n log n) mỗi recomposition (B-H5)
- HomeBannerPager `LaunchedEffect` không dùng `collectLatest` → multiple `delay` pending khi swipe nhanh (B-H7)
- `combine` flow trong ViewModel không `flowOn` / `distinctUntilChanged` (C)

### Network / Data
- OkHttp thiếu disk cache, không SSL pinning (jsonsilo HTTPS = MITM-able), `Level.BODY` log có thể leak signed URL token (D-#1)
- `TemplateSyncWorker` thiếu `Constraints(NetworkType.CONNECTED)`, `BackoffPolicy.EXPONENTIAL`, retry logic — fail thẳng cho mọi exception (D-#3)
- `IntSetConverter` parse string fragile (D-#5)
- Progress save mất khi VM destroy giữa debounce 500ms (C-10)

### Build / Release
- ProGuard rules **trống** + `isMinifyEnabled = true` → release sẽ crash mediation SDK (E)
- `isShrinkResources` không bật → APK size phình (E)
- backup_rules trống cho phép auto cloud backup → leak user data (E)

### A11y / UX
- `AppTemplateCard` thiếu loading state, `contentDescription = null` cho painting + heart icon (B-H1)
- `ColoringPalette` Unicode `✓` render rủi ro tofu, fontSize 11sp/14sp không respect font scale, touch 40dp < 48dp (B-H3)
- Touch target nhiều chỗ < 48dp (B-M7, B-H3)
- `HomeContent.bannerNestedScrollConnection` thrash AnimatedVisibility khi fling (B-H4)
- `MyFeedRow` chunked(2) → row cuối lệch alignment (B-H6)
- `MyFeedTabBar` overflow risk khi text vi dài (B-H12)
- `ColorTemplateBottomSheet` không có hide animation khi clear state (B-H9)
- `Calendar.getDisplayName(Locale.ENGLISH)` cứng → user vi vẫn thấy "January" (B-H11)
- `ColoringScreen` background `Color.White` + strings hardcode English (B-H10)
- `ALL_CATEGORY` semantics bug: prepend pseudo-row vào empty list → UI lừa user "có category" (D-#8)

### App init
- Eager DataStore init kéo theo `runBlocking` (A-H6)
- `applyLocaleAndPop` dùng deprecated `updateConfiguration` + không re-trigger Compose `stringResource` (A-H9)
- FO SDK splash không có local timeout (A-M13)

---

## MEDIUM / LOW (P2-P3)

Tham khảo từng group report. Tóm lược:

- **Recomposition**: `HomePainting`/`MyFeedItem`/`HomeBanner` thiếu `@Stable`/`@Immutable`; AppRaisedSurface `.let` chain phá modifier stability.
- **Design system gaps**: `AppText` không expose `lineHeight`/`fontWeight`; `themes.xml` parent `Theme.MaterialComponents.DayNight.NoActionBar` dư thừa cho 100% Compose.
- **A11y**: Nhiều component thiếu `Role.Button/RadioButton/Tab`, `progressSemantics`, heading semantics.
- **Code smell**: `ColoringScreen.ErrorState` `Spacer(Modifier.width(0.dp))` dead code; duplicate Modifier `.width(102.dp).size(102.dp, 6.dp)`; `colors.xml` còn purple_200/500/700 template defaults.
- **Future-proof**: targetSdk 36 yêu cầu Predictive Back, edge-to-edge mặc định, foreground service type rõ ràng — chưa thấy implement đầy đủ.

---

## Cross-Cutting Themes

### 🔁 Anti-pattern lặp lại
1. **`runBlocking` trên main thread**: 3 chỗ (MainActivity.attachBaseContext, PreferencesManager, sync paths). Pattern xử lý locale bootstrap cần redesign — dùng AtomicReference cache hoặc legacy SharedPreferences boot file thay DataStore.
2. **Hardcoded magic values**: API_KEY, keystore password, Artifactory creds, Facebook ClientToken, AdMob test IDs, Color(0xFF...) ngoài DS, `Locale.ENGLISH` cứng — toàn bộ phải externalize.
3. **Main-thread heavy work**: ColoringView SVG parse + Region + distance transform + snapshot bitmap; thiếu `withContext(Dispatchers.Default)` ở use case layer.
4. **State race trong Compose ↔ AndroidView interop**: `var view by remember { mutableStateOf }` + side-effect đọc state — không idiomatic. Dùng `AndroidView(factory, update)` chuẩn.
5. **Lack of consent gating**: Crashlytics, Analytics, AdMob auto-init trước user consent → GDPR/CCPA risk.

### ✅ Điểm mạnh đáng ghi nhận
- Clean architecture (data/domain/presentation) + MVI Contract sạch.
- Offline-first pattern implement **đúng theo doc**: separated `observe*` / `triggerSync` / `observeSyncStatus`, `replaceAll` 1 transaction, `formatVersion` filter cho schema drift, splash trigger sync, layered DTO→Entity→Domain mappers không leak Room types.
- Design system token-based (49 color fields, CompositionLocal pattern chuẩn).
- `collectAsStateWithLifecycle` consistent (không thấy `collectAsState`).
- `key` parameter dùng đúng trong tất cả LazyColumn/LazyGrid.
- `CancellationException` re-throw chuẩn ở đa số try/catch.
- Pole-of-Inaccessibility 2-pass distance transform — kỹ thuật tốt (chỉ sai chỗ chạy main thread).
- Type-safe Compose Navigation qua kotlinx.serialization.

---

## Top 10 Priority Fixes (Action Plan)

| Priority | Issue | Effort | Owner | Blocker for |
|---:|---|---:|---|---|
| **1** | S1: Rotate keystore + git history cleanup + Play Console "Reset upload key" | 4-8h | DevOps | Release |
| **2** | S4 + S5: Swap test → real AdMob unit IDs cho `appProd` + `APPLICATION_ID` + CI gate chặn keyword `3940256099942544` | 2h | Dev + Apero | Revenue |
| **3** | S2: Rotate API_KEY + Apero Artifactory creds qua secret manager + `local.properties` | 3h | DevOps | Security |
| **4** | D1: Implement Room Migration objects + `exportSchema = true` + xóa `fallbackToDestructiveMigration` | 1 ngày | Backend dev | Data integrity |
| **5** | D4: Move ColoringView SVG parse + Region precompute + label geometry → `Dispatchers.Default` trong `PrepareColoringUseCase` | 1-2 ngày | Senior dev | ANR / UX |
| **6** | U1 + U2: Wire dark mode (`if (darkTheme) DarkAppColors else LightAppColors`) + promote tất cả hardcoded Color() vào AppColors | 1-2 ngày | UI dev | UX |
| **7** | D2 + D3: Guard `TemplateSyncWorker` chống empty + atomic file write (tmp+rename) + retry/backoff | 6h | Backend dev | Data integrity |
| **8** | D5: Bitmap lifecycle cleanup + recycle thumbnail sau save | 4h | Senior dev | Memory / OOM |
| **9** | D6 + A2: Cache locale qua AtomicReference; defer FO SDK / AperoAd init khỏi main thread | 6h | Senior dev | Cold start |
| **10** | S6: Verify FO SDK UMP handling; nếu thiếu, tích hợp Google UMP SDK trước Crashlytics/Analytics init | 1-3 ngày | Senior dev | GDPR / Compliance |

**Estimated total for P0 fixes:** 8-15 ngày dev (1.5-3 tuần với 1 senior dev).

---

## Testing & Verification Recommendations

App hiện không có test (`app/src/test` trống). Khuyến nghị:

1. **DAO tests** với in-memory Room cho FavoriteDao, ColoringProgressDao, PaintingCatalogDao.
2. **Mapper tests** cho IntSetConverter, PaintingEntityMapper, ColoringProgressEntityMapper, ColoringAssetMapper.
3. **Worker tests** với `TestListenableWorkerBuilder` cho `TemplateSyncWorker` + edge case "200 OK + empty body".
4. **ViewModel tests** với `MainDispatcherRule` + `kotlinx-coroutines-test` cho HomeViewModel, ColoringViewModel, MyFeedViewModel.
5. **Compose UI tests** cho Bottom navigation, Coloring tap flow, Banner pager.
6. **Macrobenchmark** cho cold start + frame time của ColoringView render.
7. **TalkBack manual test** cho a11y sweep.
8. **Smoke test `assembleAppProdRelease`** trước mọi merge để bắt ProGuard/R8 issue sớm.

---

## Unresolved Questions (cần PM/Tech Lead trả lời)

1. **FO SDK & UMP**: FO SDK có handle UMP/GDPR consent internally không? Nếu không, kế hoạch tích hợp Google UMP SDK ra sao?
2. **Apero ad units production**: bao giờ nhận từ Apero account? Có thể CI gate hard-fail nếu thấy test ID trong appProd?
3. **Apero `apiKey`**: đã bind đúng applicationId production (`com.colorbynumber.coloringgames.numberpaint`) chưa?
4. **Target user**: có bao gồm trẻ em < 13 (COPPA / Families policy) không? Nếu có, ad mediation cần cấu hình child-directed flag.
5. **Play App Signing**: đang dùng hay không? Nếu có, key trong repo chỉ là upload key — vẫn cần rotate nhưng impact thấp hơn.
6. **Painting max size**: thực tế max bao nhiêu region trong painting production? Quyết định severity của D4 (ColoringView ANR).
7. **Module split timing**: khi nào bắt đầu tách module (`:core:designsystem`, `:feature:home`, …)? Hiện chưa cấp bách nhưng sẽ thành bottleneck khi team scale.
8. **Schema migration strategy**: dùng `Migration` objects truyền thống hay `formatVersion` filter ở mapper layer (hiện đang có sẵn cho catalog)?
9. **Asset cache invalidation**: TTL hay checksum? Hiện chưa có policy rõ.
10. **HomeBanner backend**: API đã trả URL hợp lệ chưa? Cần fallback graceful nếu chưa.
11. **Onboarding nội dung**: en "Tap Numbers, Bring Art to Life" vs vi "Chọn bức tranh bạn thích" — phiên bản nào đúng intent? PM xác nhận.
12. **Tablet/Foldable**: có yêu cầu adaptive UI cho large screens không?
13. **Crashlytics PII policy**: được phép log gì, không log gì?
14. **Dark mode design tokens**: có Figma reference cho dark mode không? `DarkAppColors` hiện đang đoán dựa Material defaults.

---

## Recommended Roadmap

**Phase 0 — Pre-release blockers (1-2 tuần)**
- Top 10 priorities ở trên.
- Verify FO SDK UMP behavior.
- Smoke test release build.

**Phase 1 — Stability & Compliance (1-2 tuần)**
- Tất cả High issues group C (coroutine/memory/lifecycle).
- High issues group D (network resilience, atomic writes).
- High issues group E (ProGuard rules, isShrinkResources, backup rules).
- Test coverage tối thiểu (DAO, Mapper, Worker).

**Phase 2 — UX polish (1-2 tuần)**
- Dark mode hoàn thiện + Preview NIGHT_YES coverage.
- Localization vi đầy đủ + sync onboarding.
- A11y sweep (Roles, touch targets, contentDescription).
- Compose recomposition optimization (@Immutable, derivedStateOf).
- Tablet/Foldable adaptive UI (nếu yêu cầu).

**Phase 3 — Architecture maturity (sau 1.0)**
- Module split khi đạt threshold (15+ feature screens hoặc team >3 dev).
- Macrobenchmark + Baseline Profile.
- Spatial index cho ColoringView nếu painting > 500 region.

---

## Tài liệu tham khảo

- `first-open-sdk-integration-guide.md` (project root) — FO SDK init flow.
- `offline-first-reactive-cache-pattern.md` (project root) — design intent của data layer.
- `PROJECT_BASE_PROMPT.md` (project root) — project specs.
- 5 group reports trong `plans/reports/audit-group-{a..e}-*-260507-1130.md`.
