--- name: compose-ui description: Campfire Compose UI conventions. Trigger when authoring or reviewing Compose UI in this repo, and ALWAYS before adding, replacing, or importing an icon in UI code — every icon comes from the `CampfireIcons` pack, generated via the Valkyrie CLI (never from a material-icons library import, and never hand-written vector paths). --- ## When to use - Building or editing a Compose screen, composable, or widget in Campfire. - Any task that needs an icon: a new glyph, a replacement, or a reference the codebase doesn't have yet. ## Icons — the one rule All icons are project-owned `ImageVector`s under `common/compose/src/commonMain/kotlin/app/campfire/common/compose/icons/`, exposed as extension properties on the `CampfireIcons` object. The deprecated `material-icons-extended` library was removed in PR #1056 and must stay gone — the dependency is not in the version catalog and no `androidx.compose.material.icons` import may be reintroduced. Packs: | Pack | Use | Style | |---|---|---| | `CampfireIcons.Rounded` | Default for all UI glyphs | Material Symbols Rounded, fill=0 (fill=1 for solid-by-nature glyphs) | | `CampfireIcons.Filled` | Nav selected states, paired with an unselected `Rounded`/`Outline` icon | fill=1 | | `CampfireIcons.Outline` | Legacy unselected nav set | fill=0 | | `CampfireIcons.Theme` | Decorative camping/nature icons for app themes | Icons8 Arcade — use the `add-theme-icons` skill instead | Before adding, check the icon doesn't already exist: `grep -r "val CampfireIcons" common/compose/src/commonMain/kotlin/app/campfire/common/compose/icons/`. Many old material names were collapsed onto one asset (Clear→`Close`, WarningAmber→`Warning`, ErrorOutline→`Error`, DeleteOutline→`Delete`) — search for the concept, not just the exact name. To add a missing icon, follow [references/adding-icons.md](references/adding-icons.md) — Valkyrie CLI generation from a Material Symbols SVG, with the project's post-processing rules (license header, bare `name`, `autoMirror`, lazy pattern). ## Conventions The Circuit screen/presenter/UI pattern, DI scopes, and module layout live in `CLAUDE.md` — follow them as written. On top of those: - Every `ImageVector` is declared `by lazy(LazyThreadSafetyMode.NONE)` (Theme pack: `PUBLICATION`). The backing-property `private var _x` + `get()` pattern from older Valkyrie output is banned — convert it on sight. - Every `@Composable` takes `modifier: Modifier = Modifier` as its first optional parameter (see the `compose-modifier` skill). - Icon-only buttons are wrapped in `IconButtonTooltip` with a localized label (see the `iconbutton-a11y` skill). - User-facing strings go through `composeResources` string resources (see the `i18n-compose` skill); no apostrophe escaping — write `doesn't`, never `doesn\'t`. ## Verify - `./scripts/ktlint --format` (auto-fixes import ordering), then - `./gradlew :common:compose:compileKotlinJvm` for icon-only changes, or the touched feature module's JVM compile task for UI changes (`:app:desktop:compileKotlin` compiles everything — the desktop module is pure JVM, so there is no `compileKotlinJvm` there). iOS link tasks can hang — compile tasks only. - Read gradle's own exit code (`./gradlew > log 2>&1; echo $status`) — piping the build into `tail` reports the pipe's exit code and masks a failed or nonexistent task. - Valkyrie PascalCases SVG filenames segment-wise (`Grid3x3.svg` → property `Grid3X3`) — copy the property name from the generated file, not from the filename.