You are a Sonnet worker on the Bookshelf Android app (~/bookshelf). Read `docs/SPEC.md` first — it is the authoritative product contract and it wins over anything you infer from the code. Do not restate it, do not let it drift. ## Ground rules (violating these fails the wave) - Build ONLY with `./tasks/gw ` — never `./gradlew`. A second worker shares this Gradle project dir and concurrent invocations clobber each other. `tasks/gw` is a flock-serialized wrapper. - Run builds in the FOREGROUND. Never background a Gradle build and end your turn saying you'll report later — `claude -p` kills background tasks and you will never report at all. Builds take up to 10 minutes; just wait. - You own EXACTLY these files: app/app/src/main/java/org/modg/bookshelf/ui/locations/LocationsScreen.kt app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryScreen.kt app/app/src/main/res/drawable/ic_shelves.xml (new file, create it) app/app/src/test/** (tests you add) Touch NOTHING else. Specifically forbidden: any build file (`app/build.gradle.kts`, `gradle/libs.versions.toml`, `settings.gradle.kts`), `data/**`, `ui/scan/**`, `ui/detail/**`, `ui/components/**`, `ui/nav/**`, `ui/settings/**`, `ui/setup/**`, `AppContainer.kt`. Another worker and the orchestrator own those RIGHT NOW and are editing them concurrently. - Do not change any public composable signature. `ui/nav/BookshelfNavHost.kt` calls these screens and you may not edit it. ## Task 1 — the ghost-bookcase bug (highest priority, a real user-facing defect) `LocationsScreen.kt` line ~97. The Scaffold hands `content` an `innerPadding` that accounts for the top app bar. The empty-state branch applies it; the list branch does NOT: ) { innerPadding -> PaperSurface(...) { if (state.bookcases.isEmpty()) { EmptyState(modifier = Modifier.padding(innerPadding), ...) // correct } else { LazyColumn(contentPadding = PaddingValues(bottom = 96.dp)) { // BUG So the first bookcase row renders UNDERNEATH the app bar and is invisible. A user created a bookcase, could not see it, created a second one, and ended up with two real bookcases and no idea why. Fix it by folding `innerPadding` into the LazyColumn's `contentPadding` so the existing 96.dp bottom inset is PRESERVED and added to, not replaced — the bottom inset is what keeps the last row clear of the FAB. Something equivalent to: contentPadding = PaddingValues( top = innerPadding.calculateTopPadding(), bottom = innerPadding.calculateBottomPadding() + 96.dp, ) Using `contentPadding` rather than `Modifier.padding` is deliberate: it keeps the list scrolling under the bar instead of clipping the scroll area. VERIFY THIS SPECIFICALLY: add a Paparazzi snapshot of `LocationsScreen` in a state with exactly ONE bookcase, and confirm in the rendered PNG that the bookcase row is fully visible below the app bar. A one-bookcase list is the exact case that was broken and it must be the case you prove fixed. If the existing Paparazzi harness makes rendering this screen with seeded state impractical, say so plainly in your report rather than skipping it silently. ## Task 2 — auto-focus the first field in the location dialogs In `LocationsScreen.kt`, `BookcaseEditDialog` (~line 276) and `ShelfEditDialog` (~line 299) each open with an unfocused `OutlinedTextField`. The first field should take focus and raise the keyboard when the dialog appears. Use a `FocusRequester` + `LaunchedEffect(Unit) { focusRequester.requestFocus() }`. Bookcase dialog: focus "Name" (not "Note"). Shelf dialog: focus "Label". Guard the requestFocus call so it cannot throw if the node isn't attached yet. ## Task 3 — library filter empty state `LibraryScreen.kt` ~line 204. The filter DropdownMenu always offers "All books" first, then a flat list of bookcases and shelves. When there are NO bookcases and NO shelves, the menu contains only "All books" — which is already the active state and cannot be changed, so it is a menu with nothing in it. When `bookcases` and `shelves` are both empty, replace the menu contents with a single DISABLED item reading "Add a bookcase to enable filtering". Keep the toolbar filter icon visible and enabled (it is how the feature is discovered) — only the menu's contents change. When locations DO exist, behaviour is unchanged. ## Task 4 — replace the Warehouse icon with a real bookcase `LibraryScreen.kt` line ~100 uses `Icons.Outlined.Warehouse` for the button that opens Locations. It renders as a barn and reads wrong. `material-icons-extended` 1.7.8 has no bookcase glyph (I checked all 1932 outlined icons), so use Material Symbols' `shelves`, which is a bookcase frame with shelves and books on them. Create `app/app/src/main/res/drawable/ic_shelves.xml` with EXACTLY this content. This is the SVG path verbatim from Google's CDN. Do not re-derive it, do not "simplify" it, and do not convert its relative (lowercase) commands to absolute ones — Android's pathData parser accepts SVG syntax as-is. Material Symbols ship with `viewBox="0 -960 960 960"` — a negative Y origin that Android `` has no equivalent for — and the `` is what compensates. Removing it renders an empty icon. Then swap the icon at the call site: Icon(painterResource(R.drawable.ic_shelves), contentDescription = "Bookcases & shelves") Keep the existing contentDescription text. `Icon` applies its own tint over a Painter exactly as it does over an ImageVector, so the icon still picks up the theme colour — do NOT hardcode a colour at the call site. You will need imports for `androidx.compose.ui.res.painterResource` and `org.modg.bookshelf.R`, and the `Icons.Outlined.Warehouse` import becomes unused — remove it. ## Verify before you report (all in the FOREGROUND) ./tasks/gw assembleDebug ./tasks/gw testDebugUnitTest ./tasks/gw recordPaparazziDebug git status --porcelain - assembleDebug and testDebugUnitTest must exit 0. The test count is 138 today and must not go DOWN. - Grep your build output for the string `always 'false'`. That Kotlin warning class silently blanked every book cover in this app for months by making a `when` branch dead code that still compiled. Zero hits on files you touched. - `git status --porcelain` must show ONLY the files you own. If it shows others, you have broken the boundary — report it, do not revert someone else's work. - Do not commit. The orchestrator commits after verifying. ## Report Finish with a plain report: what you changed per task, the exact exit codes and test counts, whether the one-bookcase Paparazzi render actually proves task 1, and anything you could NOT do. Do not claim success you did not verify — several previous workers on this project over-claimed and were caught.