openvibers/openvibe.chat Commits
Deploy notices: a new deploy folds into the last card only if nobody has spoken in ANY room since (the global feed shows stream messages too, so cards kept folding past them and their time ranges ran past the messages below), and only within 3 hours
Changes
@@@ -15,7 +15,8 @@// • Late joiners of this announcement get it once (replayTo, from the join handler).// ═══════════════════════════════════════════════════════════════--const FOLD_WINDOW_MS = 12 * 60 * 60 * 1000;++// A card covers at most 3 hours of deploys, so its time range stays readable.++const FOLD_WINDOW_MS = 3 * 60 * 60 * 1000;const MAX_COMMITS = 40;const ATTEMPTS_MS = [5000, 20000, 45000]; // clients reconnect with backoff after a restart@@@ -30,10 +31,15 @@ const parseLog = (raw) => raw.trim().split('\n').filter(Boolean).map((line) => {const plainText = (meta) => `🚀 ${meta.commits.length} update${meta.commits.length === 1 ? '' : 's'} shipped: ${meta.commits.slice(0, 3).map(c => c.subject).join(' · ')}${meta.commits.length > 3 ? ` · and ${meta.commits.length - 3} more` : ''}`;--/** Insert a notice, or fold into the newest row when that row is itself a recent deploy notice. */++/**++ * Insert a notice, or fold into the newest row when that row is itself a recent deploy notice.++ * "Newest" is across EVERY room: the global feed shows stream and channel messages too, so a card++ * that kept folding while people chatted in a stream ended up with a time range running past the++ * messages under it. Anyone speaking anywhere since the last card starts a new card.++ */function persist(db, commits) {const nowIso = new Date().toISOString();-- const newest = db.get('SELECT id, message_type, metadata FROM chat_messages WHERE is_global = 1 AND is_deleted = 0 ORDER BY id DESC LIMIT 1');++ const newest = db.get('SELECT id, message_type, metadata FROM chat_messages WHERE is_deleted = 0 ORDER BY id DESC LIMIT 1');let prev = null;if (newest && newest.message_type === 'system' && newest.metadata) {try { const m = JSON.parse(newest.metadata); if (m && m.kind === 'deploy' && Date.now() - Date.parse(m.first_at) < FOLD_WINDOW_MS) prev = m; } catch { /* not ours */ }@@@ -174,11 +174,19 @@ t('deploy notices: stored as one rolling global row, shown to everyone, folded or = await calls([{ op: 'deployNotice', args: [[commit(2)]] }]);assert.strictEqual(r.body.results[0].result.id, id1, 'folded into the same row');assert.strictEqual(JSON.parse(h.db.getChatMessageById(id1).metadata).deploys, 2);++ // Someone speaks in a stream room (the global feed shows it): the next deploy is a new card,++ // so no card's time range runs past a message under it.++ h.db.saveChatMessage({ stream_id: streamId, user_id: viewer.id, username: viewer.username, message: 'hello brother', message_type: 'chat', is_global: false });++ r = await calls([{ op: 'deployNotice', args: [[commit(3)]] }]);++ const id3 = r.body.results[0].result.id;++ assert.notStrictEqual(id3, id1, 'a message since the last card starts a new card');++ assert.strictEqual(JSON.parse(h.db.getChatMessageById(id3).metadata).deploys, 1);++ assert.strictEqual(JSON.parse(h.db.getChatMessageById(id1).metadata).deploys, 2, 'the old card is left as it was');// A late joiner gets it once.const late = await h.ws({ ip: '198.51.100.52' });late.sendJson({ type: 'join' });const upd = await late.next((m) => m.type === 'update', 4000);-- assert.strictEqual(upd.id, id1);++ assert.strictEqual(upd.id, id3);late.close();});
@@ -15,7 +15,8 @@
@@ -15,7 +15,8 @@
// • Late joiners of this announcement get it once (replayTo, from the join handler).
// • Late joiners of this announcement get it once (replayTo, from the join handler).
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
-const FOLD_WINDOW_MS = 12 * 60 * 60 * 1000;
+// A card covers at most 3 hours of deploys, so its time range stays readable.
+const FOLD_WINDOW_MS = 3 * 60 * 60 * 1000;
const MAX_COMMITS = 40;
const MAX_COMMITS = 40;
const ATTEMPTS_MS = [5000, 20000, 45000]; // clients reconnect with backoff after a restart
const ATTEMPTS_MS = [5000, 20000, 45000]; // clients reconnect with backoff after a restart
@@ -30,10 +31,15 @@ const parseLog = (raw) => raw.trim().split('\n').filter(Boolean).map((line) => {
@@ -30,10 +31,15 @@ const parseLog = (raw) => raw.trim().split('\n').filter(Boolean).map((line) => {
const plainText = (meta) => `🚀 ${meta.commits.length} update${meta.commits.length === 1 ? '' : 's'} shipped: ${meta.commits.slice(0, 3).map(c => c.subject).join(' · ')}${meta.commits.length > 3 ? ` · and ${meta.commits.length - 3} more` : ''}`;
const plainText = (meta) => `🚀 ${meta.commits.length} update${meta.commits.length === 1 ? '' : 's'} shipped: ${meta.commits.slice(0, 3).map(c => c.subject).join(' · ')}${meta.commits.length > 3 ? ` · and ${meta.commits.length - 3} more` : ''}`;
-/** Insert a notice, or fold into the newest row when that row is itself a recent deploy notice. */
+/**
+ * Insert a notice, or fold into the newest row when that row is itself a recent deploy notice.
+ * "Newest" is across EVERY room: the global feed shows stream and channel messages too, so a card
+ * that kept folding while people chatted in a stream ended up with a time range running past the
+ * messages under it. Anyone speaking anywhere since the last card starts a new card.
+ */
function persist(db, commits) {
function persist(db, commits) {
const nowIso = new Date().toISOString();
const nowIso = new Date().toISOString();
- const newest = db.get('SELECT id, message_type, metadata FROM chat_messages WHERE is_global = 1 AND is_deleted = 0 ORDER BY id DESC LIMIT 1');
+ const newest = db.get('SELECT id, message_type, metadata FROM chat_messages WHERE is_deleted = 0 ORDER BY id DESC LIMIT 1');
let prev = null;
let prev = null;
if (newest && newest.message_type === 'system' && newest.metadata) {
if (newest && newest.message_type === 'system' && newest.metadata) {
try { const m = JSON.parse(newest.metadata); if (m && m.kind === 'deploy' && Date.now() - Date.parse(m.first_at) < FOLD_WINDOW_MS) prev = m; } catch { /* not ours */ }
try { const m = JSON.parse(newest.metadata); if (m && m.kind === 'deploy' && Date.now() - Date.parse(m.first_at) < FOLD_WINDOW_MS) prev = m; } catch { /* not ours */ }
@@ -174,11 +174,19 @@ t('deploy notices: stored as one rolling global row, shown to everyone, folded o
@@ -174,11 +174,19 @@ t('deploy notices: stored as one rolling global row, shown to everyone, folded o
r = await calls([{ op: 'deployNotice', args: [[commit(2)]] }]);
r = await calls([{ op: 'deployNotice', args: [[commit(2)]] }]);
assert.strictEqual(r.body.results[0].result.id, id1, 'folded into the same row');
assert.strictEqual(r.body.results[0].result.id, id1, 'folded into the same row');
assert.strictEqual(JSON.parse(h.db.getChatMessageById(id1).metadata).deploys, 2);
assert.strictEqual(JSON.parse(h.db.getChatMessageById(id1).metadata).deploys, 2);
+ // Someone speaks in a stream room (the global feed shows it): the next deploy is a new card,
+ // so no card's time range runs past a message under it.
+ h.db.saveChatMessage({ stream_id: streamId, user_id: viewer.id, username: viewer.username, message: 'hello brother', message_type: 'chat', is_global: false });
+ r = await calls([{ op: 'deployNotice', args: [[commit(3)]] }]);
+ const id3 = r.body.results[0].result.id;
+ assert.notStrictEqual(id3, id1, 'a message since the last card starts a new card');
+ assert.strictEqual(JSON.parse(h.db.getChatMessageById(id3).metadata).deploys, 1);
+ assert.strictEqual(JSON.parse(h.db.getChatMessageById(id1).metadata).deploys, 2, 'the old card is left as it was');
// A late joiner gets it once.
// A late joiner gets it once.
const late = await h.ws({ ip: '198.51.100.52' });
const late = await h.ws({ ip: '198.51.100.52' });
late.sendJson({ type: 'join' });
late.sendJson({ type: 'join' });
const upd = await late.next((m) => m.type === 'update', 4000);
const upd = await late.next((m) => m.type === 'update', 4000);
- assert.strictEqual(upd.id, id1);
+ assert.strictEqual(upd.id, id3);
late.close();
late.close();
});
});