
* Add style prop to UserAvatar component for better customization * Refactor UserAvatar to use getAvatarSeed utility for consistent avatar generation * Fix React hook ordering issues in profile/overview.tsx to prevent crashes during auth state changes * Add proper state initialization and cleanup during authentication transitions * Ensure consistent fallback avatar display for unauthenticated users These changes improve stability during login/logout operations and provide better visual continuity with Robohash avatars when profile images aren't available.
9.2 KiB
POWR App Coding Style Guide
Last Updated: 2025-04-01
Status: Active
Related To: Development Standards, Best Practices
Purpose
This guide establishes consistent coding standards for the POWR application codebase. It provides clear principles and specific guidelines to ensure readability, maintainability, and quality across the project. Following these guidelines helps all contributors work together effectively and reduces the cognitive load when reading and modifying code.
Core Principles
Consistency is King
Above all other principles, be consistent. If a file follows one convention, continue using that convention. Separate style changes from logic changes in different commits.
Rationale: Inconsistent naming or patterns (Apple
, a
, fruit
, redThing
for the same concept) make it difficult to understand relationships between components.
Readability Above Efficiency
Prefer readable code over fewer lines of cryptic code.
Rationale: Code will be read many more times than it will be written, by different people—including yourself a year from now.
All Code is Either Obviously Right, or Non-Obviously Wrong
Strive to make code obviously correct at first glance. It shouldn't require detailed study to decipher.
Rationale: When code is obviously right, it's probably right. The goal is to have suspicious code look suspiciously wrong and stick out. This happens when everything else is clear, and there's a tricky bit that needs work.
Code Comments: Comments indicate the code isn't self-explanatory through naming, structure, or organization. While not prohibited, strive to make code require almost no comments.
Good uses for comments:
- Explaining why the code is written a certain way
- Documenting details that can't be easily explained in code (algorithms, patterns)
- API interface documentation (keep comments close to code for easier maintenance)
Boring is Best
Make your code the most boring version it could be.
Rationale: Production code should be maintainable, not clever. Reserve creativity for test cases with interesting constants and edge cases.
Split Implementation from Interface
Always separate storage, presentation, and communication protocols.
Rationale: When layers are inappropriately tied together, changes may cause unexpected breakage in seemingly unrelated areas.
Split "Policy" and "Mechanics"
Separate configuration/policy ("the why") from implementation/mechanics ("the how").
Rationale: This separation allows testing implementation independently from policy triggers, making features easier to toggle and canary.
Corollaries:
- Create separate functions for "doing" and for "choosing when to do"
- Create flags for all implementation features
Documentation of Deficiencies
TODO
Comments
Use TODO
comments liberally to document potential improvements:
Format: // TODO[(Context)]: <Action> by/when <Deadline Condition>
Components:
- Context - (Optional) Reference to issue tracker or documentation
- Action - Specific, actionable task with explanations as needed
- Deadline Condition - When the task should be completed
Rationale: TODO
comments help readers understand what is missing or could be improved, making technical debt visible.
Good examples:
// TODO: Replace certificate with staging version once we get letsencrypt to work.
// TODO(POWR-123): Replace old logic with new logic when out of experimental mode.
// TODO(NOSTR-456): Implement zap forwarding when NIP-57 is fully supported by relays.
Less effective examples (lacking deadline condition):
// TODO: Add precompiling templates here if we need it.
// TODO: Remove use of bash.
Poor examples:
// TODO: wtf?
// TODO: We shouldn't do this.
// TODO: err...
FIXME
Comments
Use FIXME
comments as stronger TODO
s that MUST be addressed before code submission.
Format: // FIXME: <Action or note>
Rationale: These act as development reminders for incomplete or problematic code that needs attention before merging.
Examples:
// FIXME: Remove hack
// FIXME: Revert hardcoding of relay URL
// FIXME: Implement function
// FIXME: Refactor these usages across codebase
Naming Conventions
Semantic Variable Names
Names should reflect content and intent. Choose specific, descriptive names. Use only well-known abbreviations.
// Bad
input = "123-4567";
dialPhoneNumber(input); // unclear whether this makes semantic sense
// Good
phoneNumber = "123-4567";
dialPhoneNumber(phoneNumber); // clearly intentional
// Bad
text = 1234;
address = "nostr:npub1/" + text; // why is text being added?
// Good
profileId = 1234;
address = "nostr:npub1/" + profileId; // clearly adding a profile ID
Rationale: Semantic names make bugs obvious and express intention without requiring comments.
Include Units for Measurements
Always include units in variable names for clarity:
- Time intervals:
timeoutSec
,timeoutMs
,refreshIntervalHours
- Timestamps:
startTimestamp
(use language-appropriate types internally) - Distances:
lengthFt
,lengthMeter
,lengthCm
- Computer storage:
diskMib
(1 Mebibyte = 1024 Kibibytes),ramMb
(1 Megabyte = 1000 Kilobytes)
// Bad
cost = disk * cents;
// Good
costCents = diskMib * 1024 * centsPerKilobyte;
Rationale: Including units prevents many common bugs related to unit confusion.
Constants
Assign Literals to Constants
Every string or numeric literal should be assigned to a constant or constant-like variable.
Exceptions: Identity-type zero/one values: 0
, 1
, -1
, ""
// Bad
if (status === 4) { /* ... */ }
// Good
const STATUS_COMPLETED = 4;
if (status === STATUS_COMPLETED) { /* ... */ }
Rationale: Constants make code more readable and maintainable by centralizing values and their meanings.
React & React Native Specifics
Component Organization
Structure React components consistently:
- Imports
- Type definitions and interfaces
- Constants
- Component function
- Helper functions
- Styles (if using StyleSheet)
- Export statement
Props and State
- Use TypeScript interfaces for props and state types
- Default props should be handled with destructuring in function parameters
- Prefer functional components with hooks over class components
interface ButtonProps {
title: string;
onPress: () => void;
color?: string;
}
const Button = ({ title, onPress, color = 'blue' }: ButtonProps) => {
// Component implementation
};
Testing Guidelines
All core code guidelines apply to tests with these modifications:
Repetitive Test Code Allowed
While DRY (Don't Repeat Yourself) is a good practice, clarity in tests takes precedence.
Rationale: Test readability is paramount. Sometimes repetition makes tests easier to understand.
Small Test Cases
Create small, targeted test cases rather than large, complex ones.
Rationale: Small tests are easier to write, debug, and understand. Large tests often indicate design problems in the code being tested.
Avoid Complex Logic in Tests
Keep test logic simple. If needed, create helper functions with their own tests.
Rationale: Complex test logic is more likely to contain bugs, defeating the purpose of testing.
Descriptive Test Values
Use creative but clearly test-oriented values that don't look like production data.
// Bad
login("abc", "badpassword"); // Are these values significant?
const testPubkey = "npub1abcdef12345..."; // Looks too "real"
// Good
const testUsername = "test-user-alpha";
const testPassword = "obvious-test-password!";
const testBadPassword = "wrong-password-for-testing";
const testPubkey = "npub_for_test_user_alpha";
login(testUsername, testPassword); // Expected success
login(testUsername, testBadPassword); // Expected failure
Rationale: When variable names are clear, test intent becomes obvious.
Keep Related Logic Together
Group related test logic and conditions together for better readability.
// Bad
initialBalance = 100;
deposit();
// ...many lines of test code...
withdraw();
expect(balance).toBe(167); // Why 167?
// Good
initialBalance = 100;
depositAmount = 100;
withdrawAmount = 33;
expectedBalance = 167; // Clear where 167 comes from
// ...test code...
expect(balance).toBe(expectedBalance);
Rationale: Grouping related elements makes tests more understandable and self-documenting.
TypeScript Best Practices
Type Safety
- Prioritize type safety; avoid using
any
unless absolutely necessary - Use TypeScript's utility types (
Partial<T>
,Pick<T>
, etc.) when appropriate - Define explicit return types for functions, especially for public API functions
Interfaces vs Types
- Prefer interfaces for public APIs that may be extended
- Use type aliases for unions, intersections, and types that won't be extended
Enums
- Use string enums for better debugging and serialization
- Consider using const enums for performance when applicable