mirror of
https://github.com/bitwarden/android.git
synced 2026-05-04 06:36:47 -05:00
Optimize reviewing-changes skill (#6099)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
# Architectural Patterns Quick Reference
|
||||
|
||||
Quick reference for Bitwarden Android architectural patterns during code reviews. For comprehensive details, read `docs/ARCHITECTURE.md` and `docs/STYLE_AND_BEST_PRACTICES.md`.
|
||||
|
||||
## MVVM + UDF Pattern
|
||||
|
||||
### ViewModel Structure
|
||||
|
||||
**✅ GOOD - Proper state encapsulation**:
|
||||
```kotlin
|
||||
@HiltViewModel
|
||||
class FeatureViewModel @Inject constructor(
|
||||
private val repository: FeatureRepository
|
||||
) : ViewModel() {
|
||||
// Private mutable state
|
||||
private val _state = MutableStateFlow<FeatureState>(FeatureState.Initial)
|
||||
|
||||
// Public immutable state
|
||||
val state: StateFlow<FeatureState> = _state.asStateFlow()
|
||||
|
||||
// Actions as functions, state updated via internal action
|
||||
fun onActionClicked() {
|
||||
viewModelScope.launch {
|
||||
val result = repository.performAction()
|
||||
sendAction(FeatureAction.Internal.ActionComplete(result))
|
||||
}
|
||||
}
|
||||
|
||||
// The ViewModel has a handler that processes the internal action
|
||||
private fun handleInternalAction(action: FeatureAction.Internal) {
|
||||
when (action) {
|
||||
is FeatureAction.Internal.ActionComplete -> {
|
||||
// The action handler evaluates the result and updates state
|
||||
action.result.fold(
|
||||
onSuccess = { _state.value = State.Success },
|
||||
onFailure = { _state.value = State.Error(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ BAD - Common violations**:
|
||||
```kotlin
|
||||
class FeatureViewModel : ViewModel() {
|
||||
// ❌ Exposes mutable state
|
||||
val state: MutableStateFlow<FeatureState>
|
||||
|
||||
// ❌ Business logic in ViewModel
|
||||
fun onSubmit() {
|
||||
val encrypted = encryptionManager.encrypt(data) // Should be in Repository
|
||||
_state.value = FeatureState.Success
|
||||
}
|
||||
|
||||
// ❌ Direct Android framework dependency
|
||||
fun onCreate(context: Context) { // ViewModels shouldn't depend on Context
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Expose `StateFlow<T>`, never `MutableStateFlow<T>`
|
||||
- Delegate business logic to Repository/Manager
|
||||
- No direct Android framework dependencies (except ViewModel, SavedStateHandle)
|
||||
- Use `viewModelScope` for coroutines
|
||||
|
||||
Reference: `docs/ARCHITECTURE.md#mvvm-pattern`
|
||||
|
||||
---
|
||||
|
||||
### UI Layer (Compose)
|
||||
|
||||
**✅ GOOD - Stateless, observes only**:
|
||||
```kotlin
|
||||
@Composable
|
||||
fun FeatureScreen(
|
||||
state: FeatureState,
|
||||
onActionClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
when (state) {
|
||||
is FeatureState.Loading -> LoadingIndicator()
|
||||
is FeatureState.Success -> SuccessContent(state.data)
|
||||
is FeatureState.Error -> ErrorMessage(state.error)
|
||||
}
|
||||
|
||||
BitwardenButton(
|
||||
text = "Action",
|
||||
onClick = onActionClick // Sends event to ViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ BAD - Stateful, modifies state**:
|
||||
```kotlin
|
||||
@Composable
|
||||
fun FeatureScreen(viewModel: FeatureViewModel) {
|
||||
var localState by remember { mutableStateOf(...) } // ❌ State in UI
|
||||
|
||||
Button(onClick = {
|
||||
viewModel._state.value = FeatureState.Loading // ❌ Directly modifying ViewModel state
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Compose screens observe state, never modify
|
||||
- User actions passed as events/callbacks to ViewModel
|
||||
- No business logic in UI layer
|
||||
- Use existing components from `:ui` module
|
||||
|
||||
---
|
||||
|
||||
## Hilt Dependency Injection
|
||||
|
||||
### ViewModels
|
||||
|
||||
**✅ GOOD - Interface injection**:
|
||||
```kotlin
|
||||
@HiltViewModel
|
||||
class FeatureViewModel @Inject constructor(
|
||||
private val repository: FeatureRepository, // Interface, not implementation
|
||||
private val authManager: AuthManager,
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : ViewModel()
|
||||
```
|
||||
|
||||
**❌ BAD - Common violations**:
|
||||
```kotlin
|
||||
// ❌ No @HiltViewModel annotation
|
||||
class FeatureViewModel @Inject constructor(...)
|
||||
|
||||
// ❌ Injecting implementation instead of interface
|
||||
class FeatureViewModel @Inject constructor(
|
||||
private val repository: FeatureRepositoryImpl // Should inject interface
|
||||
)
|
||||
|
||||
// ❌ Manual instantiation
|
||||
class FeatureViewModel : ViewModel() {
|
||||
private val repository = FeatureRepositoryImpl() // Should use @Inject
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Annotate with `@HiltViewModel`
|
||||
- Use `@Inject constructor`
|
||||
- Inject interfaces, not implementations
|
||||
- Use `SavedStateHandle` for process death survival
|
||||
|
||||
Reference: `docs/ARCHITECTURE.md#dependency-injection`
|
||||
|
||||
---
|
||||
|
||||
### Repositories and Managers
|
||||
|
||||
**✅ GOOD - Implementation with @Inject**:
|
||||
```kotlin
|
||||
interface FeatureRepository {
|
||||
suspend fun fetchData(): Result<Data>
|
||||
}
|
||||
|
||||
class FeatureRepositoryImpl @Inject constructor(
|
||||
private val apiService: FeatureApiService,
|
||||
private val database: FeatureDao
|
||||
) : FeatureRepository {
|
||||
override suspend fun fetchData(): Result<Data> = runCatching {
|
||||
apiService.getData()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Module provides interface**:
|
||||
```kotlin
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class DataModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindFeatureRepository(
|
||||
impl: FeatureRepositoryImpl
|
||||
): FeatureRepository
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Define interface for abstraction
|
||||
- Implementation uses `@Inject constructor`
|
||||
- Module binds implementation to interface
|
||||
- Appropriate scoping (`@Singleton`, `@ViewModelScoped`)
|
||||
|
||||
---
|
||||
|
||||
## Module Organization
|
||||
|
||||
```
|
||||
android/
|
||||
├── core/ # Shared utilities (cryptography, analytics, logging)
|
||||
├── data/ # Repositories, database, domain models
|
||||
├── network/ # API clients, network utilities
|
||||
├── ui/ # Reusable Compose components, theme
|
||||
├── app/ # Application, feature screens, ViewModels
|
||||
└── authenticator/ # Authenticator app (separate from password manager)
|
||||
```
|
||||
|
||||
**Correct Placement**:
|
||||
- UI screens and ViewModels → `:app`
|
||||
- Reusable Compose components → `:ui`
|
||||
- Data models and Repositories → `:data`
|
||||
- API services → `:network`
|
||||
- Cryptography, logging → `:core`
|
||||
|
||||
**Check for**:
|
||||
- No circular dependencies
|
||||
- Correct module placement
|
||||
- Proper visibility (internal vs public)
|
||||
|
||||
Reference: `docs/ARCHITECTURE.md#module-structure`
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Use Result Types, Not Exceptions
|
||||
|
||||
**✅ GOOD - Result-based**:
|
||||
```kotlin
|
||||
// Repository
|
||||
suspend fun fetchData(): Result<Data> = runCatching {
|
||||
apiService.getData()
|
||||
}
|
||||
|
||||
// ViewModel
|
||||
fun onFetch() {
|
||||
viewModelScope.launch {
|
||||
val result = repository.fetchData()
|
||||
sendAction(FeatureAction.Internal.FetchComplete(result))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ BAD - Exception-based in business logic**:
|
||||
```kotlin
|
||||
// ❌ Don't throw in business logic
|
||||
suspend fun fetchData(): Data {
|
||||
try {
|
||||
return apiService.getData()
|
||||
} catch (e: Exception) {
|
||||
throw FeatureException(e) // Don't throw in repositories
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Try-catch in ViewModel
|
||||
fun onFetch() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val data = repository.fetchData()
|
||||
sendAction(FeatureAction.Internal.FetchComplete(data))
|
||||
} catch (e: Exception) {
|
||||
sendAction(FeatureAction.Internal.FetchComplete(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Use `Result<T>` return types in repositories
|
||||
- Use `runCatching { }` to wrap API calls
|
||||
- Handle results with `.fold()` in ViewModels
|
||||
- Don't throw exceptions in business logic
|
||||
|
||||
Reference: `docs/ARCHITECTURE.md#error-handling`
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
### Architecture
|
||||
- [ ] ViewModels expose StateFlow, not MutableStateFlow?
|
||||
- [ ] Business logic in Repository, not ViewModel?
|
||||
- [ ] Using Hilt DI (@HiltViewModel, @Inject constructor)?
|
||||
- [ ] Injecting interfaces, not implementations?
|
||||
- [ ] Correct module placement?
|
||||
|
||||
### Error Handling
|
||||
- [ ] Using Result types, not exceptions in business logic?
|
||||
- [ ] Errors handled with .fold() in ViewModels?
|
||||
|
||||
---
|
||||
|
||||
For comprehensive details, always refer to:
|
||||
- `docs/ARCHITECTURE.md` - Full architecture patterns
|
||||
- `docs/STYLE_AND_BEST_PRACTICES.md` - Complete style guide
|
||||
274
.claude/skills/reviewing-changes/reference/priority-framework.md
Normal file
274
.claude/skills/reviewing-changes/reference/priority-framework.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Issue Priority Framework
|
||||
|
||||
Use this framework to classify findings during code review. Clear prioritization helps authors triage and address issues effectively.
|
||||
|
||||
## Critical (Blocker - Must Fix Before Merge)
|
||||
|
||||
These issues **must** be addressed before the PR can be merged. They pose immediate risks to security, stability, or architecture integrity.
|
||||
|
||||
### Security
|
||||
- Data leaks or plaintext sensitive data (passwords, keys, tokens)
|
||||
- Weak encryption or insecure key storage
|
||||
- Missing authentication or authorization checks
|
||||
- Input injection vulnerabilities (SQL, XSS, command injection)
|
||||
- Sensitive data in logs or error messages
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/vault/VaultRepository.kt:145** - CRITICAL: PIN stored without encryption
|
||||
PIN must be encrypted using Android Keystore, not stored in plaintext SharedPreferences.
|
||||
Reference: docs/ARCHITECTURE.md#security
|
||||
```
|
||||
|
||||
### Stability
|
||||
- Compilation errors or warnings
|
||||
- Null pointer exceptions in production paths
|
||||
- Resource leaks (file handles, network connections, memory)
|
||||
- Crashes or unhandled exceptions in critical paths
|
||||
- Thread safety violations
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/auth/BiometricRepository.kt:120** - CRITICAL: Missing null safety check
|
||||
biometricPrompt result can be null. Add explicit null check to prevent crash.
|
||||
```
|
||||
|
||||
### Architecture
|
||||
- Mutable state exposure in ViewModels (violates MVVM)
|
||||
- Exception-based error handling in business logic (should use Result)
|
||||
- Circular dependencies between modules
|
||||
- Violation of zero-knowledge principles
|
||||
- Direct dependency instantiation (should use DI)
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/login/LoginViewModel.kt:45** - CRITICAL: Exposes mutable state
|
||||
Change MutableStateFlow to StateFlow in public API to prevent external state mutation.
|
||||
This violates MVVM encapsulation pattern.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Important (Should Fix)
|
||||
|
||||
These issues should be addressed but don't block merge if there's a compelling reason. They improve code quality, maintainability, or robustness.
|
||||
|
||||
### Testing
|
||||
- Missing tests for critical paths (authentication, encryption, data sync)
|
||||
- Missing tests for new public APIs
|
||||
- Tests that don't verify actual behavior (test implementation, not behavior)
|
||||
- Missing test coverage for error scenarios
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/auth/BiometricRepository.kt** - IMPORTANT: Missing test for cancellation
|
||||
Add test for user cancellation scenario to prevent regression.
|
||||
```
|
||||
|
||||
### Architecture
|
||||
- Inconsistent patterns within PR (mixing error handling approaches)
|
||||
- Poor separation of concerns
|
||||
- Tight coupling between components
|
||||
- Not following established project patterns
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/vault/VaultViewModel.kt:89** - IMPORTANT: Business logic in ViewModel
|
||||
Encryption logic should be in Repository, not ViewModel.
|
||||
Reference: docs/ARCHITECTURE.md#mvvm-pattern
|
||||
```
|
||||
|
||||
### Documentation
|
||||
- Undocumented public APIs (missing KDoc)
|
||||
- Missing documentation for complex algorithms
|
||||
- Unclear naming or confusing interfaces
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**core/crypto/EncryptionManager.kt:34** - IMPORTANT: Missing KDoc
|
||||
Public encryption method should document parameters, return value, and exceptions.
|
||||
```
|
||||
|
||||
### Performance
|
||||
- Inefficient algorithms in hot paths (with evidence from profiling)
|
||||
- Blocking main thread with I/O operations
|
||||
- Memory inefficient data structures (with evidence)
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/vault/VaultListViewModel.kt:78** - IMPORTANT: N+1 query pattern
|
||||
Fetching items one-by-one in loop. Consider batch fetch to reduce database queries.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Suggested (Nice to Have)
|
||||
|
||||
These are improvement opportunities but not required. Consider the effort vs. benefit before requesting changes.
|
||||
|
||||
### Code Quality
|
||||
- Minor style inconsistencies (if not caught by linter)
|
||||
- Opportunities for DRY improvements
|
||||
- Better variable naming for clarity
|
||||
- Simplification opportunities
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/vault/VaultScreen.kt:145** - SUGGESTED: Consider extracting helper function
|
||||
This 20-line block appears in 3 places. Consider extracting to reduce duplication.
|
||||
```
|
||||
|
||||
### Testing
|
||||
- Additional test coverage for edge cases (beyond critical paths)
|
||||
- More comprehensive integration tests
|
||||
- Performance tests for non-critical paths
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/login/LoginViewModelTest.kt** - SUGGESTED: Add test for concurrent login attempts
|
||||
Not critical, but would increase confidence in edge case handling.
|
||||
```
|
||||
|
||||
### Refactoring
|
||||
- Extracting reusable patterns
|
||||
- Modernizing old patterns (if touching related code)
|
||||
- Improving testability
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/vault/VaultRepository.kt:200** - SUGGESTED: Consider extracting validation logic
|
||||
Could be extracted to separate validator class for reusability and testing.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional (Acknowledge But Don't Require)
|
||||
|
||||
Note good practices to reinforce positive patterns. Keep these **brief** - list only, no elaboration.
|
||||
|
||||
### Good Practices
|
||||
|
||||
**Format**: Simple bullet list, no explanation
|
||||
|
||||
```markdown
|
||||
## Good Practices
|
||||
- Proper Hilt DI usage throughout
|
||||
- Comprehensive unit test coverage
|
||||
- Clear separation of concerns
|
||||
- Well-documented public APIs
|
||||
```
|
||||
|
||||
**Don't do this** (too verbose):
|
||||
```markdown
|
||||
## Good Practices
|
||||
- Proper Hilt DI usage throughout: Great job using @Inject constructor and injecting interfaces! This follows our established patterns perfectly and makes the code very testable. Really excellent work here! 👍
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Classification Guidelines
|
||||
|
||||
### When Something is Between Categories
|
||||
|
||||
**If unsure between Critical and Important**:
|
||||
- Ask: "Could this cause production incidents, data loss, or security breaches?"
|
||||
- If yes → Critical
|
||||
- If no → Important
|
||||
|
||||
**If unsure between Important and Suggested**:
|
||||
- Ask: "Would I block merge over this?"
|
||||
- If yes → Important
|
||||
- If no → Suggested
|
||||
|
||||
**If unsure between Suggested and Optional**:
|
||||
- Ask: "Is this actionable feedback or just acknowledgment?"
|
||||
- If actionable → Suggested
|
||||
- If acknowledgment → Optional
|
||||
|
||||
### Context Matters
|
||||
|
||||
**Same issue, different contexts**:
|
||||
|
||||
```
|
||||
// Critical for production code
|
||||
Missing null safety check in auth flow → CRITICAL
|
||||
|
||||
// Suggested for internal test utility
|
||||
Missing null safety check in test helper → SUGGESTED
|
||||
```
|
||||
|
||||
**Same pattern, different risk levels**:
|
||||
|
||||
```
|
||||
// Critical for new feature
|
||||
Missing tests for new auth method → CRITICAL
|
||||
|
||||
// Important for bug fix
|
||||
Missing regression test → IMPORTANT
|
||||
|
||||
// Suggested for refactoring
|
||||
Missing tests for refactored helper → SUGGESTED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples by Change Type
|
||||
|
||||
### Dependency Update
|
||||
- **Critical**: Known CVEs in old version not addressed
|
||||
- **Important**: Breaking changes that need migration
|
||||
- **Suggested**: Beta/alpha version stability concerns
|
||||
|
||||
### Bug Fix
|
||||
- **Critical**: Fix doesn't address root cause
|
||||
- **Important**: Missing regression test
|
||||
- **Suggested**: Similar bugs in related code
|
||||
|
||||
### Feature Addition
|
||||
- **Critical**: Security vulnerabilities, architecture violations
|
||||
- **Important**: Missing tests for critical paths
|
||||
- **Suggested**: Additional test coverage, minor refactoring
|
||||
|
||||
### UI Refinement
|
||||
- **Critical**: Missing accessibility for key actions
|
||||
- **Important**: Not using theme (hardcoded colors)
|
||||
- **Suggested**: Minor spacing/alignment improvements
|
||||
|
||||
### Refactoring
|
||||
- **Critical**: Changes behavior (should be behavior-preserving)
|
||||
- **Important**: Incomplete migration (mix of old/new patterns)
|
||||
- **Suggested**: Additional instances that could be refactored
|
||||
|
||||
### Infrastructure
|
||||
- **Critical**: Hardcoded secrets, no rollback plan
|
||||
- **Important**: Performance regression in build times
|
||||
- **Suggested**: Further optimization opportunities
|
||||
|
||||
---
|
||||
|
||||
## Special Cases
|
||||
|
||||
### Technical Debt
|
||||
- Acknowledge existing tech debt but don't require fixing in unrelated PR
|
||||
- Exception: If change makes tech debt worse, it's Important to address
|
||||
|
||||
### Scope Creep
|
||||
- Don't request changes outside PR scope
|
||||
- Can note as "Future consideration" but not required for this PR
|
||||
|
||||
### Linter-Catchable Issues
|
||||
- Don't flag issues that automated tools handle
|
||||
- Exception: If linter is misconfigured and missing real issues
|
||||
|
||||
### Personal Preferences
|
||||
- Don't flag unless grounded in project standards or architectural principles
|
||||
- Use "I-statements" if suggesting alternative approaches
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Critical**: Block merge, must fix (security, stability, architecture)
|
||||
**Important**: Should fix before merge (testing, quality, performance)
|
||||
**Suggested**: Nice to have, consider effort vs benefit
|
||||
**Optional**: Acknowledge good practices, keep brief
|
||||
161
.claude/skills/reviewing-changes/reference/review-psychology.md
Normal file
161
.claude/skills/reviewing-changes/reference/review-psychology.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Review Psychology: Constructive Feedback Phrasing
|
||||
|
||||
Effective code review feedback is clear, actionable, and constructive. This guide provides phrasing patterns for inline comments.
|
||||
|
||||
## Core Directives
|
||||
|
||||
- Ask questions for design decisions, be prescriptive for clear violations
|
||||
- Focus on code, not people ("This code..." not "You...")
|
||||
- Use I-statements for subjective feedback ("Hard for me to understand...")
|
||||
- Explain rationale with every recommendation
|
||||
- Avoid: "just", "simply", "obviously", "easy"
|
||||
- Keep positive feedback brief (list only, no elaboration)
|
||||
|
||||
---
|
||||
|
||||
## Phrasing Templates
|
||||
|
||||
### Critical Issues (Prescriptive)
|
||||
|
||||
**Pattern**: State problem + Provide solution + Explain why
|
||||
|
||||
```
|
||||
**[file:line]** - CRITICAL: [Issue description]
|
||||
|
||||
[Specific fix with code example if applicable]
|
||||
|
||||
[Rationale explaining why this is critical]
|
||||
|
||||
Reference: [docs link if applicable]
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/vault/VaultRepository.kt:145** - CRITICAL: PIN stored without encryption
|
||||
|
||||
PIN must be encrypted using Android Keystore, not stored in plaintext SharedPreferences.
|
||||
Plaintext storage exposes the PIN to backup systems and rooted devices.
|
||||
|
||||
Reference: docs/ARCHITECTURE.md#security
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Suggested Improvements (Exploratory)
|
||||
|
||||
**Pattern**: Observe + Suggest + Explain benefit
|
||||
|
||||
```
|
||||
**[file:line]** - Consider [alternative approach]
|
||||
|
||||
[Current observation]
|
||||
Can we [specific suggestion]?
|
||||
|
||||
[Benefit or rationale]
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**app/login/LoginScreen.kt:89** - Consider using existing BitwardenButton
|
||||
|
||||
This custom button implementation looks similar to `ui/components/BitwardenButton.kt:45`.
|
||||
Can we use the existing component to maintain consistency across the app?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Questions (Collaborative)
|
||||
|
||||
**Pattern**: Ask + Provide context (optional)
|
||||
|
||||
```
|
||||
**[file:line]** - [Question about intent or approach]?
|
||||
|
||||
[Optional context or observation]
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/sync/SyncManager.kt:234** - How does this handle concurrent sync attempts?
|
||||
|
||||
It looks like multiple coroutines could call `startSync()` simultaneously.
|
||||
Is there a mechanism to prevent race conditions, or is that handled elsewhere?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Test Suggestions
|
||||
|
||||
**Pattern**: Observe gap + Suggest specific test + Provide skeleton
|
||||
|
||||
```
|
||||
**[file:line]** - Consider adding test for [scenario]
|
||||
|
||||
[Rationale]
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun `test description`() = runTest {
|
||||
// Test skeleton
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
**data/auth/BiometricRepository.kt** - Consider adding test for cancellation scenario
|
||||
|
||||
This would prevent regression of the bug you just fixed:
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun `when biometric cancelled then returns cancelled state`() = runTest {
|
||||
coEvery { biometricPrompt.authenticate() } returns null
|
||||
|
||||
val result = repository.authenticate()
|
||||
|
||||
assertEquals(AuthResult.Cancelled, result)
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Be Prescriptive vs Ask Questions
|
||||
|
||||
**Be Prescriptive** (Tell them what to do):
|
||||
- Security issues
|
||||
- Architecture pattern violations
|
||||
- Null safety problems
|
||||
- Compilation errors
|
||||
- Documented project standards
|
||||
|
||||
**Ask Questions** (Seek explanation):
|
||||
- Design decisions with multiple valid approaches
|
||||
- Performance trade-offs without data
|
||||
- Unclear intent or reasoning
|
||||
- Scope decisions (this PR vs future work)
|
||||
- Patterns not documented in project guidelines
|
||||
|
||||
---
|
||||
|
||||
## Special Cases
|
||||
|
||||
**Nitpicks** - For truly minor suggestions, use "Nit:" prefix:
|
||||
```
|
||||
**Nit**: Extra blank line at line 145
|
||||
```
|
||||
|
||||
**Uncertainty** - If unsure, acknowledge it:
|
||||
```
|
||||
I'm not certain, but this might be called frequently.
|
||||
Has this been profiled?
|
||||
```
|
||||
|
||||
**Positive Feedback** - Brief list only, no elaboration:
|
||||
```
|
||||
## Good Practices
|
||||
- Proper Hilt DI usage throughout
|
||||
- Comprehensive unit test coverage
|
||||
- Clear separation of concerns
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# Security Patterns Quick Reference
|
||||
|
||||
Quick reference for Bitwarden Android security patterns during code reviews. For comprehensive details, read `docs/ARCHITECTURE.md#security`.
|
||||
|
||||
## Encryption and Key Storage
|
||||
|
||||
**✅ GOOD - Android Keystore**:
|
||||
```kotlin
|
||||
// Sensitive data encrypted with Keystore
|
||||
class SecureStorage @Inject constructor(
|
||||
private val keystoreManager: KeystoreManager
|
||||
) {
|
||||
suspend fun storePin(pin: String): Result<Unit> = runCatching {
|
||||
val encrypted = keystoreManager.encrypt(pin.toByteArray())
|
||||
securePreferences.putBytes(KEY_PIN, encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
// Or use EncryptedSharedPreferences
|
||||
val encryptedPrefs = EncryptedSharedPreferences.create(
|
||||
context,
|
||||
"secure_prefs",
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
```
|
||||
|
||||
**❌ BAD - Plaintext or weak encryption**:
|
||||
```kotlin
|
||||
// ❌ CRITICAL - Plaintext storage
|
||||
sharedPreferences.edit {
|
||||
putString("pin", userPin) // Never store sensitive data in plaintext
|
||||
}
|
||||
|
||||
// ❌ CRITICAL - Weak encryption
|
||||
val cipher = Cipher.getInstance("DES") // Use AES-256-GCM
|
||||
|
||||
// ❌ CRITICAL - Hardcoded keys
|
||||
val key = "my_secret_key_123" // Use Android Keystore
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Use Android Keystore for encryption keys
|
||||
- Use EncryptedSharedPreferences for simple key-value storage
|
||||
- Use AES-256-GCM for encryption
|
||||
- Never store sensitive data in plaintext
|
||||
- Never hardcode encryption keys
|
||||
|
||||
Reference: `docs/ARCHITECTURE.md#security`
|
||||
|
||||
---
|
||||
|
||||
## Logging Sensitive Data
|
||||
|
||||
**✅ GOOD - No sensitive data**:
|
||||
```kotlin
|
||||
Log.d(TAG, "Authentication attempt for user")
|
||||
Log.d(TAG, "Vault sync completed with ${items.size} items")
|
||||
```
|
||||
|
||||
**❌ BAD - Logs sensitive data**:
|
||||
```kotlin
|
||||
// ❌ CRITICAL
|
||||
Log.d(TAG, "Password: $password")
|
||||
Log.d(TAG, "Auth token: $token")
|
||||
Log.d(TAG, "PIN: $pin")
|
||||
Log.d(TAG, "Encryption key: ${key.encoded}")
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Never log passwords, PINs, tokens, keys
|
||||
- Never log encryption keys or sensitive data
|
||||
- Be careful with error messages (don't include sensitive context)
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
### Security
|
||||
- [ ] Sensitive data encrypted with Keystore?
|
||||
- [ ] No plaintext passwords/keys?
|
||||
- [ ] No sensitive data in logs?
|
||||
- [ ] Using AES-256-GCM for encryption?
|
||||
- [ ] No hardcoded encryption keys?
|
||||
|
||||
---
|
||||
|
||||
For comprehensive security details, always refer to:
|
||||
- `docs/ARCHITECTURE.md#security` - Complete security architecture and zero-knowledge principles
|
||||
127
.claude/skills/reviewing-changes/reference/testing-patterns.md
Normal file
127
.claude/skills/reviewing-changes/reference/testing-patterns.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Testing Patterns Quick Reference
|
||||
|
||||
Quick reference for Bitwarden Android testing patterns during code reviews. For comprehensive details, read `docs/ARCHITECTURE.md` and `docs/STYLE_AND_BEST_PRACTICES.md`.
|
||||
|
||||
## ViewModel Tests
|
||||
|
||||
**✅ GOOD - Tests behavior**:
|
||||
```kotlin
|
||||
@Test
|
||||
fun `when login succeeds then state updates to success`() = runTest {
|
||||
// Arrange
|
||||
val viewModel = LoginViewModel(mockRepository)
|
||||
coEvery { mockRepository.login(any(), any()) } returns Result.success(User())
|
||||
|
||||
// Act
|
||||
viewModel.onLoginClicked("user@example.com", "password")
|
||||
|
||||
// Assert
|
||||
viewModel.state.test {
|
||||
assertEquals(LoginState.Loading, awaitItem())
|
||||
assertEquals(LoginState.Success, awaitItem())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ BAD - Tests implementation**:
|
||||
```kotlin
|
||||
@Test
|
||||
fun `repository is called with correct parameters`() {
|
||||
// ❌ This tests implementation details, not behavior
|
||||
viewModel.onLoginClicked("user", "pass")
|
||||
coVerify { mockRepository.login("user", "pass") }
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Test behavior, not implementation
|
||||
- Use `runTest` for coroutine tests
|
||||
- Use Turbine for Flow testing
|
||||
- Use MockK for mocking
|
||||
|
||||
---
|
||||
|
||||
## Repository Tests
|
||||
|
||||
**✅ GOOD - Tests data transformations**:
|
||||
```kotlin
|
||||
@Test
|
||||
fun `fetchItems maps API response to domain model`() = runTest {
|
||||
// Arrange
|
||||
val apiResponse = listOf(ApiItem(id = "1", name = "Test"))
|
||||
coEvery { apiService.getItems() } returns apiResponse
|
||||
|
||||
// Act
|
||||
val result = repository.fetchItems()
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isSuccess)
|
||||
assertEquals(
|
||||
listOf(DomainItem(id = "1", name = "Test")),
|
||||
result.getOrThrow()
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Test data transformations
|
||||
- Test error handling (network failures, API errors)
|
||||
- Test caching behavior if applicable
|
||||
- Mock API services and databases
|
||||
|
||||
Reference: Project uses JUnit 5, MockK, Turbine, kotlinx-coroutines-test
|
||||
|
||||
---
|
||||
|
||||
## Null Safety
|
||||
|
||||
**✅ GOOD - Safe handling**:
|
||||
```kotlin
|
||||
// Safe call with elvis operator
|
||||
val result = apiService.getData() ?: return State.Error("No data")
|
||||
|
||||
// Let with safe call
|
||||
intent?.getStringExtra("key")?.let { value ->
|
||||
processValue(value)
|
||||
}
|
||||
|
||||
// Require with message
|
||||
val data = requireNotNull(response.data) { "Response data must not be null" }
|
||||
```
|
||||
|
||||
**❌ BAD - Unsafe assertions**:
|
||||
```kotlin
|
||||
// ❌ Unsafe - can crash
|
||||
val result = apiService.getData()!!
|
||||
|
||||
// ❌ Platform type unchecked
|
||||
val intent: Intent = getIntent() // Could be null from Java
|
||||
val value = intent.getStringExtra("key") // Potential NPE
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Avoid `!!` unless safety is guaranteed (rare)
|
||||
- Handle platform types with explicit nullability
|
||||
- Use safe calls (`?.`), elvis operator (`?:`), or explicit checks
|
||||
- Use `requireNotNull` with descriptive message if crash is acceptable
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
### Testing
|
||||
- [ ] ViewModels have unit tests?
|
||||
- [ ] Tests verify behavior, not implementation?
|
||||
- [ ] Edge cases covered?
|
||||
- [ ] Error scenarios tested?
|
||||
|
||||
### Code Quality
|
||||
- [ ] Null safety handled properly (no `!!` without guarantee)?
|
||||
- [ ] Public APIs have KDoc?
|
||||
- [ ] Following naming conventions?
|
||||
|
||||
---
|
||||
|
||||
For comprehensive details, always refer to:
|
||||
- `docs/ARCHITECTURE.md` - Full architecture patterns
|
||||
- `docs/STYLE_AND_BEST_PRACTICES.md` - Complete style guide
|
||||
85
.claude/skills/reviewing-changes/reference/ui-patterns.md
Normal file
85
.claude/skills/reviewing-changes/reference/ui-patterns.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Compose UI Patterns Quick Reference
|
||||
|
||||
Quick reference for Bitwarden Android Compose UI patterns during code reviews. For comprehensive details, read `docs/ARCHITECTURE.md` and `docs/STYLE_AND_BEST_PRACTICES.md`.
|
||||
|
||||
## Component Reuse
|
||||
|
||||
**✅ GOOD - Uses existing components**:
|
||||
```kotlin
|
||||
BitwardenButton(
|
||||
text = "Submit",
|
||||
onClick = onSubmit
|
||||
)
|
||||
|
||||
BitwardenTextField(
|
||||
value = text,
|
||||
onValueChange = onTextChange,
|
||||
label = "Email"
|
||||
)
|
||||
```
|
||||
|
||||
**❌ BAD - Duplicates existing components**:
|
||||
```kotlin
|
||||
// ❌ Recreating BitwardenButton
|
||||
Button(
|
||||
onClick = onSubmit,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = BitwardenTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Text("Submit")
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Check `:ui` module for existing components before creating custom ones
|
||||
- Use BitwardenButton, BitwardenTextField, etc. for consistency
|
||||
- Place new reusable components in `:ui` module
|
||||
|
||||
---
|
||||
|
||||
## Theme Usage
|
||||
|
||||
**✅ GOOD - Uses theme**:
|
||||
```kotlin
|
||||
Text(
|
||||
text = "Title",
|
||||
style = BitwardenTheme.typography.titleLarge,
|
||||
color = BitwardenTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp)) // Standard spacing
|
||||
```
|
||||
|
||||
**❌ BAD - Hardcoded values**:
|
||||
```kotlin
|
||||
Text(
|
||||
text = "Title",
|
||||
style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold), // Use theme
|
||||
color = Color(0xFF0066FF) // Use theme color
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(17.dp)) // Non-standard spacing
|
||||
```
|
||||
|
||||
**Key Rules**:
|
||||
- Use `BitwardenTheme.colorScheme` for colors
|
||||
- Use `BitwardenTheme.typography` for text styles
|
||||
- Use standard spacing (4.dp, 8.dp, 16.dp, 24.dp)
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
### UI Patterns
|
||||
- [ ] Using existing Bitwarden components from `:ui` module?
|
||||
- [ ] Using BitwardenTheme for colors and typography?
|
||||
- [ ] Using standard spacing values (4, 8, 16, 24 dp)?
|
||||
- [ ] No hardcoded colors or text styles?
|
||||
- [ ] UI is stateless (observes state, doesn't modify)?
|
||||
|
||||
---
|
||||
|
||||
For comprehensive details, always refer to:
|
||||
- `docs/ARCHITECTURE.md` - Full architecture patterns
|
||||
- `docs/STYLE_AND_BEST_PRACTICES.md` - Complete style guide
|
||||
Reference in New Issue
Block a user