Wipe decrypted-plaintext caches on cabinet teardown and preview switch
The module-level `decryptCache` in FilePreview and `readmeCache` in
DirectoryReadme hold decrypted document bytes keyed by AT-URI. Both
exist for Suspense stability across unmount/remount — correct, but
also the reason eviction needed explicit wiring. It never got it.
Concrete leaks the fix closes:
- Open preview for file A, open preview for file B without closing A
first: cache[A] lingers. Now `handlePreview` evicts the prior URI
before setting the new one.
- Navigate out of FileView: the active preview's cache entry lingers
across the unmount. New unmount effect evicts the current URI.
- Logout / session transition without a full page reload: auth.ts
nulls opakeInstance and OpakeProvider.wipeState() runs, but the
WASM-only wipe can't reach JS heap maps. New user's session sees
the previous user's plaintext under document URIs still in memory
(typed into devtools or a router prefetch with the wrong DID).
Fix: add `clearPreviewCache()` (matching `evictAllReadmeCaches` which
already existed unused). Call both from a CabinetLayout unmount
effect — that's the auth-gate boundary; a route change out of
`/cabinet/*` is the same event as provider unmount and the cleanest
hook point for "all decrypted material goes away now."
Does not address the related Rust-side issue that `HeldTree`'s
cabinet X25519 private key doesn't zeroize on drop — that's a
separate commit (requires RedactedDebug on the cabinet key type).