Changeset 7d19465 in Klonkt for src/services


Ignore:
Timestamp:
07/17/2026 02:27:52 AM (8 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
f1a23b8
Parents:
92a2c46
git-author:
Robin <roboburr@…> (07/17/2026 02:27:34 AM)
git-committer:
Robin <roboburr@…> (07/17/2026 02:27:52 AM)
Message:

Fix: boosted video-only posts keep their media in the Circle (Loops)

Boosting a video-only post lost its media twice over: resolveRemoteNote only
surfaced image/* attachments (images list), so upsertBoostedNote stored
media_json=[] for a Loops video; and its refresh-UPDATE clobbered an existing
good media_json (cached via following) with that empty set. resolveRemoteNote
now also returns full typed media via mediaFromNote (images stays image-only
for the interact preview); upsertBoostedNote prefers the typed media and its
refresh keeps cached media when the resolve yielded none. Verified live: the
reported Loops file is 2.9MB with moov in reach, and a seeded boosted video row
now renders a grid tile with a real generated poster (200 image/webp, 13KB).
Covered by test/boost-media.test.js (63 tests green). Reported by Robin.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r92a2c46 r7d19465  
    18281828    cw: note.summary || '',
    18291829    images,
     1830    // Full typed media (incl. video/mp4) for the timeline cache. `images` above is
     1831    // image-only for the interact page preview; a boosted video-only post (Loops)
     1832    // lost its media entirely because upsertBoostedNote only saw `images`.
     1833    media: mediaFromNote(note),
    18301834    threadInboxes,                                          // every ancestor author's inbox
    18311835    localPostId: localTgt ? localTgt.post_id : '',          // our post this belongs to (if any)
     
    20142018  if (!slug || !note || !note.object_uri) return;
    20152019  const id = note.object_uri;
    2016   const media = JSON.stringify((note.images || []).map((u) => ({ url: u, type: 'image/jpeg' })));
     2020  // Prefer the full typed media (incl. video/mp4 — a Loops boost is video-only and
     2021  // rendered a bare text tile); fall back to the image-only list for older callers.
     2022  const media = (note.media && note.media !== '[]')
     2023    ? note.media
     2024    : JSON.stringify((note.images || []).map((u) => ({ url: u, type: 'image/jpeg' })));
    20172025  try {
    20182026    const r = tlStmts().ins.run(id, slug, note.actor_uri || '', note.actor_name || '', note.actor_handle || '',
     
    20232031      // resolved note. Without this a row cached without its cover (or with
    20242032      // stale content) stayed stale forever — even boosting again didn't heal it.
    2025       db.prepare('UPDATE ap_timeline SET content = ?, media_json = ?, nsfw = ?, cw = ?, url = COALESCE(?, url) WHERE slug = ? AND id = ?')
    2026         .run(note.content || '', media, note.sensitive ? 1 : 0, note.cw || null, note.url || null, slug, id);
     2033      // Keep the CACHED media when the resolve yielded none: an empty re-resolve
     2034      // used to clobber a good media_json (the followed copy had the video, the
     2035      // boost wiped it to []).
     2036      db.prepare(`UPDATE ap_timeline SET content = ?, media_json = CASE WHEN ? = '[]' THEN media_json ELSE ? END,
     2037                  nsfw = ?, cw = ?, url = COALESCE(?, url) WHERE slug = ? AND id = ?`)
     2038        .run(note.content || '', media, media, note.sensitive ? 1 : 0, note.cw || null, note.url || null, slug, id);
    20272039    }
    20282040  } catch { /* ignore */ }
Note: See TracChangeset for help on using the changeset viewer.