Blur gallery images by default, sync progress UI, and add chat/monitor tests

- Blur NSFW media gallery images until hover/reveal
- Surface chat sync progress in the dashboard header
- Extend chat service, cache, and monitor with delta-media handling
- Add tests for chat cooldown, delta media, and monitor flows

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.7 committed 2026-05-22 10:01:09 +01:00
1 parent 633bb0ec04
commit 2260388dc8
15 files changed
+675 -41

No files matched your search

+12
View File
@@ -8,6 +8,7 @@ export function createConfigRouter(ctx: WebContext): Router {
res.json({
rateLimit: ctx.runtimeConfig.rateLimit,
browser: { dryRun: ctx.runtimeConfig.browser.dryRun },
reddit: ctx.runtimeConfig.reddit,
});
});
@@ -39,11 +40,22 @@ export function createConfigRouter(ctx: WebContext): Router {
}
}
if (updates.reddit) {
if (typeof updates.reddit.prioritizeRecentPosts === 'boolean') {
ctx.runtimeConfig.reddit.prioritizeRecentPosts = updates.reddit.prioritizeRecentPosts;
}
if (typeof updates.reddit.recentWindowMinutes === 'number' && updates.reddit.recentWindowMinutes > 0) {
ctx.runtimeConfig.reddit.recentWindowMinutes = updates.reddit.recentWindowMinutes;
}
}
// Mirror into the loaded config so getStats reflects new values.
ctx.config.rateLimit.minDelayBetweenMessagesMs = ctx.runtimeConfig.rateLimit.minDelayBetweenMessagesMs;
ctx.config.rateLimit.maxMessagesPerHour = ctx.runtimeConfig.rateLimit.maxMessagesPerHour;
ctx.config.rateLimit.jitterMs = ctx.runtimeConfig.rateLimit.jitterMs;
ctx.config.browser.dryRun = ctx.runtimeConfig.browser.dryRun;
ctx.config.reddit.prioritizeRecentPosts = ctx.runtimeConfig.reddit.prioritizeRecentPosts;
ctx.config.reddit.recentWindowMinutes = ctx.runtimeConfig.reddit.recentWindowMinutes;
ctx.getOnConfigChange()?.(ctx.runtimeConfig);
+28 -9
View File
@@ -32,13 +32,20 @@ export function createConversationsRouter(ctx: WebContext): Router {
return;
}
try {
// Pull in any new activity from the homeserver before we read the cache,
// so a stale "waiting for them to reply" view picks up the reply they
// already sent. Cheap (~one /sync call) and queued behind any in-flight sync.
try {
await ctx.services.chat.refreshTopChats();
} catch (err) {
ctx.logger.warn('Pre-open delta sync failed:', err);
// If a sync is already in flight (e.g. a Full sync the user kicked off
// from the dashboard), don't queue *another* delta + lazy-load behind it
// — that's what makes the modal sit on "Loading…" for minutes. Render
// from cache and let the frontend show a "sync in progress" banner; the
// user can reopen once it's done.
const syncStatus = ctx.services.chat.getSyncStatus();
const syncInFlight = syncStatus.syncing;
if (!syncInFlight) {
try {
await ctx.services.chat.refreshTopChats();
} catch (err) {
ctx.logger.warn('Pre-open delta sync failed:', err);
}
}
const conversation = await findConversation(decodeURIComponent(req.params.id));
if (!conversation) {
@@ -53,7 +60,9 @@ export function createConversationsRouter(ctx: WebContext): Router {
? conversation.messages[conversation.messages.length - 1].timestamp
: 0;
const hasNewerActivity = (conversation.lastMessageTime ?? 0) > cachedLastTs;
if (!conversation.messages || conversation.messages.length === 0 || hasNewerActivity) {
const needsLazyLoad =
!conversation.messages || conversation.messages.length === 0 || hasNewerActivity;
if (needsLazyLoad && !syncInFlight) {
try {
await ctx.services.chat.loadConversationDetails(conversation);
await ctx.services.chat.saveCacheExternal();
@@ -88,7 +97,17 @@ export function createConversationsRouter(ctx: WebContext): Router {
ctx.logger.warn(`Failed to look up triggering post for ${conversation.username}:`, err);
}
res.json({ ...conversation, triggeringPost });
res.json({
...conversation,
triggeringPost,
syncInProgress: syncInFlight
? {
kind: syncStatus.syncingKind,
progress: syncStatus.progress,
staleMessages: needsLazyLoad,
}
: null,
});
} catch (error) {
ctx.logger.error('Failed to fetch conversation:', error);
res.status(500).json({ error: String(error) });