# Data Model: Compose Preview Screenshot Testing

**Feature**: 018-compose-screenshot-testing
**Date**: 2026-05-08

## Entities

This feature does not introduce database entities or domain models. The "data" consists of file-system artifacts managed by the CST plugin and Gradle build system.

### Preview Composable

A `@Composable` function annotated with `@Preview` or `@PreviewLightDark` in a KMP module's `commonMain` source set.

| Field | Type | Description |
|-------|------|-------------|
| Function name | Kotlin function | Unique within its file; naming convention `*Preview` or in `*Previews.kt` |
| Visibility | `internal` or `public` | Must NOT be `private` — needs to be importable by `screenshot-tests` module |
| Annotations | `@Preview` / `@PreviewLightDark` | From `androidx.compose.ui.tooling.preview` (CMP 1.11+) |
| Theme wrapper | `AppTheme { ... }` | Required — ensures M3 theming in reference images |
| Sample data | Hardcoded synthetic | Never real user data, PII, or location coordinates |
| Module | KMP module path | e.g. `core:ui`, `feature:messaging` |

**Validation rules**:
- Function must be annotated with at least one preview annotation
- Content must be wrapped in `AppTheme`
- No ViewModel, DI, or network dependencies
- Must compile on all KMP targets (lives in `commonMain`)

### Screenshot Test Wrapper

A `@Composable` function in the `screenshotTest` source set of the `screenshot-tests` module.

| Field | Type | Description |
|-------|------|-------------|
| Function name | Kotlin function | Convention: `{PreviewName}ScreenshotTest` |
| Annotations | `@PreviewTest` + `@Preview`/`@PreviewLightDark` | `@PreviewTest` from `com.android.tools.screenshot` |
| Body | Composable call | Delegates to the target preview composable from a KMP module |

**Validation rules**:
- Must have `@PreviewTest` annotation (required since CST alpha10)
- Must also have `@Preview` or `@PreviewLightDark` (tells CST what configurations to render)
- Renaming the function invalidates existing reference images

### Reference Image

A PNG file generated by the CST `update{Variant}ScreenshotTest` task.

| Field | Type | Description |
|-------|------|-------------|
| File name | String | `{package}.{FunctionName}_{hash1}_{hash2}_{index}.png` |
| Location | File path | `screenshot-tests/src/screenshotTestDebug/reference/` |
| Format | PNG | Lossless raster image |
| Variant | Build variant | e.g. `fdroidDebug`, `debug` |

**Validation rules**:
- Must be committed to version control
- Generated on a consistent OS/JDK to avoid rendering differences
- Validated by `validate{Variant}ScreenshotTest` against current rendered output

### Diff Report

An HTML report generated by the CST `validate{Variant}ScreenshotTest` task when validation fails.

| Field | Type | Description |
|-------|------|-------------|
| Location | File path | `screenshot-tests/build/reports/screenshotTest/preview/{variant}/index.html` |
| Content | HTML | Side-by-side reference/actual/diff images with percentage difference |
| CI artifact | Uploaded | Available as GitHub Actions build artifact on failure |

## Relationships

```
Preview Composable (commonMain)
    ↓ imported by
Screenshot Test Wrapper (screenshotTest source set)
    ↓ rendered by CST plugin → generates
Reference Image (version-controlled PNG)
    ↓ compared by validate task → produces on diff
Diff Report (HTML, CI artifact)
    ↓ selected images copied by
Docs Asset (consumed by spec 003 docs pipeline)
```

## State Transitions

Reference images have a simple lifecycle:

```
[Not Exists] → update task → [Generated] → commit → [Tracked]
[Tracked] → UI changes → validate fails → [Stale]
[Stale] → update task → [Updated] → commit → [Tracked]
[Tracked] → preview deleted → [Orphaned] → manual cleanup → [Removed]
```

## Version Catalog Entries

New entries required in `gradle/libs.versions.toml`:

```toml
[versions]
compose-screenshot = "0.0.1-alpha14"

[libraries]
screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "compose-screenshot" }

[plugins]
compose-screenshot = { id = "com.android.compose.screenshot", version.ref = "compose-screenshot" }
```

## Configuration Properties

| Property | File | Value |
|----------|------|-------|
| `android.experimental.enableScreenshotTest` | `gradle.properties` | `true` |
| `experimentalProperties["android.experimental.enableScreenshotTest"]` | `screenshot-tests/build.gradle.kts` | `true` |
| `imageDifferenceThreshold` | `screenshot-tests/build.gradle.kts` | `0.0005f` |
| `namespace` | `screenshot-tests/build.gradle.kts` | `org.meshtastic.screenshot.tests` |

## Existing Preview Inventory

### Modules with existing previews (require audit)

| Module | Preview count | Files | Key issues |
|--------|--------------|-------|------------|
| `core:ui` | ~40+ | ~25 component files + `AlertPreviews.kt` | Mixed theme wrapping; some lack `AppTheme` |
| `feature:messaging` | 6 | 4 files | Good — uses `AppTheme`, `@PreviewLightDark` |
| `feature:node` | 14 | 5 files | `NodeDetailPreviews.kt` has 4 `private` previews |
| `feature:wifi-provision` | 19 | 2 files | Excellent coverage |
| `feature:settings` | 3 | 3 files (all `androidMain`) | Need `commonMain` previews |
| `feature:intro` | 1 | 1 file (`androidMain`) | Need `commonMain` previews |

### Modules needing new previews

| Module | Primary components to preview |
|--------|------------------------------|
| `feature:connections` | `DeviceListItem`, `ConnectingDeviceInfo`, `DisconnectButton`, `DeviceList` |
| `feature:firmware` | `FirmwareUpdateScreen` and sub-components |
| `feature:settings` (commonMain) | Settings sections currently only previewed in `androidMain` |
| `feature:intro` (commonMain) | Welcome/onboarding screen composables currently only previewed in `androidMain` |

### Modules deferred

| Module | Reason |
|--------|--------|
| `feature:map` | Platform-specific map renderers don't work in CST preview context |
| `feature:widget` | Glance composables are incompatible with CST |
| `feature:discovery` | No UI composables |
| `feature:metrics` | No UI composables (metrics embedded in `feature:node`) |
| `feature:auto` | No UI composables |
| `feature:docs` | No UI composables |
