From 4a668b7fc417c27a41e1f6781d36b3f33eee828f Mon Sep 17 00:00:00 2001 From: th Date: Tue, 14 Apr 2026 23:16:48 +0200 Subject: [PATCH] fixes: timeAgo utc parse, reload clears unread, sync-event debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - detail banner timeAgo: SQLite timestamps are UTC without tz marker, JS was parsing them as local. Fixed by appending Z when absent — same pattern chat uses. "2h ago" for a 30s-old update is gone. - banner Reload button now re-fires detail.opened so the collab-buckets plugin clears the red dot on the board card and calls mark-card-seen. Dismiss (×) still leaves the dot — that's the acknowledgement that nothing was actually read. - instrument /api/relay/poll with a sync_debug field so the JS can console.debug what happened on the sync-events sub-request (status, count, error body). Helps diagnose "no toast" reports. Co-Authored-By: Claude Opus 4.6 (1M context) --- config.php | 2 +- electron/package.json | 2 +- public/index.php | 9 ++++++++- public/js/modules/caldav.js | 3 +++ public/js/modules/detail.js | 14 +++++++++++++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/config.php b/config.php index 7b5d141..9946605 100644 --- a/config.php +++ b/config.php @@ -1,7 +1,7 @@ true]; $sch = curl_init(rtrim($relayUrl, '/') . '/api/sync-events'); curl_setopt_array($sch, [ CURLOPT_RETURNTRANSFER => true, @@ -970,14 +971,20 @@ if (str_starts_with($route, '/api/')) { $sResp = curl_exec($sch); $sStatus = curl_getinfo($sch, CURLINFO_HTTP_CODE); curl_close($sch); + $syncDebug['status'] = $sStatus; if ($sStatus === 200) { $decoded = json_decode($sResp, true); if (is_array($decoded) && isset($decoded['events'])) { $syncEvents = $decoded['events']; + $syncDebug['count'] = count($syncEvents); + } else { + $syncDebug['parse_error'] = substr((string)$sResp, 0, 200); } + } else { + $syncDebug['body'] = substr((string)$sResp, 0, 200); } - echo json_encode(['ok' => true, 'pulled' => $pulled, 'sync_events' => $syncEvents]); exit; + echo json_encode(['ok' => true, 'pulled' => $pulled, 'sync_events' => $syncEvents, 'sync_debug' => $syncDebug]); exit; } // List relay tenants (for the recipient picker) diff --git a/public/js/modules/caldav.js b/public/js/modules/caldav.js index 829de4a..4a0337d 100644 --- a/public/js/modules/caldav.js +++ b/public/js/modules/caldav.js @@ -340,6 +340,9 @@ function showSyncEventToast(ev) { } function drainSyncEvents(relayRes) { + if (relayRes?.sync_debug) { + console.debug('[sync-events]', relayRes.sync_debug); + } const events = Array.isArray(relayRes?.sync_events) ? relayRes.sync_events : []; for (const ev of events) showSyncEventToast(ev); } diff --git a/public/js/modules/detail.js b/public/js/modules/detail.js index 10bb60c..995cbcc 100644 --- a/public/js/modules/detail.js +++ b/public/js/modules/detail.js @@ -116,6 +116,10 @@ function showStaleBanner(fresh) { state.viewingCard = current; renderSidebar(current); showContent(current); + // Treat the reload as an explicit "I've now seen the new version" + // — re-fire detail.opened so collab-buckets clears the red dot + // on the board card and persists mark-card-seen to the backend. + window.hyprcrd?.hooks?.emit?.('detail.opened', { id: current.id }); } dismissStaleBanner(); }); @@ -123,7 +127,15 @@ function showStaleBanner(fresh) { } function timeAgo(iso) { - const t = new Date(iso).getTime(); + if (!iso) return ''; + // SQLite stores CURRENT_TIMESTAMP as UTC without a tz marker + // ("2026-04-14 12:34:56"). Without an explicit Z, JS parses it as + // local time — off by the tz offset (2h on CEST, hence "2h ago" + // for a 30-second-old update). + const normalized = String(iso).includes('T') || /[Zz]|[+-]\d\d:?\d\d$/.test(iso) + ? iso + : iso.replace(' ', 'T') + 'Z'; + const t = new Date(normalized).getTime(); if (!isFinite(t)) return ''; const s = Math.max(0, (Date.now() - t) / 1000); if (s < 60) return `${Math.floor(s)}s ago`;