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`;
|