{\rtf1\ansi\ansicpg1251\cocoartf2868
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;\f1\froman\fcharset0 Times-Roman;\f2\fmodern\fcharset0 CourierNewPS-ItalicMT;
}
{\colortbl;\red255\green255\blue255;\red0\green29\blue164;\red255\green255\blue255;\red9\green9\blue9;
\red0\green0\blue0;\red114\green0\blue130;\red17\green109\blue18;}
{\*\expandedcolortbl;;\cssrgb\c0\c20000\c70196;\cssrgb\c100000\c100000\c100000;\cssrgb\c3137\c3137\c3137;
\cssrgb\c0\c0\c0;\cssrgb\c52941\c6275\c58039;\cssrgb\c2353\c49020\c9020;}
\paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0
\deftab720
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 \cb3 \expnd0\expndtw0\kerning0
# \cf4 CLAUDE.md
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Build & Development Commands
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ```
\f2\i \cf6 bash
\f1\i0\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf7 # Build
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew assembleDebug
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew assembleStaging
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew assembleRelease
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 # Lint (must pass before PR)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew ktlint
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew ktlintFormat \'a0 # auto-fix style issues
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 # Tests
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew test\'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 # all unit tests
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew :chat:test\'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 \'a0 # single module
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew :common:testDebugUnitTest \'a0 # specific variant
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew connectedAndroidTest\'a0 \'a0 \'a0 \'a0 # instrumented tests
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 # Full check (lint + tests)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 ./gradlew check
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Module Architecture
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 Three-module clean architecture:
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 - \cf4 **`:common`** \'97 shared utilities, infra (network, resources), base domain/data. No module deps.
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **`:chat`** \'97 feature module with Compose UI, DI, domain models, data sources, ViewModels.
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **`:app`** \'97 application module, wires everything together. Depends on `:chat` and `:common`.
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 Each feature module follows layers: `ui/` \uc0\u8594  `domain/` \u8594  `data/`
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Key Patterns
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ### \cf4 ViewModel
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 ```
\f2\i \cf6 kotlin
\f1\i0\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf7 @HiltViewModel
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 class FooViewModel @Inject constructor(...) : ViewModel() \{
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0var uiState by mutableStateOf(FooUiState())
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0\'a0\'a0\'a0\'a0private set
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val _uiAction = Channel<FooUiAction>()
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0val uiAction = _uiAction.receiveAsFlow()
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \}
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Use `Channel<UiAction>` for one-time events (navigation, snackbars)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Use `mutableStateOf` for observable screen state
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `viewModelScope.launch \{ \}` for async \'97 always block body `\{ \}`, not `= expr` (returns `Job`)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 "Not implemented" actions: `snackbar.show(SnackbarEvent.Info(strings.getString(R.string.not_implemented)))`
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ### \cf4 Navigation
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Routes defined as `sealed class` with `data object` entries: `ChatNavScreen`, `MainNavScreen`
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Route params serialized via `kotlinx.serialization`: `Uri.encode(Json.encodeToString(entry))`
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Composable observes actions via `LaunchedEffect(uiAction) \{ ... \}`
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ### \cf4 Bottom Sheet / Disclaimer
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `rememberBottomSheetScaffoldState(skipHiddenState = false)` must be defined **outside** any `if` block
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Use `LaunchedEffect` to expand/collapse imperatively
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ### \cf4 Logging
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf4 Use extension functions \'97 **never** `Log.d`:
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 ```
\f2\i \cf6 kotlin
\f1\i0\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf7 logV \{ "verbose" \}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 logD \{ "debug" \}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 logI \{ "info" \} \'a0 \'a0 // sent to Crashlytics \'97 no PII
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 logW(error) \{ "warning" \}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 logE(error) \{ "error" \}
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf4 Never log card numbers, passwords, user text, or names in `logI`/`logW`/`logE`.
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Testing
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 Use **MockK** (`io.mockk`) for all mocking \'97 never Mockito. MockK handles Kotlin final classes natively without `open` modifiers or JVM flags.
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ```
\f2\i \cf6 kotlin
\f1\i0\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf7 // UseCase test \'97 mock the repository, test the logic
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 class GetFooUseCaseTest \{
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val repository: FooRepository = mockk()
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val useCase = GetFooUseCase(repository)
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0@Test
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0fun `given valid input when invoked then returns expected result`() = runTest \{
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0\'a0\'a0\'a0\'a0coEvery \{ repository.getFoo(any()) \} returns ApiResult.Success(fakeFoo)
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0\'a0\'a0\'a0\'a0val result = useCase(param)
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0\'a0\'a0\'a0\'a0assertThat(result).isInstanceOf(ApiResult.Success::class)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0\}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \}
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 // ViewModel test \'97 mock the UseCase and ResultHandler
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 class FooViewModelTest \{
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val testDispatcher = StandardTestDispatcher()
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val getFoo: GetFooUseCase = mockk()
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0private val resultHandler: ResultHandler = mockk()
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0@Before fun setUp() \{ Dispatchers.setMain(testDispatcher) \}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \'a0\'a0\'a0@After fun tearDown() \{ Dispatchers.resetMain() \}
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 \}
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 Key rules:
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `mockk<T>()` to create mocks (never `mock()` from Mockito)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `coEvery \{ suspendFn(...) \} returns value` for suspend functions
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `coEvery \{ ... \} returnsMany listOf(a, b)` for chained return values
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 `any()` as argument matcher (works for nullable params too)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Use `assertk` for assertions (`assertThat`, `isEqualTo`, `isTrue`, etc.)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Do **not** mark production classes `open` just for testing
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Code Style
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 - \cf4 ktlint 0.50.0 enforced (`kotlin.code.style = official`)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 Run `./gradlew ktlintFormat` before submitting a PR
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Commit & Branch Convention
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 **Branches**: `username/PHOEN-123-short-description`
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf4 **Commits**:
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf7 PHOEN-123 short summary
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf7 - change 1 and explanation
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf7 - change 2 and explanation
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 ```
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 ## \cf4 Tech Stack
\f1\fs24 \cf0 \
\

\f0\fs26\fsmilli13333 \cf2 - \cf4 **UI**: Jetpack Compose (BOM 2025.01.00), Material3
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **DI**: Hilt 2.49
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Navigation**: Navigation Compose 2.8.5
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Async**: Kotlin Coroutines 1.8.1
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Networking**: Retrofit 2.9.0, OkHttp 5, Socket.io 2.1.2 (WebSocket for chat/voice)
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Serialization**: kotlinx.serialization
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Firebase**: Auth, Crashlytics, Analytics, Messaging
\f1\fs24 \cf0 \

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Media**: ExoPlayer 1.2.1, Lottie 6.6.0
\f1\fs24 \cf0 \
\pard\pardeftab720\li960\fi-960\sa320\partightenfactor0

\f0\fs26\fsmilli13333 \cf2 - \cf4 **Min SDK**: 28, **Target SDK**: 35, **Java**: 17, **Kotlin**: 2.0.0
\f1\fs24 \cf0 \
}