Round 4: hidden-msg pagination, iOS audio unlock, iOS long-press callout, draft hardening, pinned-by
Older-messages pagination (couple of chats wouldn't scroll back): the thread query
returned the latest 40 rows and JS filtered out hidden (delete-for-me) messages
AFTER the LIMIT, so a chat with a hidden recent message returned <40 → the client
read that as "no older history." Now excluded in SQL (repos.thread /
threadByConversation take the viewer id), so a page is always 40 VISIBLE rows.
Verified locally: hide 3 recent → page still returns 40 (older ones fill in).
#2 iOS in-chat tone was silent: WebAudio context is created suspended and only
resumes inside a user gesture. Added unlockAudio() on first tap/click (resume +
0-gain blip), re-armed each gesture so a background→foreground re-suspend recovers.
#9 Long-press "works once then stops" on images was iOS's native touch-callout
(Save Image / selection magnifier) hijacking the gesture. Disabled
-webkit-touch-callout/user-select on #msgs bubbles; added a Save action to the
sheet so image-saving isn't lost.
#13 Pin/unpin WAS being audited (verified: message.pin in /api/audit) — there's just
no in-app viewer. Surfaced "Pinned by X" in the pinned bar for immediate context.
#14 Hardened draft save: it now runs BEFORE maybeAutocorrect/autoGrow (wrapped) in the
input handler, so a throw there can't skip persisting the draft.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -839,7 +839,7 @@ route('GET', '/api/messages/thread', async (req, res) => {
|
||||
const hidden = new Set(await R.messages.hiddenForUser(u.id)); // #18: messages this user "deleted for me"
|
||||
if (group) {
|
||||
if (!await R.conversations.isMember(group, u.id)) return json(res, 403, { error: 'not a member of this group' });
|
||||
const rows = (await R.messages.threadByConversation(group, 40, before)).filter((m) => !hidden.has(m.id)); // #18
|
||||
const rows = (await R.messages.threadByConversation(group, u.id, 40, before)).filter((m) => !hidden.has(m.id)); // #18 (hidden now excluded in SQL too, so the 40-row page counts only visible messages)
|
||||
if (!peek && !before) {
|
||||
await R.conversations.markRead(group, u.id);
|
||||
const evt = { type: 'group-read', group, by: u.id, byName: names[u.id] || u.email, at: now() };
|
||||
@@ -1695,7 +1695,7 @@ route('GET', '/api/messages/pinned', async (req, res) => {
|
||||
rows = await R.messages.pinnedInDm(u.team_id, u.id, other);
|
||||
}
|
||||
const hidden = new Set(await R.messages.hiddenForUser(u.id)); // #18: don't surface a message you deleted-for-me
|
||||
json(res, 200, await Promise.all(rows.filter((m) => !hidden.has(m.id)).map(async (m) => { const d = await buildMsgDTO(m, names, u.id); d.fromName = names[m.sender_id] || ''; return d; })));
|
||||
json(res, 200, await Promise.all(rows.filter((m) => !hidden.has(m.id)).map(async (m) => { const d = await buildMsgDTO(m, names, u.id); d.fromName = names[m.sender_id] || ''; d.pinnedBy = names[m.pinned_by] || ''; return d; }))); // #13: who pinned it (shown in the pinned bar)
|
||||
});
|
||||
// Edit a message (sender only, text only) — updates the body + marks it edited, and pushes the
|
||||
// change live to the other side / other tabs (mirrors the delete broadcast).
|
||||
|
||||
Reference in New Issue
Block a user