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`. Another 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:
      ui/components/ShelfPickerSheet.kt          (new file, create it)
      ui/scan/ScanScreen.kt
      ui/scan/ScanViewModel.kt
      ui/detail/DetailScreen.kt
      ui/detail/DetailViewModel.kt
      data/prefs/SettingsStore.kt
      app/app/src/test/**                         (tests you add)
  (paths relative to app/app/src/main/java/org/modg/bookshelf/)
  Touch NOTHING else. Specifically forbidden: any build file
  (`app/build.gradle.kts`, `gradle/libs.versions.toml`), `data/metadata/**`
  (the ORCHESTRATOR is editing that right now, in this same working tree),
  `ui/locations/**`, `ui/library/**`, `ui/nav/**`, `ui/settings/**`,
  `ui/setup/**`, `AppContainer.kt`, and every file in `ui/components/` EXCEPT the
  new `ShelfPickerSheet.kt` you create.
- Do not change any public composable signature. `ui/nav/BookshelfNavHost.kt`
  calls these screens and you may not edit it.
- `AppContainer.settingsStore` is already a public val — you do NOT need to change
  AppContainer to reach it.

## Background: what the user actually reported
They are shelving books a box at a time, scanning a run of books that all belong
on the SAME shelf. Two complaints:
1. The shelf dropdown lists every bookcase×shelf pair in one flat menu. With more
   than a couple of bookcases that is unusable.
2. Re-picking the same shelf for every book in the box is tedious.

## Task 1 — a grouped shelf picker, replacing the flat dropdown
There are currently TWO near-duplicate flat pickers:
  - `ShelfPicker` in `ScanScreen.kt` (~line 477)
  - the location picker inside `DetailScreen.kt` (~line 273-310)
Both build a `DropdownMenu` containing "Not shelved" followed by one flat item per
(bookcase, shelf) pair, labelled "Bookcase • Shelf".

Replace BOTH with ONE new shared composable in `ui/components/ShelfPickerSheet.kt`,
presented as a Material 3 `ModalBottomSheet` rather than a dropdown. Contents, top
to bottom:
  a. A "Recent" section — see task 2. Omit the whole section when there is no
     remembered shelf, or when the remembered shelf no longer exists.
  b. "Not shelved" as an always-available choice.
  c. One section per bookcase, in `position` order. The bookcase name is a section
     header (non-selectable — a bookcase is not a location a book can sit in;
     only shelves are). Its shelves are listed under it in `position` order,
     labelled with just the shelf label, since the header already gives the
     bookcase. Make the currently-selected shelf visually distinct.
  d. A bookcase with no shelves shows its header and an inline "No shelves yet"
     hint, so an empty bookcase does not look like a rendering bug.
The sheet must scroll. Do NOT add a search field — the user explicitly deferred
that; we will add it later if the grouping alone proves insufficient.

Design language is in SPEC.md ("feels like books") and the existing components in
`ui/components/` are your reference for type, spacing and the gold hairline rules.
Reuse `GoldDivider` for section separation rather than inventing a new rule.

## Task 2 — remember the most recently used shelf
Add a `LAST_SHELF_ID` key to `SettingsStore` (a nullable String preference, with a
`Flow<String?>` reader, a setter, and a way to clear it), following the exact
shape of the keys already there. Clear it in `clearAuth()` alongside the rest —
signing out of a shared library should not leak the other account's shelf.

Write it whenever a book's shelf is set to a non-null value:
  - `ScanViewModel` — on save, both from a metadata hit and from manual entry.
  - `DetailViewModel` — when the user changes a book's location.
Setting a book to "Not shelved" must NOT overwrite the remembered shelf; it isn't
a shelf, and clobbering the memory with it would defeat the whole feature.

Surface it as the "Recent" section at the top of the picker, labelled with the
full "Bookcase • Shelf" text (the section has no bookcase header to lean on).

**Do NOT pre-select it.** The user considered and explicitly rejected
pre-selection: the risk of silently mis-shelving a book, when the user forgets to
change it, outweighs saving one tap. A new scan still starts with no shelf chosen;
the remembered shelf is one tap away at the top of the sheet, and that is all.

Note `ScanViewModel._selectedShelfId` already survives across saves within a single
scanning session (it is deliberately not reset in `recordSave`). Keep that. What
you are adding is persistence ACROSS sessions and screens.

## Task 3 — three small fixes in ScanScreen.kt
3a. In `SearchingSheet` (~line 268), delete the third Text, the one reading
    "Barcode read — you can lower the book." The user says "Searching…" plus the
    ISBN already carries it. Update the composable's KDoc, which currently
    justifies that line — do not leave a comment explaining code that is gone.

3b. `LookupFailedSheet` (~line 435) takes a `reason` parameter and never renders
    it. `ScanSheetState.LookupFailed(isbn13, reason)` already carries a diagnostic
    string, and `MetadataRepository` already builds it as e.g.
    "open library: network error; google books: http 429". It is currently dead —
    it reaches the UI and is dropped on the floor.
    Render it, below the existing explanatory paragraph, in
    `MaterialTheme.typography.bodySmall` and `onSurfaceVariant`. This is the only
    diagnostic channel we have from a real phone, so it must actually appear.
    Keep the existing headline and paragraph as they are — the reason is
    supplementary detail, not a replacement for plain-language copy.
    NOTE: the orchestrator is concurrently making those reason strings more
    specific. Do not depend on their exact wording — render whatever you are
    given, and do not parse, match on, or reformat the string.

3c. `ManualIsbnDialog` (~line 512) opens with an unfocused text field. Auto-focus
    it and raise the keyboard, via `FocusRequester` +
    `LaunchedEffect(Unit) { focusRequester.requestFocus() }`. Guard the call so it
    cannot throw if the node is not attached yet.

## 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. Add real tests for the logic you introduce — at minimum the
  SettingsStore round-trip, that "Not shelved" does not overwrite the memory, and
  that a remembered shelf which no longer exists is not offered. Assertion-free
  tests are explicitly forbidden by SPEC's quality bar.
- Add a Paparazzi snapshot of the new picker sheet with at least two bookcases,
  one of them empty, and a recent shelf present — that is the state the whole
  change exists for, and no one has ever trusted a worker's word on how this app
  looks.
- 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` will show files the orchestrator and the other worker
  are editing (`data/metadata/**`, `ui/locations/**`, `ui/library/**`,
  `res/drawable/**`). That is EXPECTED. Confirm only that no file outside your
  own list was changed BY YOU. Never revert or "fix" someone else's work.
- Do not commit. The orchestrator commits after verifying.

## Report
Finish with a plain report: what you changed per task, exact exit codes and test
counts, what the new Paparazzi PNG shows, 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.
