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 <task>` — 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 `<vector>` has no
equivalent for — and the `<group android:translateY="960">` is what compensates.
Removing it renders an empty icon.

    <?xml version="1.0" encoding="utf-8"?>
    <!-- Material Symbols "shelves" (Apache 2.0). Source viewBox is
         "0 -960 960 960"; Android has no viewport origin, so the group
         translate is load-bearing. Do not flatten it. -->
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="960"
        android:viewportHeight="960">
        <group android:translateY="960">
            <path
                android:fillColor="#FF000000"
                android:pathData="M120-40v-880h80v80h560v-80h80v880h-80v-80H200v80h-80Zm80-480h80v-160h240v160h240v-240H200v240Zm0 320h240v-160h240v160h80v-240H200v240Zm160-320h80v-80h-80v80Zm160 320h80v-80h-80v80Z" />
        </group>
    </vector>

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.
