sync-event toasts, image pull ordering, stale banner field diff

- replace chat-bot sync notifications with a dedicated relay_sync_events
  queue: POST/GET /api/sync-events, drained by /api/relay/poll and
  surfaced as stacking toasts via caldav.js. Old /api/chat/bot removed,
  bot=1 rows purged on bootstrap, orange bot rendering dropped from
  relay UI. Chat thread stays clean.
- collab pull: fetch attachments BEFORE applying the body so the moment
  state.cards reflects the new reference the file is on disk. Fixes the
  "preview shows broken/missing image, editor shows it" race where the
  browser cached the 404 between attachment landing and user re-render.
- detail stale banner now compares title/body/color/tags in addition to
  updated_at — robust against second-granularity timestamp collisions
  and any other case where updated_at doesn't tick but content differs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-14 23:00:30 +02:00
1 parent 26f95ba079
commit 1966f8f4c0
10 files changed
+170 -75

No files matched your search

+27 -7
View File
@@ -892,17 +892,17 @@ if (str_starts_with($route, '/api/')) {
}
$pulled++;
// Send bot notification back to the sender so their chat shows the
// sync confirmation. Best effort — relay may be older and not have
// /api/chat/bot; we don't want to fail the poll on it.
// Fire a sync-event notification to the sender so their hyprcrd
// surfaces a toast. Best-effort — an older relay may not have
// /api/sync-events, and we don't want to fail the whole poll.
$senderName = $msg['sender_username'] ?? '';
if ($senderName && $senderName !== $relayUser) {
$botBody = $relayUser . ' synced: ' . $title;
$bch = curl_init(rtrim($relayUrl, '/') . '/api/chat/bot');
$eventBody = $relayUser . ' synced: ' . $title;
$bch = curl_init(rtrim($relayUrl, '/') . '/api/sync-events');
curl_setopt_array($bch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['recipient' => $senderName, 'body' => $botBody]),
CURLOPT_POSTFIELDS => json_encode(['recipient' => $senderName, 'body' => $eventBody]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Relay-User: ' . $relayUser,
@@ -957,7 +957,27 @@ if (str_starts_with($route, '/api/')) {
}
}
echo json_encode(['ok' => true, 'pulled' => $pulled]); exit;
// Drain pending sync-event notifications so the JS caller can toast
// them. GET /api/sync-events on the relay auto-marks fetched events.
// Best-effort — older relays won't have the endpoint.
$syncEvents = [];
$sch = curl_init(rtrim($relayUrl, '/') . '/api/sync-events');
curl_setopt_array($sch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Relay-User: ' . $relayUser, 'X-Relay-Key: ' . $relayKey],
CURLOPT_TIMEOUT => 5,
]);
$sResp = curl_exec($sch);
$sStatus = curl_getinfo($sch, CURLINFO_HTTP_CODE);
curl_close($sch);
if ($sStatus === 200) {
$decoded = json_decode($sResp, true);
if (is_array($decoded) && isset($decoded['events'])) {
$syncEvents = $decoded['events'];
}
}
echo json_encode(['ok' => true, 'pulled' => $pulled, 'sync_events' => $syncEvents]); exit;
}
// List relay tenants (for the recipient picker)