Feature: Pending registration screen As a logged-in user who has not yet verified their account I want /register/pending to tell me where the verification link went, let me log out or cancel my registration, and route me to the right destination no matter which of those calls succeeds or fails So that I always know what to do next while waiting for the emailed link, and a device that verified elsewhere or a server error never strands me on the wrong screen Background: Given /register/pending is a "session" route (meta.auth "session"), rendered as PendingPage inside AuthShell, per docs/design/routes.md; "session" only requires a logged-in account, so both a verified and an unverified account can land on it without being redirected away by the router guard And PendingPage only calls DELETE /v1/auth/register and POST /v1/auth/logout And RegisterView.vue, on a 200 from POST /v1/auth/register, writes the submitted email to sessionStorage under the key "renewcms:pendingEmail" before navigating to /register/pending; this is the only change this slice makes to RegisterView.vue And on mount, PendingPage reads "renewcms:pendingEmail" from sessionStorage once: if present, it renders the Default state with that email interpolated into the ResultPanel text; if absent (for example, the user arrived by logging in as an unverified account rather than by just registering), it renders the Email unknown state with the generic text And in both cases the ResultPanel has tone "success", icon "i-lucide-mail-check", title "Check your email", and two secondary actions, "Log out" and "Cancel registration" (color "neutral", variant "outline"); the note line ("Opened it on another device? That works too; you'll log in there first. Can't find it? Check your spam folder. If the link has expired, cancel your registration and start again.") is rendered through ResultPanel's default slot as a second paragraph below the description, per components.md's "Slot default for extras" And "Log out" calls POST /v1/auth/logout, which per loginHandler.Logout always responds 200 and clears the jwt cookie; on its response, PendingPage calls session.clear() and navigates to /login, disabling both actions while the call is in flight And "Cancel registration" opens ConfirmDialog (built by this slice; components.md documents it but no file exists yet) via useOverlay().create(ConfirmDialog).open(), with title "Cancel your registration?", description "Your account will be deleted. You can register again with the same or a different email.", confirmLabel "Cancel registration", cancelLabel "Keep my account" (components.md's "Other uses" line pairs title/description/confirmLabel/cancelLabel in that order, so ConfirmDialog exposes a cancelLabel prop alongside confirmLabel even though the compact props line only names confirmLabel), pendingLabel "Cancelling…", and tone "danger"; its action prop performs the DELETE /v1/auth/register call And while the dialog's action is in flight (Cancelling), both buttons are disabled, the confirm button reads "Cancelling…", aria-busy is set, and the dialog cannot be dismissed, per ConfirmDialog's general contract in components.md And PendingPage's DELETE /v1/auth/register call passes { onForbidden: 'passthrough' } (a new RequestOptions field on useApi, added by this slice, mirroring the existing onUnauthorized field: type 'redirect' | 'passthrough', default 'redirect'; when 'passthrough' is given, the 403 branch in useApi's request() skips the "if session.session?.verified === false, push to /register/pending" step entirely and always throws ApiError(403)) so PendingPage's own 403 handling below runs instead of the global handler bouncing it back to /register/pending And on a 200 from DELETE /v1/auth/register, the action resolves, the dialog closes, the app navigates to /register, and a toast (color "success", icon "i-lucide-circle-check", title "Registration cancelled", description "You can create the owner account again.") is shown And on a 403 from DELETE /v1/auth/register (user.ErrAlreadyVerified, since the account was verified on another device while this tab still holds a stale unverified session), the action first awaits session.retry() so the session store's cached verified flag is refreshed to true — without this, the router guard's stale local state would immediately bounce a navigation to /home straight back to /register/pending — then the dialog closes, PendingPage replaces its panel with a ResultPanel (tone "success", icon "i-lucide-shield-check", title "You're verified", text "Taking you to your dashboard…", no actions) and shows a toast (color "info", icon "i-lucide-info", title "Your account is already verified", description "There's nothing to cancel.") over it, exactly as the design board draws this state, then navigates to /home after the same 800 ms Verify's Success uses And on a 500 response or a request that never reaches the server, the action rejects, the dialog stays open, and it shows the failure inline (per ConfirmDialog's general contract: "the action runs inside the dialog, so errors show there instead of a toast"), re-enabling both buttons so the user can retry or choose "Keep my account" And a 401 from either call is handled entirely by useApi's existing global 401 handling (redirect to /login?redirect=...&reason=expired); PendingPage adds no special case for it Scenario: Default — the email is known from a just-completed registration Given sessionStorage holds "renewcms:pendingEmail" set to "marta@renew.example" When the user navigates to /register/pending Then a ResultPanel is shown with tone "success" and icon "i-lucide-mail-check" And its title is "Check your email" And its text is "We sent a verification link to marta@renew.example. It expires in 2 hours." And its note reads "Opened it on another device? That works too; you'll log in there first. Can't find it? Check your spam folder. If the link has expired, cancel your registration and start again." And its actions are "Log out" and "Cancel registration", both secondary Scenario: Email unknown — arrived by logging in as an unverified account Given sessionStorage holds no "renewcms:pendingEmail" entry When the user navigates to /register/pending Then a ResultPanel is shown with tone "success" and icon "i-lucide-mail-check" And its title is "Check your email" And its text is "We sent a verification link to the email you registered with. It expires in 2 hours." And its note is the same as the Default state's note And its actions are "Log out" and "Cancel registration", both secondary Scenario: Logging out Given the user is on /register/pending When the user activates "Log out" Then both actions are disabled while POST /v1/auth/logout is in flight When the response resolves 200 Then the session store is cleared And the app navigates to /login Scenario: Confirm cancel — the destructive dialog opens Given the user is on /register/pending When the user activates "Cancel registration" Then a ConfirmDialog opens with title "Cancel your registration?", description "Your account will be deleted. You can register again with the same or a different email.", a "Keep my account" cancel button, a "Cancel registration" confirm button in tone "danger", role "alertdialog", and focus starting on "Keep my account" And the underlying ResultPanel is still shown behind it, unchanged Scenario: Cancelling — the DELETE is in flight and the dialog can't be dismissed Given the ConfirmDialog from "Confirm cancel" is open When the user activates "Cancel registration" inside the dialog Then both dialog buttons are disabled, the confirm button reads "Cancelling…", aria-busy is set on the dialog, and it cannot be dismissed by Escape or an overlay click And DELETE /v1/auth/register is sent with { onForbidden: 'passthrough' } Scenario: Keep my account — the dialog is dismissed without deleting anything Given the ConfirmDialog from "Confirm cancel" is open When the user activates "Keep my account" Then the dialog closes, emitting close(false) And DELETE /v1/auth/register is never called And the ResultPanel from before the dialog opened is still shown, unchanged Scenario: Cancelled — 200 navigates to /register with a toast Given the ConfirmDialog's action is in flight after the user confirmed When DELETE /v1/auth/register resolves 200 Then the dialog closes And the app navigates to /register And a toast is shown with color "success", icon "i-lucide-circle-check", title "Registration cancelled", description "You can create the owner account again." Scenario: Already verified — 403 shows the verified panel and toast, then navigates to /home Given the ConfirmDialog's action is in flight after the user confirmed And the session store currently holds { verified: false } When DELETE /v1/auth/register resolves 403 with the apperr.AlreadyVerifiedErrorMessage body Then session.retry() is awaited before any navigation happens And once it resolves the session store holds { verified: true } And the dialog closes And a ResultPanel is shown with tone "success", icon "i-lucide-shield-check", title "You're verified", text "Taking you to your dashboard…", and no actions And a toast is shown over it with color "info", icon "i-lucide-info", title "Your account is already verified", description "There's nothing to cancel." And after 800 ms the app navigates to /home Scenario Outline: Cancel failed — the dialog stays open and shows the failure inline Given the ConfirmDialog's action is in flight after the user confirmed When DELETE /v1/auth/register Then the dialog does not close And the failure is shown inline in the dialog, not as a toast And both dialog buttons are re-enabled so the user can retry "Cancel registration" or choose "Keep my account" Examples: | failure | | fails with a 500 response | | never reaches the server |