Skip to content

Commit 9cf1266

Browse files
committed
v3.66.401
1 parent 5e074ca commit 9cf1266

9 files changed

Lines changed: 433 additions & 114 deletions

File tree

functions/api/bot.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3659,7 +3659,7 @@ async function handleShopAction(context, body, botPrivkey, botPubkey) {
36593659
}
36603660

36613661
var BOT_NYM = "Nymbot";
3662-
var NYMCHAT_VERSION = "3.66.400";
3662+
var NYMCHAT_VERSION = "3.66.401";
36633663
var NYMCHAT_IOS_APP = "https://testflight.apple.com/join/k8FS8Mm3";
36643664
var NYMCHAT_ANDROID_APP = "https://play.google.com/store/apps/details?id=com.nym.bar";
36653665
var COMMAND_PREFIX = "?";
@@ -4296,7 +4296,7 @@ var NYMBOT_SYSTEM_PROMPT = [
42964296
"Games & Fun: ?trivia [category] — AI-generated trivia (general, history, science, crypto, nostr), ?joke — AI-generated joke, ?riddle — AI-generated riddle, ?wordplay [mode] — AI word game (wordle, anagram, scramble), ?flip — Coin flip, ?8ball — Magic 8-ball, ?pick <options> — Random pick.",
42974297
"Utility: ?math <expr> — Calculate, ?units <value> <from> to <to> — Convert units, ?time — UTC time, ?btc — Current Bitcoin price.",
42984298
"Channel Activity: ?who — Active nyms in channel, ?summarize — AI summary of channel discussion, ?top — Top channels by activity, ?last [N] — Recent messages, ?seen <nym> — Where was someone last seen.",
4299-
"Info: ?help — List all bot commands, ?about — About Nymchat (version, platform links), ?nostr — Nostr protocol tips, ?changelog [version] — Live Nymchat release notes pulled from GitHub (default shows the latest release; pass a tag like ?changelog v3.66.400 for a specific version).",
4299+
"Info: ?help — List all bot commands, ?about — About Nymchat (version, platform links), ?nostr — Nostr protocol tips, ?changelog [version] — Live Nymchat release notes pulled from GitHub (default shows the latest release; pass a tag like ?changelog v3.66.401 for a specific version).",
43004300
"Users can also type @Nymbot <question> to ask me directly.",
43014301
"Users can quote-reply any message and mention @Nymbot to ask about it, or reply to my responses to continue the conversation with context.",
43024302
"",
@@ -5129,7 +5129,7 @@ function findRelease(releases, query) {
51295129
var t = (releases[i].tag || "").toLowerCase().replace(/^v/, "");
51305130
if (t === normalized) return releases[i];
51315131
}
5132-
// Prefix match (e.g. "3.61" matches "3.66.400")
5132+
// Prefix match (e.g. "3.61" matches "3.66.401")
51335133
for (var j = 0; j < releases.length; j++) {
51345134
var tt = (releases[j].tag || "").toLowerCase().replace(/^v/, "");
51355135
if (tt.indexOf(normalized) === 0) return releases[j];
@@ -5184,7 +5184,7 @@ function needsChangelogContext(question) {
51845184
if (/\b(changelog|release notes?|what'?s new|whats new|patch notes?|update notes?)\b/.test(q)) return true;
51855185
if (/\b(latest|newest|recent|new|previous|last)\b.{0,30}\b(release|version|update)\b/.test(q)) return true;
51865186
if (/\b(release|version|update)\b.{0,30}\b(history|notes?|log|info)\b/.test(q)) return true;
5187-
// Specific version reference like "3.66.400", "v3.61", "version 3.60.300"
5187+
// Specific version reference like "3.66.401", "v3.61", "version 3.60.300"
51885188
if (/\bv?\d+\.\d+(?:\.\d+)?\b/.test(q) && /\b(nym|nymchat|app|version|release|update)\b/.test(q)) return true;
51895189
return false;
51905190
}

js/app.js

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3539,7 +3539,7 @@ function initWallpaperUI() {
35393539
}
35403540
}
35413541

3542-
const NYMCHAT_VERSION = 'v3.66.400';
3542+
const NYMCHAT_VERSION = 'v3.66.401';
35433543

35443544
function showAbout(prefill) {
35453545
const modal = document.getElementById('aboutModal');
@@ -4951,12 +4951,17 @@ async function applyNostrSettingsAdditive(s) {
49514951
}
49524952
const pk = n.senderPubkey || n.channelInfo?.pubkey || '';
49534953
if (pk && nym.blockedUsers && nym.blockedUsers.has(pk)) continue;
4954-
const viewedFromLastRead = n.timestamp <= (nym.notificationLastReadTime || 0);
4954+
// Use receivedAt when present (set on the device that observed
4955+
// the notification) so synced notifications keep their original
4956+
// unread status. Fall back to timestamp for legacy entries.
4957+
const observedAt = (typeof n.receivedAt === 'number' && n.receivedAt > 0) ? n.receivedAt : n.timestamp;
4958+
const viewedFromLastRead = observedAt <= (nym.notificationLastReadTime || 0);
49554959
nym.notificationHistory.push({
49564960
title: n.title || '',
49574961
body: n.body || '',
49584962
channelInfo: n.channelInfo || null,
49594963
timestamp: n.timestamp,
4964+
receivedAt: observedAt,
49604965
senderNym: n.senderNym || '',
49614966
senderPubkey: pk,
49624967
eventId: n.eventId || n.channelInfo?.eventId || undefined,
@@ -6314,17 +6319,21 @@ function renderRelayList(pool, stats) {
63146319
const listEl = document.getElementById('rsRelayList');
63156320
if (!listEl) return;
63166321

6317-
// Build sorted relay entries (no type labels - all relays are read/write)
63186322
const entries = [];
63196323

63206324
if (typeof nym !== 'undefined' && nym.useRelayProxy && nym._isAnyPoolOpen()) {
6321-
// Pool mode: only show actually connected relays
6325+
// Pool mode: render every known relay (connected + recently-seen) so
6326+
// a single shard hiccup doesn't make rows disappear and reappear.
63226327
const connectedSet = new Set(nym.poolConnectedRelays);
6323-
connectedSet.forEach(url => {
6328+
const known = new Set([...connectedSet]);
6329+
if (nym._poolRelayLastSeen) {
6330+
nym._poolRelayLastSeen.forEach((_, url) => known.add(url));
6331+
}
6332+
known.forEach(url => {
63246333
if (url === 'relay-pool') return;
63256334
entries.push({
63266335
url,
6327-
open: true,
6336+
open: connectedSet.has(url),
63286337
events: stats.eventsPerRelay.get(url) || 0,
63296338
latency: stats.latencyPerRelay.get(url) || null
63306339
});
@@ -6341,7 +6350,6 @@ function renderRelayList(pool, stats) {
63416350
});
63426351
}
63436352

6344-
// Sort: connected first, then by events descending
63456353
entries.sort((a, b) => {
63466354
if (a.open !== b.open) return a.open ? -1 : 1;
63476355
return b.events - a.events;
@@ -6352,31 +6360,45 @@ function renderRelayList(pool, stats) {
63526360
return;
63536361
}
63546362

6355-
// Only rebuild DOM if count changed; otherwise update in-place for performance
6356-
const existing = listEl.querySelectorAll('.relay-stats-row');
6357-
if (existing.length !== entries.length) {
6358-
let html = '';
6359-
entries.forEach((e, i) => {
6360-
const shortUrl = e.url.replace('wss://', '').replace('ws://', '');
6361-
html += `<div class="relay-stats-row" data-rs-idx="${i}">` +
6363+
const existing = new Map();
6364+
listEl.querySelectorAll('.relay-stats-row').forEach(row => {
6365+
const url = row.dataset.rsUrl;
6366+
if (url) existing.set(url, row);
6367+
});
6368+
6369+
const seen = new Set();
6370+
let prevRow = null;
6371+
entries.forEach(e => {
6372+
seen.add(e.url);
6373+
let row = existing.get(e.url);
6374+
const shortUrl = e.url.replace('wss://', '').replace('ws://', '');
6375+
if (!row) {
6376+
row = document.createElement('div');
6377+
row.className = 'relay-stats-row';
6378+
row.dataset.rsUrl = e.url;
6379+
row.innerHTML =
63626380
`<span class="relay-stats-dot ${e.open ? 'open' : 'closed'}"></span>` +
63636381
`<span class="relay-stats-url" title="${nym.escapeHtml(e.url)}">${nym.escapeHtml(shortUrl)}</span>` +
63646382
`<span class="relay-stats-latency">${e.latency !== null ? e.latency + 'ms' : '--'}</span>` +
6365-
`<span class="relay-stats-events">${e.events} evt</span>` +
6366-
`</div>`;
6367-
});
6368-
listEl.innerHTML = html;
6369-
} else {
6370-
// Update in-place
6371-
entries.forEach((e, i) => {
6372-
const row = existing[i];
6373-
if (!row) return;
6383+
`<span class="relay-stats-events">${e.events} evt</span>`;
6384+
} else {
63746385
const dot = row.querySelector('.relay-stats-dot');
6375-
if (dot) { dot.className = `relay-stats-dot ${e.open ? 'open' : 'closed'}`; }
6386+
if (dot) dot.className = `relay-stats-dot ${e.open ? 'open' : 'closed'}`;
63766387
const evtEl = row.querySelector('.relay-stats-events');
63776388
if (evtEl) evtEl.textContent = e.events + ' evt';
63786389
const latEl = row.querySelector('.relay-stats-latency');
63796390
if (latEl) latEl.textContent = e.latency !== null ? e.latency + 'ms' : '--';
6380-
});
6381-
}
6391+
}
6392+
// Move into the correct sort position
6393+
if (prevRow) {
6394+
if (prevRow.nextElementSibling !== row) {
6395+
prevRow.parentNode.insertBefore(row, prevRow.nextElementSibling);
6396+
}
6397+
} else if (listEl.firstElementChild !== row) {
6398+
listEl.insertBefore(row, listEl.firstElementChild);
6399+
}
6400+
prevRow = row;
6401+
});
6402+
6403+
existing.forEach((row, url) => { if (!seen.has(url)) row.remove(); });
63826404
}

js/modules/channels.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,13 @@ ${distance ? `<div class="geohash-info-item"><strong>Distance:</strong> ${distan
911911
this.clearQuoteReply();
912912
if (this.pendingEdit) this.cancelEditMessage();
913913

914+
// Close the mobile sidebar as soon as the switch is committed so the
915+
// UI feels responsive even while the channel loads. Anything that
916+
// throws later won't leave the sidebar stuck open.
917+
if (window.innerWidth <= 768) {
918+
this.closeSidebar();
919+
}
920+
914921
// Track navigation history
915922
this._pushNavigation({ type: 'channel', channel, geohash });
916923

@@ -1010,11 +1017,6 @@ ${distance ? `<div class="geohash-info-item"><strong>Distance:</strong> ${distan
10101017
this.hideChannelAutocomplete();
10111018
this.hideEmojiAutocomplete();
10121019
this._focusMessageInput();
1013-
1014-
// Close mobile sidebar on mobile
1015-
if (window.innerWidth <= 768) {
1016-
this.closeSidebar();
1017-
}
10181020
},
10191021

10201022
_focusMessageInput() {

js/modules/groups.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2245,6 +2245,11 @@ Object.assign(NYM.prototype, {
22452245
this.userScrolledUp = false;
22462246
if (this.pendingEdit) this.cancelEditMessage();
22472247

2248+
// Close the mobile sidebar as soon as the switch is committed.
2249+
if (window.innerWidth <= 768) {
2250+
this.closeSidebar();
2251+
}
2252+
22482253
// Track navigation history
22492254
this._pushNavigation({ type: 'group', groupId });
22502255

@@ -2294,8 +2299,6 @@ Object.assign(NYM.prototype, {
22942299
this.hideChannelAutocomplete();
22952300
this.hideEmojiAutocomplete();
22962301
this._focusMessageInput();
2297-
2298-
if (window.innerWidth <= 768) this.closeSidebar();
22992302
},
23002303

23012304
// Bubble a group item to the top of the PM list

0 commit comments

Comments
 (0)