Compare commits

...

2 Commits

Author SHA1 Message Date
jared f054abfbd2 fix(notifications): click navigates to specific message, not inbox
CI / Build & Quality Checks (push) Successful in 10m30s
CI / Trigger Desktop Build (push) Successful in 7s
OS notifications now show the real message body ("user: text" instead
of "New inbox notification from user"), clicking jumps directly to the
room at the triggering event, and window.focus() brings the tab to
front. Reminder toasts also link to the specific event via eventId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:37:19 -04:00
jared 2b5c6fd606 perf(media): center-top focal point on cover-fit thumbnails (P5-6)
Adds objectPosition:'center top' to all cover-fit thumbnail surfaces so
portrait images show faces/subjects instead of the center-slice when
the 600px AttachmentBox height cap forces cropping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:33:36 -04:00
5 changed files with 19 additions and 12 deletions
+6 -4
View File
@@ -283,10 +283,11 @@ Themes:
**What:** Use `IntersectionObserver` to trigger media decryption and loading only when components approach the viewport.
**Approach:** Reduce initial memory footprint and improve timeline load times by deferring decryption of images/videos until they are visible.
### [ ] P5-6 · Context-Aware Thumbnail Previews
### [x] P5-6 · Context-Aware Thumbnail Previews ⚠️ UNTESTED
**What:** Enhance thumbnail rendering in the timeline for consistent, polished aesthetics.
**Approach:** Use CSS `object-fit: cover` with improved focal-point centering within `ThumbnailContent` to prevent media stretching or awkward aspect-ratio cropping.
**Approach:** Use CSS `object-fit: cover` with improved focal-point centering within `ThumbnailContent` to prevent media stretching or awkward aspect-ratio cropping.
**Fix Applied:** Added `objectPosition: 'center top'` to: (1) `media.css.ts``Image` component (timeline images), (2) video thumbnail inline style in `RenderMessageContent.tsx`, (3) `GalleryTile` `<img>` in `MediaGallery.tsx`. Full-size viewers retain `objectFit: 'contain'` — no change. `objectPosition: 'center top'` prevents face/subject cropping on tall portrait images capped at 600px by `AttachmentBox`.
---
@@ -298,11 +299,12 @@ Themes:
---
### [ ] P5-20 · Quick Reply from Browser Notification
### [~] P5-20 · Quick Reply from Browser Notification
**What:** Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
**[AUDIT REQUIRED]** (1) Verify browser Notification Actions API support in target browsers. (2) Confirmed: service worker EXISTS at `src/sw.ts` — add `notificationclick` handler there.
**Complexity:** Medium-High.
**Complexity:** Medium-High.
**Partial Fix Applied ⚠️ UNTESTED:** Notifications now (a) show the real message body (`username: message` instead of "New inbox notification from..."), (b) click navigates directly to the room at the specific event (not the inbox), (c) `window.focus()` called on click so the tab comes to front, (d) reminder toasts also link to the specific event. Full inline-reply via Notification Actions API still needs the SW `push`+`notificationclick` pipeline (requires switching from `new Notification()` to `showNotification()` through the SW).
---
+1 -1
View File
@@ -237,7 +237,7 @@ export function RenderMessageContent({
title={body}
src={src}
loading="lazy"
style={{ objectFit: 'cover', width: '100%', height: '100%' }}
style={{ objectFit: 'cover', objectPosition: 'center top', width: '100%', height: '100%' }}
/>
)}
/>
+1
View File
@@ -5,6 +5,7 @@ export const Image = style([
DefaultReset,
{
objectFit: 'cover',
objectPosition: 'center top',
width: '100%',
height: '100%',
},
+1 -1
View File
@@ -422,7 +422,7 @@ function GalleryTile({
<img
src={media.url}
alt={body}
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', display: 'block' }}
/>
)}
+10 -6
View File
@@ -19,7 +19,6 @@ import {
getDirectRoomPath,
getHomeRoomPath,
getInboxInvitesPath,
getInboxNotificationsPath,
} from '../pathUtils';
import { mDirectAtom } from '../../state/mDirectList';
import {
@@ -255,6 +254,10 @@ function MessageNotifications() {
eventId: string;
body?: string;
}) => {
const roomPath = mDirects.has(roomId)
? getDirectRoomPath(roomId, eventId)
: getHomeRoomPath(roomId, eventId);
if (document.hasFocus()) {
setToast({
id: `${roomId}-${eventId}-${Date.now()}`,
@@ -263,7 +266,7 @@ function MessageNotifications() {
body: (body ?? '').slice(0, 80),
roomName,
roomId,
hashPath: mDirects.has(roomId) ? getDirectRoomPath(roomId) : getHomeRoomPath(roomId),
hashPath: roomPath,
});
return;
}
@@ -271,12 +274,13 @@ function MessageNotifications() {
const noti = new window.Notification(roomName, {
icon: roomAvatar,
badge: roomAvatar,
body: `New inbox notification from ${username}`,
body: body ? `${username}: ${body}`.slice(0, 120) : username,
silent: true,
});
noti.onclick = () => {
if (!window.closed) navigate(getInboxNotificationsPath());
window.focus();
navigate(roomPath);
noti.close();
notifRef.current = undefined;
};
@@ -400,8 +404,8 @@ function ReminderMonitor() {
firedRef.current.add(key);
const room = mx.getRoom(r.roomId);
const hashPath = mDirects.has(r.roomId)
? getDirectRoomPath(r.roomId)
: getHomeRoomPath(r.roomId);
? getDirectRoomPath(r.roomId, r.eventId)
: getHomeRoomPath(r.roomId, r.eventId);
setToast({
id: `reminder-${key}`,
displayName: 'Reminder',