Commit Graph

87 Commits

Author SHA1 Message Date
byGalax d10840e0b2 feat(P5B.T6): composer Spielen button + bubble dispatch + game-picker
Wires mini-game entry points: createGamePayload helper in conversationFeatures.ts (GamePayload import added), game state/handler/picker-dialog/modal-mount/open-event-listener in ConversationPage.tsx (DM-only guard uses conversation.members[].userId camelCase as confirmed), and parsed.kind==='game' bubble branch in MessageBubble.tsx dispatching chatapp:open-game CustomEvent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:58:10 +02:00
byGalax cd59ee30d6 feat(P5B.T5): GameModal — picks board + turn/winner status 2026-05-16 21:53:42 +02:00
byGalax e56e22631b feat(P5B.T4): TicTacToeBoard + ConnectFourBoard render-only components 2026-05-16 21:51:06 +02:00
byGalax ecbd11e369 feat(P5A.T5): composer Watch-Together button + bubble dispatch 2026-05-16 21:30:22 +02:00
byGalax 7d5f3b37cd feat(P5A.T4): WatchTogetherModal — YouTube IFrame + drift reconcile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:26:59 +02:00
byGalax 890d5dc2b7 feat(P4C.T4): SoundboardSettings — sync hook mount + per-row badges + remote-delete
Mount useSoundboardSync in SoundboardManagerDialog, thread badges map through
SoundboardCategoryGroup/SoundboardRow, render a cloud-state glyph badge inline
with each row's size/mime metadata, and wrap handleDelete to attempt a
best-effort remote delete via deleteRemoteSound before the local deleteSound call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:02:26 +02:00
byGalax 18197a9f95 feat(P4B.T7): MessageBubble renders whiteboard kind with Öffnen button
Adds a whiteboard branch to the MessageBubble ternary chain that renders
a card with an icon, label, and Öffnen button dispatching the
chatapp:open-whiteboard CustomEvent. ConversationPage now listens for
that event via a useEffect and calls setOpenWhiteboardId to open the
modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 20:38:03 +02:00
byGalax 5e59ee22f6 feat(P4B.T5): WhiteboardModal — fullscreen container + toolbar + clear-confirm 2026-05-16 20:29:24 +02:00
byGalax be6f5b9c6f feat(P4B.T4): WhiteboardCanvas — pen/eraser drawing engine 2026-05-16 20:27:09 +02:00
byGalax d623a59c87 feat(P4A.T3): annotator toolbar — tool/color/width/undo/redo controls 2026-05-16 19:46:00 +02:00
byGalax 2a2e334f51 feat(P4A.T2): annotator drawing engine — pen/arrow/rect/circle/text/highlighter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 19:44:04 +02:00
byGalax 9228cc635c feat(P4A.T1): ImageAnnotator modal skeleton with canvas mount + op-stack types 2026-05-16 19:41:36 +02:00
byGalax c5851195c7 fix(call): strip-toggle icon no longer touches its frame edges
Shrink the StripToggleIcon SVG from 18x18 to 14x14 px so the 2-people
icon sits centred with ~11 px of breathing room inside its 36x36 button,
matching the h-4/w-4 small-icon convention used elsewhere in InCallPanel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 19:21:26 +02:00
byGalax 2583613d7f feat(P3.T7): RemoteRevokedScreen overlay for own-device revocation 2026-05-16 19:15:15 +02:00
byGalax 04d9909e92 feat(P3.T6): Settings 'Geräte' tab with revoke + this-device badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 19:13:31 +02:00
byGalax ef1f9f45d8 fix(desktop): swap GIF provider from Tenor (closed Jan 2026) to GIPHY
Google closed Tenor v2 to new API clients in Jan 2026, so the only
people who could use the picker were those with a pre-existing Google
Cloud Console key. Swapped to GIPHY's Developer API (still open, free
keys at https://developers.giphy.com/dashboard/).

- Env var renamed VITE_TENOR_API_KEY → VITE_GIPHY_API_KEY
- Endpoint, response mapping, error sentinel updated
- File still named tenor.ts for import-path stability — renaming
  later if it bothers anyone
- Public GifResult interface unchanged so the picker UI didn't need
  edits beyond the error-message switch
2026-05-16 18:25:18 +02:00
byGalax 95156a65eb feat(desktop): view-once image attachments (sender toggle + recipient lightbox + tombstone)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:18:39 +02:00
byGalax 7726498218 feat(desktop): GIF picker popover (Tenor + trending/search/recent) 2026-05-16 18:05:45 +02:00
byGalax ef98efb938 feat(desktop): mention notifications via realtime + osNotify 2026-05-16 17:59:57 +02:00
byGalax 3dbeabd268 feat(desktop): pin/unpin from message context menu + side panel 2026-05-16 17:51:01 +02:00
byGalax 6bff163f09 feat(desktop): pinned-messages side panel 2026-05-16 17:47:39 +02:00
byGalax ddeedeb71d feat(desktop): pinned-messages pill in conv header 2026-05-16 17:45:43 +02:00
byGalax 9de2c368bf feat(desktop): optional wipe-on-close (Settings -> Sicherheit) 2026-05-16 17:17:59 +02:00
byGalax 8ad212291a feat(desktop): apply friend nicknames in header / bubble / mentions / call tile 2026-05-16 17:06:18 +02:00
byGalax 898b7469bb feat(desktop): set-nickname dialog + right-click trigger in friends list 2026-05-16 17:01:38 +02:00
byGalax 502c6af1b8 feat(desktop): EmptyState primitive component 2026-05-16 16:49:16 +02:00
byGalax 5c05afb009 feat(desktop): redesign Settings + fix unreadable bubble text
Two UI fixes:

1. MessageBubble: 'Nachricht nicht lesbar' was rendered with
   text-fg-muted on the blue 'mine' bubble — invisible. Now uses
   text-accent-fg/80 on mine, text-fg-muted on peer (still
   ≥4.5:1 contrast in both modes).

2. SettingsPage: redesigned from a long single-column scroll into a
   sticky-sidebar + content layout (lg+) with:
   - 9 anchor-linked sections with icons in the sidebar
   - IntersectionObserver highlights the active section
   - Each section has a description subtitle for context
   - Voice (the densest section) is now sub-grouped into Audio-Gerät /
     Qualität / PTT / Hotkeys / E2EE via SubSection cards
   - Notifications consolidates message-sound + ringtone
   - Danger-toned account section visually separated
   - Mobile fallback is the original single-column scroll
2026-05-16 01:08:28 +02:00
byGalax d39a0fb6dc fix: stop reset_user_key from wiping conv-key bundles + auto-rotate stuck convs
Root cause of "alle Nachrichten verschlüsselt + kann nicht schreiben":
uploadUserKeyBlob (called by setupNewUserIdentity, changePin and
regenerateRecoveryCode) routed through reset_user_key, which DELETES
every conversation_keys row addressed to the user or one of their
devices. So setting a PIN destroyed every legacy bundle BEFORE the
migration could re-wrap them. The user ended up with user_keys set,
zero un-migrated bundles, no decryption, no send.

Fixes shipped:

  * supabase/migrations/20260516000001_user_key_rpcs_v2.sql
    - upsert_user_key: same UPSERT, NO delete. Used everywhere except
      "Identität zurücksetzen" (which keeps reset_user_key on purpose).
    - rotate_conv_key: bumps active_key_version atomically and inserts
      a fresh batch of bundles (per-user + per-device fallback).
  * shared/auth/userKey.ts: uploadUserKeyBlob now calls upsert_user_key.
  * shared/chat/convKeys.ts: new rotateConvKey() that wraps the fresh
    conv-key for every member's user_keys (preferred) and falls back to
    each member's per-device public_key for peers still on 0.17.x.
  * shared/chat/convKeys.ts: getOrCreateConvKey auto-triggers rotate
    when the user has no recipient_user_id row at the active version
    but rows exist (the deadlock case). Existing outbox retries drain
    on their own once the rotate completes — no manual button.
  * desktop/MessageBubble.tsx: "...cannot decrypt" is now a softer,
    German "Nachricht nicht lesbar" so users don't think the app
    crashed when historical messages can't be unwrapped.
2026-05-16 00:48:28 +02:00
byGalax 5367544b59 feat(desktop): diagnostic + manual retry for legacy key migration
The 0.18.1 fix relied on an existing-device + present-stronghold-key match.
That fails for users who:
  - had multiple device registrations and only retain the latest device's
    private key in the local vault
  - had a vault wipe / fresh OS install at some point
  - have device rows that vanished server-side but keys still locally

Migration now scans conversation_keys for distinct un-migrated
recipient_device_ids visible to the user (RLS-filtered) and probes the
stronghold for each, regardless of whether the server still lists that
device. Result struct surfaces attempted/migrated/noKey/decryptFail/rpcFail
counters; SecurityCenter shows them via a new "Migration erneut ausführen"
button so users can self-diagnose without DevTools.

Also adds [crypto-migration] console.info breadcrumbs at every decision
point so a single F12 shows what happened.
2026-05-16 00:25:48 +02:00
byGalax 6caa674c19 fix(shared): legacy conv-key migration query used .eq(null) instead of .is(null)
PostgREST translates .eq('col', null) to `col = NULL` which is always false
in SQL. The migration silently returned zero rows -> setupNewUserIdentity
fired but re-wrapped nothing -> users could set a PIN but every send threw
'Awaiting key'. Switching to .is('col', null) emits `col IS NULL` and the
migration finally finds its work.

Also makes the migration trigger idempotent and re-fires it on:
  - every successful loadOrUnlockUserKey
  - AuthContext startup when the user-key is already cached
so users stuck on 0.18.0 auto-recover the moment they install 0.18.1.

PinInput: focused + active-slot now show a brand-coloured ring, glow, and
a blinking caret so users see where the next keystroke lands.
2026-05-16 00:15:15 +02:00
byGalax 0e05a2cd85 refactor(desktop): replace VoiceChannelRail with CallPreviewPanel
Drops the always-on 'Sprach-Channel' banner. The preview panel renders
only when peers are in the active call (1:1 and group identical).
Calls are still started via the topbar phone icon.
2026-05-15 23:59:17 +02:00
byGalax b55ccf899f feat(desktop): add CallPreviewPanel — Discord-DM-style join surface 2026-05-15 23:56:02 +02:00
byGalax b789f4b10d feat(desktop): Settings security center (PIN change / recovery / reset)
Drops the manual backup-string flow; replaces it with PIN change,
recovery-code regeneration, and identity reset (all sealed via the new
user_keys table).
2026-05-15 23:09:05 +02:00
byGalax 02e1af4517 feat(desktop): DevicePage routes to UserKeySetup or UserKeyUnlock 2026-05-15 23:03:02 +02:00
byGalax e292df82f0 feat(desktop): UserKeyUnlock screen (PIN entry + recovery fallback) 2026-05-15 23:00:18 +02:00
byGalax b3804d805b feat(desktop): UserKeySetup screen (PIN + optional recovery code) 2026-05-15 22:57:57 +02:00
byGalax 02c4bb1c9b feat(desktop): shared PinInput component 2026-05-15 22:54:58 +02:00
byGalax 20216b37c6 refactor(desktop): AuthContext exposes userKeyState instead of device record
Replaces the per-device DeviceRecord lookup with a per-user discriminated
union (loading | needs-setup | needs-unlock | unlocked). Heartbeat block
deleted (telemetry no longer device-bound); webPush keyed by install-id.
2026-05-15 22:52:41 +02:00
byGalax b89a7e2617 refactor: swap device-id contexts for user-id contexts at call sites
Renames DecryptParams.ownDeviceId to ownUserId so decryptMessages actually
looks up bundles by user. Sweeps remaining OwnDeviceCtx and
loadDevicePrivateKey consumers in the desktop app to use cachedUserKey
from userIdentity. Files scheduled for deletion in later tasks
(BackupExportDialog, DeviceRestore, BackupRestoreDialog, BackupPromptBanner,
deviceBackup, DeviceRegistration) are left untouched.
2026-05-15 22:41:44 +02:00
byGalax aa609389fa feat(chat): render Media/Files + Group Info drawers inline, not overlaid
Both right-hand panels used absolute inset-y-0 right-0 and floated on
top of the conversation, hiding the messages directly underneath the
panel and looking unlike Discord's actual layout. Restructure:

* MediaFilesDrawer: drop absolute/z-index/shadow chrome, become a
  static flex column (w-[380px] shrink-0) with a left border. Internal
  layout unchanged.
* GroupInfoPanel: same treatment (w-[320px] shrink-0). Dropped the
  backdrop-blur and slide-up animation that only made sense as a modal.
* ConversationPage: wrap the chat content (voice rail, in-call panel,
  messages list, drag-overlay, input form) in a new
  `flex min-w-0 flex-1 flex-col` chat-column, and make that column a
  sibling of the drawers inside a new `flex flex-1 flex-row` row. The
  conversation header + search bar stay full-width above the row.

Result: opening a drawer narrows the chat column instead of covering
it, matching Discord's behaviour. The chat-column wrapper also carries
the `relative` anchor previously held by the outer wrapper so the
drag-and-drop overlay positions correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:21:05 +02:00
byGalax cb3cbd8827 feat(chat): show caller's name on call event pills in groups
In group conversations the "Outgoing/Incoming/Missed call" system pill
gave no clue WHO triggered the event — fine in a 1:1 where the only two
players are obvious, useless in a group with three+ members. Discord
puts the caller's name in the pill; mirror that.

CallEventRow now takes a senderDisplayName prop (plumbed through from
MessageBubble) and switches non-own labels to the name-aware variants:

* ended  + !mine + name → "{name} hat einen Anruf gestartet"
* missed + !mine + name → "Verpasster Anruf von {name}"
* declined + !mine + name → "Anruf von {name} abgelehnt"

Own events (mine) stay generic ("Outgoing call" / "No answer") since
the user already knows they were the initiator. Fallback path without
a name keeps the previous generic labels so nothing regresses if the
sender is unresolvable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:20:21 +02:00
byGalax 0d65a134fd feat(settings): click own avatar in profile preview to view fullscreen
Lightbox was previously a file-private component inside AttachmentImage
(used for enlarging chat image attachments). Extracted to a standalone
components/Lightbox.tsx so other surfaces can reuse the same dialog
without duplicating Esc/backdrop/body-overflow plumbing.

In SettingsPage's profile live-preview, the round avatar overlapping the
banner is now wrapped in a transparent button that opens the Lightbox
with the full-resolution avatar URL on click. Cursor switches to
zoom-in. Disabled when the user only has the initial-letter placeholder
(nothing meaningful to enlarge). Native button chrome (border, padding,
button-face background) is reset to keep the avatar circle's appearance
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:58:22 +02:00
byGalax 8baac2fd1e fix(call): tile sizing polish + cinema-mode chrome suppression + spec/plan docs
Equal-grid cells no longer set aspect-video — on wide chat panels this
forced cell height = width × 9/16 (~400px on a 700px panel) which pushed
the row past the section's max-h and ate the controls bar below. n>=2
cells now fill grid tracks normally via auto-rows-fr; the solo case
(n=1) keeps a 16:9 silhouette via aspect-video + max-w + justify-self-
center so a single-user-alone-calling view doesn't stretch into a
full-width slab. Same change applied to the fullscreen-grid path plus
+16px bottom-padding (pb-28) so audio-only avatars' name chip clears
the floating controls bar.

Docked stage strip thumbs (focus + bento) switch from aspect-video
shrink-0 to flex-1 min-w-[200px] max-w-[460px] so 2-3 thumbs share the
row width evenly under the share above, instead of clinging to the left
edge with dead space to the right. Fullscreen-cinema strip keeps the
small aspect-video thumbs the user explicitly approved.

ScreenShareViewer gains a hideFullscreenToggle prop; cinema mode passes
it via a new `cinema` prop on TileRender so the in-share fullscreen icon
doesn't visually collide with FullscreenCall's strip-hidden toggle at
the same top-right corner.

docs/superpowers/specs + plans for the Discord-style tile handling
workstream are committed alongside the implementation that completed it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:31:26 +02:00
byGalax 05870ef8fa feat(call): split resolution/fps in share picker + restore window state after fullscreen
ScreenSharePickerModal now exposes Auflösung (Auto · 720p · 1080p · 1440p
· 4K) and FPS (30 · 60) as separate pill rows instead of bundled quality
presets — users can pick "1440p · 30 fps" or "4K · 30 fps" which the old
preset list didn't surface. The underlying screenShareSettings framerateOverride
slot already existed; the modal just stopped resetting it to null on every
start and now plumbs the chosen FPS through to startScreenShare.

Cinema-mode fullscreen on Windows had two defects:

1. Maximized → fullscreen left the taskbar drawn on top of the window
   because DWM kept the maximized work-area constraints. We now unmaximize
   first so DWM recomposes cleanly and setFullScreen actually covers the
   whole monitor including the taskbar strip.

2. Esc out of cinema came back as a small floating window even when the
   user had been maximized before clicking the Vollbild button — the
   unmaximize from (1) was never undone. We now memo the pre-fullscreen
   maximized flag per window-id and call win.maximize() once the
   leave-full-screen event has fired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:31:10 +02:00
byGalax 91eedc0e8e fix(call): aspect-video for fullscreen grid+strip, switch section sizing to stageLayout 2026-05-12 19:03:23 +02:00
byGalax e56533918e feat(call): multi-share bento layout in stage + fullscreen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:54:23 +02:00
byGalax c87d4e82d3 feat(call): doubleclick on focused tile clears pin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:50:29 +02:00
byGalax 187f8dc95a feat(call): left-click toggles pin, drop manual focus mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:47:10 +02:00
byGalax f612c1bb50 feat(call): introduce StageLayout discriminated union
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:43:55 +02:00
byGalax a9d5e2d430 feat(call): uniform 16:9 grid cells, drop grid-rows constraint 2026-05-12 18:40:54 +02:00