# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Swoosh is an Android microblogging client for Ghost CMS. It provides offline-capable post creation, image uploads, link previews, and scheduled publishing via Ghost's Admin API.

## Build & Test Commands

```bash
./gradlew assembleDebug          # Build debug APK
./gradlew assembleRelease        # Build release APK (ProGuard enabled)
./gradlew test                   # Run all unit tests
./gradlew app:testDebugUnitTest  # Run unit tests for debug variant
./gradlew test --tests "*.MobiledocBuilderTest"  # Run a single test class
./gradlew test --tests "*.MobiledocBuilderTest.testBasicTextPost"  # Run a single test method
```

Robolectric is used for tests that need Android framework classes. Unit tests include Android resources (`unitTests.isIncludeAndroidResources = true`).

## Architecture

MVVM with Repository pattern, single-module Gradle project.

**Package: `com.swoosh.microblog`**

- **`data/api/`** — Retrofit service (`GhostApiService`), JWT auth (`GhostJwtGenerator`, `GhostAuthInterceptor`), and `ApiClient` singleton with dynamic base URL
- **`data/db/`** — Room database with `LocalPost` entity and `LocalPostDao`
- **`data/model/`** — Three model layers: `GhostPost` (API), `LocalPost` (Room entity), `FeedPost` (UI display). Additional models: `PostStats`, `OverallStats`, `GhostAccount`, `GhostNewsletter`, `GhostPage`, `GhostMember`. Enums: `PostStatus`, `QueueStatus`, `PostFilter`, `SortOrder`
- **`data/repository/`** — `PostRepository` coordinates local DB and remote API; `OpenGraphFetcher` parses link previews via Jsoup
- **`data/`** (root utilities) — `AccountManager` (multi-account, up to 5), `CredentialsManager`, `FeedPreferences`, `HashtagParser`, `MobiledocBuilder`, `ShareUtils`, `UrlNormalizer`
- **`ui/animation/`** — `SwooshMotion` shared animation specs (bouncy, snappy, gentle, quick)
- **`ui/components/`** — Reusable composables: `AnimatedDialog`, `ConfirmationDialog`, `PulsingPlaceholder`
- **`ui/feed/`** — Post feed with search, filtering (All/Published/Draft/Scheduled), sorting
- **`ui/composer/`** — Post creation/editing with image uploads, link previews, hashtags, scheduling
- **`ui/detail/`** — Full post view with pin toggle and animated delete dialog
- **`ui/members/`** — Ghost members/subscribers management
- **`ui/newsletter/`** — Newsletter configuration, subscriber count, newsletter list
- **`ui/pages/`** — Static pages CRUD (create, edit, delete, publish)
- **`ui/preview/`** — HTML post preview
- **`ui/stats/`** — Statistics dashboard with animated counters (total posts, word counts, reading time, tags)
- **`ui/settings/`** — Settings, account management, theme toggle, tags toggle, disconnect
- **`ui/setup/`** — Initial configuration wizard and add-account flow
- **`ui/tags/`** — Tag management and filtering
- **`ui/theme/`** — Material 3 theming with `ThemeMode` (Light/Dark/System), `ThemeViewModel`, `ThemePreferences`
- **`ui/navigation/`** — Compose Navigation graph with bottom nav (Home, Newsletter, Stats, Settings)
- **`worker/`** — `PostUploadWorker` (WorkManager) handles offline queue with exponential backoff

**Key data flow:** Posts are saved to Room first → queued for upload → `PostUploadWorker` syncs to Ghost API when network is available.

## Technical Details

- **Auth:** Ghost Admin API keys are split into `id:secret`, secret is hex-decoded for HS256 JWT signing (5-min expiry)
- **Content format:** Posts use Ghost's mobiledoc JSON format, built by `MobiledocBuilder` (supports text paragraphs and bookmark cards)
- **Credentials:** Stored in `EncryptedSharedPreferences` (AES256-GCM) via `CredentialsManager`
- **API client:** Base URL is configured at runtime during setup; `ApiClient` rebuilds Retrofit instance when URL changes
- **Multi-account:** `AccountManager` supports up to 5 Ghost accounts with UUID-based IDs, per-account credentials, and migration from legacy single-account format
- **Avatars:** Fetched from Ghost post authors (`posts[0].authors[0].profile_image`), not `/users/me/` (which returns 404 for integrations)
- **Permissions:** INTERNET, ACCESS_NETWORK_STATE, CAMERA
- **ProGuard:** Release build suppresses missing `com.google.errorprone.annotations` (pulled in by Google Tink via EncryptedSharedPreferences)
- **Min SDK 26, Target/Compile SDK 34, Kotlin 1.9.22, Java 17**

## Versioning

Version is defined in `app/build.gradle.kts` (`versionCode` and `versionName`).

- **Format:** Semantic versioning `MAJOR.MINOR.PATCH` (e.g., `0.2.0`)
- **`versionCode`:** Integer, increment by 1 on every release (used by Google Play)
- **`versionName`:** Human-readable, shown in Settings screen

**When to bump:**
- **PATCH** (0.2.0 → 0.2.1): Bug fixes, small tweaks, no new features
- **MINOR** (0.2.0 → 0.3.0): New features, UI changes, significant improvements
- **MAJOR** (0.x → 1.0): First stable public release

**Current:** `versionName = "0.3.0"`, `versionCode = 3`

**Process:** When making a release commit, bump both `versionCode` (+1) and `versionName` in `app/build.gradle.kts`. Always bump version when creating a release build or PR.

## Assets

Screenshots for README are in `pics/` — named by screen (feed.png, stats.png, newsletter.png, settings.png).
