diff --git a/webv4/lib/events/forum.js b/webv4/lib/events/forum.js
index 96bf879c08851a2aee27ba4b11850bb5c427579c..c7719bf32ffdb3e80aed289b10ced3fb4d1cf526 100644
--- a/webv4/lib/events/forum.js
+++ b/webv4/lib/events/forum.js
@@ -4,6 +4,7 @@ var last_subs;
 var last_groups;
 var last_threads;
 var last_run = 0;
+const is_real_user = is_user();
 const frequency = (settings.refresh_interval || 60000) / 1000;
 
 // Where 'a' is the previous data and 'b' is new
@@ -46,7 +47,7 @@ function scan_subs(group) {
 }
 
 function scan_threads(sub, offset, page_size) {
-    const scan = getThreadStats(sub, offset, page_size);
+    const scan = getThreadStats(sub, offset, page_size, !is_real_user);
     if (!last_threads) {
         forum_emit('threads', scan);
     } else {
@@ -66,11 +67,10 @@ function scan_threads(sub, offset, page_size) {
 }
 
 function cycle() {
-    if (!is_user()) return;
     if (time() - last_run <= frequency) return;
     last_run = time();
-    if (Request.has_param('groups_unread')) scan_groups();
-    if (Request.has_param('subs_unread')) scan_subs(Request.get_param('subs_unread'));
+    if (is_real_user && Request.has_param('groups_unread')) scan_groups();
+    if (is_real_user && Request.has_param('subs_unread')) scan_subs(Request.get_param('subs_unread'));
     if (Request.has_param('threads')) scan_threads(Request.get_param('threads'), Request.get_param('offset'), Request.get_param('page_size'));
 }
 
diff --git a/webv4/lib/forum.js b/webv4/lib/forum.js
index 2793ec4f979f391db43e047372f98170b7f919ae..630e51543c99c1088e68edcbd56ab67c91ca7a8a 100644
--- a/webv4/lib/forum.js
+++ b/webv4/lib/forum.js
@@ -45,15 +45,13 @@ function listSubs(group) {
     });
 }
 
-function listThreads(sub, offset, count, page_offset) {
+function listThreads(sub, offset, count) {
 
     offset = parseInt(offset);
     if (isNaN(offset) || offset < 0) return false;
     count = parseInt(count);
     if (isNaN(count) || count < 1) return false;
 
-    if (page_offset) offset = offset * count;
-
     var threads = getMessageThreads(sub, settings.max_messages);
     if (offset >= threads.order.length) return false;
 
@@ -72,10 +70,40 @@ function listThreads(sub, offset, count, page_offset) {
 
 }
 
+function getNewestMessageInSub(sub) {
+    const mb = new MsgBase(sub.code);
+    if (!mb.open()) return;
+    var h;
+    var ret;
+    for (var m = mb.last_msg; m >= mb.first_msg; m--) {
+        h = mb.get_msg_header(m);
+        if (h === null) continue;
+        ret = {
+            from: h.from,
+            subject: h.subject,
+            when_written_time: h.when_written_time,
+        };
+        break;
+    }
+    mb.close();
+    return ret;
+}
+
+function getNewestMessagePerSub(grp) {
+    grp = parseInt(grp, 10);
+    if (isNaN(grp) || grp < 0 || !msg_area.grp_list[grp]) return [];
+    return msg_area.grp_list[grp].sub_list.reduce(function (a, c) {
+        const s = getNewestMessageInSub(c);
+        if (s !== undefined) a[c.code] = s;
+        log(LOG_DEBUG, JSON.stringify(s));
+        return a;
+    }, {});
+}
+
 function getSubUnreadCount(sub) {
     var ret = {
-        scanned : 0,
-        total : 0
+        scanned: 0,
+        total: 0
     };
     if (typeof msg_area.sub[sub] === 'undefined') return ret;
     try {
@@ -157,7 +185,7 @@ function getThreadVoteTotals(thread, mkeys) {
 }
 
 // { [thread_id]: { total, unread, votes: { up: { parent, total }, down: { parent, total }, total }, newest } }
-function getThreadStats(sub, offset, page_size) {
+function getThreadStats(sub, offset, page_size, guest) {
     const threads = getMessageThreads(sub, settings.max_messages);
     const ret = {};
     if (!offset) offset = 0;
@@ -169,7 +197,7 @@ function getThreadStats(sub, offset, page_size) {
         var mkeys = Object.keys(thread.messages);
         ret[threads.order[n]] = {
             total: mkeys.length,
-            unread: getUnreadInThread(sub, thread, mkeys),
+            unread: guest ? 0 : getUnreadInThread(sub, thread, mkeys),
             votes: getThreadVoteTotals(thread, mkeys),
             newest: {
                 from: thread.messages[mkeys[mkeys.length - 1]].from,
diff --git a/webv4/lib/locale/en_us.ini b/webv4/lib/locale/en_us.ini
index d231320aa24ac4d6f163a9731ef62d2249b0d262..fa9bfb0e921a51334734db2433ea0d345ac6e872 100644
--- a/webv4/lib/locale/en_us.ini
+++ b/webv4/lib/locale/en_us.ini
@@ -74,6 +74,7 @@ label_message_to = To
 label_message_date = on
 label_message_subject = Subject
 label_thread_latest_reply = Latest reply from
+label_sub_newest_message = Latest message
 suffix_reply_count = reply
 suffix_replies_count = replies
 badge_poll = POLL
diff --git a/webv4/pages/001-forum.xjs b/webv4/pages/001-forum.xjs
index 2f40948612a3618cecbc8142a00a00eb5c465efe..85ace20d863e7fc622e88cba12f8de87f007fb4b 100644
--- a/webv4/pages/001-forum.xjs
+++ b/webv4/pages/001-forum.xjs
@@ -10,11 +10,11 @@
 
 <!-- To do: Search (forum, group, sub, or thread), Sortation and data-attributes -->
 
-<?xjs if (Request.has_param('sub') && Request.has_param('thread')) { ?>
+<? if (Request.has_param('sub') && Request.has_param('thread')) { ?>
 
     <?xjs
         var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0;
-        const thread = getMessageThreads(Request.get_param('sub'), settings.max_messages).thread[Request.get_param('thread')];
+        var thread = getMessageThreads(Request.get_param('sub'), settings.max_messages).thread[Request.get_param('thread')];
     ?>
 
     <ol class="breadcrumb">
@@ -50,7 +50,7 @@
 
     </div>
 
-<?xjs } else if (Request.has_param('sub') && typeof msg_area.sub[Request.get_param('sub')] != 'undefined') { ?>
+<? } else if (Request.has_param('sub') && msg_area.sub[Request.get_param('sub')] !== undefined) { ?>
 
     <ol class="breadcrumb">
         <li>
@@ -72,8 +72,8 @@
 
     <?xjs
         var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0;
-        const threads = listThreads(Request.get_param('sub'), offset, settings.page_size, true) || { total: 0, threads: [] };
-        const sub = msg_area.sub[Request.get_param('sub')];
+        var threads = listThreads(Request.get_param('sub'), offset * settings.page_size, settings.page_size, true) || { total: 0, threads: [] };
+        var sub = msg_area.sub[Request.get_param('sub')];
     ?>
 
     <button class="btn btn-default icon" aria-label="<? locale.write('button_post_message'); ?>" title="<? locale.write('button_post_message'); ?>" onclick="addNew('<? write(sub.code); ?>')">
@@ -107,9 +107,9 @@
     </div>
 
     <div id="forum-thread-stats-template" hidden>
-        <?xjs if (is_user()) { ?>
+        <? if (is_user()) { ?>
             <span data-unread-messages title="<? locale.write('badge_unread_messages'); ?>" class="badge <? write(sub.scan_cfg&SCAN_CFG_NEW || sub.scan_cfg&SCAN_CFG_YONLY ? 'scanned' : ''); ?>" hidden></span>
-        <?xjs } ?>
+        <? } ?>
         <span data-upvotes-badge title="<? locale.write('badge_upvotes'); ?>" class="badge upvote-bg" style="display:none;">
             <span class="glyphicon glyphicon-arrow-up"></span>
             <span data-upvotes></span>
@@ -121,25 +121,25 @@
     </div>
 
     <div id="forum-list-container" class="list-group">
-        <?xjs threads.threads.forEach(function (e) { ?>
+        <? threads.threads.forEach(function (e) { ?>
             <a href="./?page=<? Request.write_param('page'); ?>&sub=<? Request.write_param('sub'); ?>&thread=<? write(e.id); ?>" class="list-group-item striped">
                 <div class="row">
                     <div id="left-<? write(e.id); ?>" class="col-sm-9">
                         <strong><? write(e.subject); ?></strong>
-                        <p><strong><? write(e.first.from); ?></strong>, <? write(system.timestr(e.first.when_written_time)); ?></p>
+                        <p><? locale.write('label_thread_from'); ?> <strong><? write(e.first.from); ?></strong> <? locale.write('label_message_date'); ?> <? write(system.timestr(e.first.when_written_time)); ?></p>
                     </div>
                     <div class="col-sm-3">
                         <div id="right-<? write(e.id); ?>" class="pull-right"></div>
                     </div>
                 </div>
             </a>
-        <?xjs }); ?>
+        <? }); ?>
     </div>
 
-    <?xjs if (threads.total / settings.page_size > 1) { ?>
-        <?xjs qs = http_request.query_string.replace(/&offset=\d+/g, ''); ?>
+    <? if (threads.total / settings.page_size > 1) { ?>
+        <? var qs = http_request.query_string.replace(/&offset=\d+/g, ''); ?>
         <div class="btn-group">
-            <?xjs if (offset > 0) { ?>
+            <? if (offset > 0) { ?>
                 <a href="./?<? write(qs); ?>&offset=0" role="button" class="btn btn-default" title="<? locale.write('button_thread_first_page'); ?>">
                     <span class="glyphicon glyphicon-fast-backward"></span>
                 </a>
@@ -149,14 +149,14 @@
                 <a href="./?<? write(qs); ?>&offset=<? write(Math.max(0, offset - 1)); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_previous_page'); ?>">
                     <span class="glyphicon glyphicon-step-backward"></span>
                 </a>
-            <?xjs } ?>
+            <? } ?>
             <a href="./?<? write(qs); ?>&offset=<? write(offset); ?>" role="button" class="btn btn-default" disabled>
                 <? write(offset + 1); ?>
             </a>
-            <?xjs for (var n = offset + 1; n < threads.total / settings.page_size && n - offset < 9; n++) { ?>
+            <? for (var n = offset + 1; n < threads.total / settings.page_size && n - offset < 9; n++) { ?>
                 <a href="./?<? write(qs); ?>&offset=<? write(n); ?>" role="button" class="btn btn-default"><? write(n + 1); ?></a>
-            <?xjs } ?>
-            <?xjs if (threads.total / settings.page_size > offset + 9) { ?>
+            <? } ?>
+            <? if (threads.total / settings.page_size > offset + 9) { ?>
                 <a href="./?<? write(qs); ?>&offset=<? write(offset + 1); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_next_page'); ?>">
                     <span class="glyphicon glyphicon-step-forward"></span>
                 </a>
@@ -166,23 +166,25 @@
                 <a href="./?<? write(qs); ?>&offset=<? write(Math.floor(threads.total / settings.page_size)); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_last_page'); ?>">
                     <span class="glyphicon glyphicon-fast-forward"></span>
                 </a>
-            <?xjs } ?>
+            <? } ?>
         </div class="btn-group">
-    <?xjs } ?>
+    <? } ?>
 
     <script type="text/javascript">
-        registerEventListener('forum', function (e) {
+
+        registerEventListener('forum', e => {
             const data = JSON.parse(e.data);
             if (data.type != 'threads') return;
             onThreadStats(data.data);
         }, {
             threads: '<? Request.write_param("sub"); ?>',
             offset: '<? write(offset); ?>',
-            page_size: '<? write(settings.page_size); ?>'
+            page_size: '<? write(settings.page_size); ?>',
         });
+
     </script>
 
-<?xjs } else if (Request.has_param('group') && typeof msg_area.grp_list[Request.get_param('group')] != 'undefined') { ?>
+<? } else if (Request.has_param('group') && msg_area.grp_list[Request.get_param('group')] !== undefined) { ?>
 
     <ol class="breadcrumb">
         <li>
@@ -198,54 +200,81 @@
     </ol>
 
     <div id="forum-list-container" class="list-group">
-        <?xjs const subs = listSubs(Request.get_param('group')); ?>
-        <?xjs subs.forEach(function (e) { ?>
+        <? var subs = listSubs(Request.get_param('group')); ?>
+        <? subs.forEach(function (e) { ?>
             <a href="./?page=<? Request.write_param('page'); ?>&sub=<? write(e.code); ?>" class="list-group-item striped">
-                <h4><strong><? write(e.name); ?></strong></h4>
+                <h4>
+                    <strong>
+                        <? write(e.name); ?>
+                    </strong>
+                </h4>
                 <span title="<? locale.write('badge_unread_messages'); ?>" class="badge <? write(e.scan_cfg&SCAN_CFG_NEW || e.scan_cfg&SCAN_CFG_YONLY ? 'scanned' : 'total'); ?>" id="badge-<? write(e.code); ?>"></span>
                 <p><? write(e.description); ?></p>
+                <span id="newest-msg-<? write(e.code); ?>"></span>
             </a>
-        <?xjs }); ?>
+        <? }); ?>
     </div>
 
-    <?xjs if (is_user()) { ?>
+    <? if (is_user()) { ?>
         <script type="text/javascript">
-            registerEventListener('forum', function (e) {
+            registerEventListener('forum', e => {
                 const data = JSON.parse(e.data);
                 if (data.type != 'subs_unread') return;
                 onSubUnreadCount(data.data);
              }, { subs_unread: '<? Request.write_param("group"); ?>' });
         </script>
-    <?xjs } ?>
+    <? } ?>
+
+    <script type="text/javascript">
+        (async function getNewestMessagePerSub() {
+            try {
+                const response = await fetch('./api/forum.ssjs?call=get-newest-message-per-sub&group=<? Request.write_param('group'); ?>');
+                const data = await response.json();
+                Object.entries(data).forEach(e => {
+                    document.getElementById(`newest-msg-${e[0]}`).innerHTML = `<? locale.write('label_sub_newest_message'); ?>: ${e[1].subject}, <? locale.write('label_thread_from'); ?> ${e[1].from} <? locale.write('label_message_date'); ?> ${new Date(e[1].when_written_time * 1000)}`;
+                });
+            } catch (err) {
+                console.error('Error fetching latest messages', err);
+            }
+        })();
+    </script>
 
-<?xjs } else { ?>
+<? } else { ?>
 
     <ol class="breadcrumb">
         <li>
-            <a href="./?page=<? Request.write_param('page'); ?>"><? locale.write('title'); ?></a>
+            <a href="./?page=<? Request.write_param('page'); ?>">
+                <? locale.write('title'); ?>
+            </a>
         </li>
     </ol>
 
     <div id="forum-list-container" class="list-group">
-        <?xjs const groups = listGroups(); ?>
-        <?xjs groups.forEach(function (e) { ?>
+        <? const groups = listGroups(); ?>
+        <? groups.forEach(function (e) { ?>
             <a href="./?page=<? Request.write_param('page'); ?>&group=<? write(e.index); ?>" class="list-group-item striped">
-                <h3><strong><? write(e.name); ?></strong></h3>
+                <h3>
+                    <strong>
+                        <? write(e.name); ?>
+                    </strong>
+                </h3>
                 <span title="<? locale.write('badge_unread_messages'); ?>" class="badge ignored" id="badge-ignored-<? write(e.index); ?>"></span>
                 <span title="<? locale.write('badge_unread_messages'); ?>" class="badge scanned" id="badge-scanned-<? write(e.index); ?>"></span>
-                <p><? write(e.description); ?>: <? write(e.sub_count); ?> <? locale.write('sub_boards'); ?></p>
+                <p>
+                    <? write(e.description); ?>: <? write(e.sub_count); ?> <? locale.write('sub_boards'); ?>
+                </p>
             </a>
-        <?xjs }); ?>
+        <? }); ?>
     </div>
 
-    <?xjs if (is_user()) { ?>
+    <? if (is_user()) { ?>
         <script type="text/javascript">
-            registerEventListener('forum', function (e) {
+            registerEventListener('forum', e => {
                 const data = JSON.parse(e.data);
                 if (data.type != 'groups_unread') return;
                 onGroupUnreadCount(data.data);
             }, { groups_unread: null });
         </script>
-    <?xjs } ?>
+    <? } ?>
 
-<?xjs } ?>
+<? } ?>
diff --git a/webv4/root/api/forum.ssjs b/webv4/root/api/forum.ssjs
index 154c36fed6021b45b7b4937a2e29af0488699d09..5eb01d9364e1c2969652be19712ff078f34e3a37 100644
--- a/webv4/root/api/forum.ssjs
+++ b/webv4/root/api/forum.ssjs
@@ -175,6 +175,10 @@ if (Request.has_param('call') && (http_request.method === 'GET' || http_request.
                 }
                 break;
 
+            case 'get-newest-message-per-sub':
+                if (Request.has_param('group')) reply = getNewestMessagePerSub(Request.get_param('group'));
+                break;
+
             default:
                 break;