Adds sticky `lastSelectedSpaceId` to useSpaceStore. Updated on every
setCurrentSpace(non-null) and on loadSpaceDetail success; NOT cleared by
setCurrentSpace(null), so the @me URL effect in AppLayout no longer wipes
the memory. Cleared only when the remembered space is actually removed
(deleteSpace / leaveSpace / removeSpace / removeInstanceSpaces / reset).
Ephemeral — URL drives initial state on reload.
MobileBottomNav.handleTab('spaces') and MobileSpacesScreen's initial
useState now resolve via `currentSpaceId ?? lastSelectedSpaceId`. The
prior Object.keys(lastChannelPerSpace)[0] fallback was wrong twice over:
first-inserted ordering locked the tab to whichever space the user
opened first in their session, and any approach reading currentSpaceId
alone breaks after a /channels/@me detour.
Also fixes a pre-existing render loop in MobileSpacesScreen's
currentSpaceId sync effect — `selectedSpaceId` removed from deps,
functional setState makes the update idempotent.
Spec: docs/systems/mobile-ui.md updates Tab Tap Behavior, MobileScreenHeader,
and LocalStorage Persistence sections.
MobileInstancePanel gains Registration and Federation entries (parity with
desktop's six sub-tabs). MobileShell registers the matching screenMap
wrappers; the Federation entry surfaces the live approval-count badge via
a new uiStore slot and a wrapper that forwards FederationPanel's
onApprovalCountChange.
MobileShell's deep-link reconstruction now reacts to post-mount pathname
changes (was [] / mount-only) with an idempotency guard that skips the
push when the topmost stack entry already represents the new URL —
prevents the pushMobileScreen → history.pushState → location-effect
double-push.
RegistrationPanel's four portaled modals (CreateInvite, EditInvite,
ReinstateInvite, Redemptions) now render through the shared <Modal>
with mobileStyle="fullscreen", portaled to document.body to escape the
parent settings dialog's backdrop-filter containing block. Desktop
appearance preserved (same maxWidth, same sticky action bar).