feat(messages): standalone Messages module + floating chat dock

Move messaging out of the notification center into its own left-nav module
(own page, own unread badge), so a reload returns to it and notifications stay
separate. Add a Messenger-style floating dock (bottom-right): pop a conversation
out from the page, keep chatting across navigation, minimise (rollup) or close.

- Messages is now a routed page; NotificationsPanel reverts to inbox-only and the
  nav badge no longer mixes in messages.
- Extract shared KeyGate + ChatThread (used by both the page and dock windows);
  e2ee exposes a shared unlock subscription so page and dock agree.
- ChatDock (always mounted for human users) owns the app-wide live-message
  subscription and per-device key restore. EN/HU/DE strings.
This commit is contained in:
npeter83 2026-06-25 22:34:24 +02:00
parent 2f0ca68e8a
commit 3fd71cd316
14 changed files with 518 additions and 347 deletions

View file

@ -35,6 +35,8 @@ import ConfigPanel from "./components/ConfigPanel";
import AdminUsers from "./components/AdminUsers";
import SettingsPanel from "./components/SettingsPanel";
import NotificationsPanel from "./components/NotificationsPanel";
import Messages from "./components/Messages";
import ChatDock from "./components/ChatDock";
import OnboardingWizard from "./components/OnboardingWizard";
import { shouldAutoOpenOnboarding } from "./lib/onboarding";
import Toaster from "./components/Toaster";
@ -545,7 +547,11 @@ export default function App() {
) : page === "playlists" ? (
<Playlists canWrite={meQuery.data!.can_write} />
) : page === "notifications" ? (
<NotificationsPanel filters={filters} setFilters={setFilters} setPage={setPage} onFocusChannel={focusChannel} meId={meQuery.data!.id} isDemo={meQuery.data!.is_demo} />
<NotificationsPanel filters={filters} setFilters={setFilters} setPage={setPage} onFocusChannel={focusChannel} />
) : page === "messages" && !meQuery.data!.is_demo ? (
<div className="p-4 max-w-3xl w-full mx-auto">
<Messages meId={meQuery.data!.id} />
</div>
) : page === "settings" ? (
<SettingsPanel
me={meQuery.data!}
@ -569,6 +575,7 @@ export default function App() {
(collapsed or expanded) without tracking its width. */}
<Toaster />
</div>
{meQuery.data && !meQuery.data.is_demo && <ChatDock meId={meQuery.data.id} />}
{wizardOpen && meQuery.data && !meQuery.data.is_demo && (
<OnboardingWizard me={meQuery.data} onClose={() => setWizardOpen(false)} />
)}