## 1. Turn ImportView into a modal body

- [x] 1.1 Change `ImportView`'s signature to take `eventId: string`, `eventName?: string`, `onClose: () => void` and `onImported: () => void`; delete the local `events` state, the `api.events()` effect, and the `eventId` state (design D1)
- [x] 1.2 Remove the "Import into" `<select>` and its wrapping `Card`; keep the file input and the column-help paragraph, and reword the help text so it names the destination event rather than "the event chosen above"
- [x] 1.3 Wrap the component in `Modal` with `size="wide"` and a title naming the destination (e.g. `Import guests into {eventName}`), following the `GuestForm` pattern (design D2)
- [x] 1.4 Drop the file input's `disabled={!eventId}` guard and the reason comment — the modal only mounts with an event, so the guard is now unreachable
- [x] 1.5 Call `onImported()` after a successful `commit()` while leaving the result card rendered, and add a Close action to the modal footer (design D3)
- [x] 1.6 Update the component docstring: it is no longer a screen, the destination arrives as a prop, and the `events` column is still ignored

## 2. Wire it into the guest list

- [x] 2.1 Add a `canImport: boolean` prop to `GuestsView` and an `importing` boolean state
- [x] 2.2 Add the Import CSV button beside Add guest — gated on `canImport`, `disabled` when no event is selected, with a `title` giving the reason, matching Add guest's treatment (design D1, D5)
- [x] 2.3 Render `<ImportView …>` when `importing && event`, passing the selected event's id and `title_en` from the already-loaded `events` list, `onClose` clearing the state and `onImported` calling `load()`
- [x] 2.4 Close the import modal when the event filter changes while it is open, so the destination can never drift from what the modal says
- [x] 2.5 Pass `canImport={permissions.includes("import_guests")}` from `web/app/admin/(protected)/guests/page.tsx`

## 3. Retire the standalone screen

- [x] 3.1 Replace the body of `web/app/admin/(protected)/import/page.tsx` with `redirect("/admin/guests")` from `next/navigation`, keeping the file inside the protected layout, and note why it is a redirect rather than a deletion (design D4)
- [x] 3.2 Remove the `/admin/import` entry from `AdminNav`'s `ITEMS` (design D6)

## 4. Documentation

- [x] 4.1 Rewrite `docs/USER-GUIDE.md` §2b so the steps are: Guests → choose the event → Import CSV → upload → read the report → Import; drop the "Admin → Import" and "choose the event to import into" steps
- [x] 4.2 Update the §1 table row and the appendix line that say the destination is "the event you picked on the import screen" to name the guest screen's event filter — the §1 row already pointed at the event's Guests tab and needed no change; the §2a paragraph and the appendix line did
- [x] 4.3 Check `CLAUDE.md` and the OpenSpec changes for any other reference to a standalone import screen and correct it — none found outside this change's own artifacts

## 5. Verify

- [x] 5.1 `make lint` clean (web `tsc --noEmit` catches the new prop threading and the removed `ImportView` props) — ruff, mypy, web typecheck and eslint all pass
- [x] 5.2 `make dev`, then walk it by hand: with no event selected the Import button is disabled and says why; with an event selected the modal names it, a mixed-validity CSV shows the per-row report, Discard writes nothing, and Import updates the table behind the modal — all confirmed; a 5-row file previewed 3 valid / 2 invalid / 1 merge, committed 2 imported + 1 merged + 2 skipped, and the list behind went 1 → 3 guests while the modal was still open
- [x] 5.3 Confirm `/admin/import` redirects to `/admin/guests` while signed in, and still hits the sign-in flow while signed out — 307 to `/admin/guests` signed in, 307 to `/admin/signin` signed out
- [x] 5.4 Sign in as a role without `import_guests` (or temporarily drop the permission) and confirm no Import control appears and the Import nav tab is gone for every role — a temporary viewer account saw only Dashboard and Guests in the nav and no Import control on the guest screen; the account was removed afterwards
- [x] 5.5 Confirm Escape, the backdrop and the close control all dismiss the modal without writing, and that reopening it starts from a clean state rather than the previous file's report — backdrop and ✕ confirmed by real interaction; Escape confirmed via the dialog's `cancel` event, because the browser harness could not deliver keystrokes to the page (no `keydown` reached it at all), so a physical Escape press is still worth one manual check
- [x] 5.6 Also confirmed: changing the event filter while the modal is open closes it rather than retargeting the destination (task 2.4)
