Skip to content

QuotedTweetSnapshot drops most fields ft already extracts for outer tweets #146

@RealADemin

Description

@RealADemin

QuotedTweetSnapshot was introduced in 6ef3f42 (April 2026) as the minimum-viable shape needed to render quoted tweets — handle, name, text, media, post date. PR #134 (merged 2026-05-02) refined preservation across syncs but didn't expand the captured fields. The shape hasn't been revisited despite ft's parser already extracting much richer data for the outer-tweet BookmarkRecord from the same response.

The quoted_status_result payload contains the same legacy.* and core.user_results.result.* paths that convertTweetToRecord parses for outer tweets at src/graphql-bookmarks.ts:255-376. Currently those paths are read for outer and silently dropped when constructing the quoted snapshot at lines 295-320. This issue proposes mirroring the extraction.

The asymmetry

ft extracts the following from the outer tweet's response. None are propagated to QuotedTweetSnapshot:

Outer extracts (BookmarkRecord) On QuotedTweetSnapshot?
engagement: { likeCount, repostCount, replyCount, quoteCount, bookmarkCount, viewCount }
conversationId
inReplyToStatusId, inReplyToUserId
quotedStatusId (chain detection)
language
sourceApp
possiblySensitive
links: string[] (from legacy.entities.urls) + t.co→display_url expansion of text
Full BookmarkAuthorSnapshot (incl. verification fields once #145 ships) ✗ — has flat authorHandle/authorName/authorProfileImageUrl only

All of these come from legacy.* and core.user_results.result.* paths that exist on quoted_status_result.result.* at the same nesting depth — verified May 2026 against live captures.

Concrete failing cases

Verified against live X API:

  1. Article filter cannot detect quote-articles. Bookmark 2049874687069426008 is a quote-tweet whose quoted tweet's text is a single https://t.co/JlZbH7mCPt, resolving via 301 to x.com/i/article/2049760065427574784 (a real X Article). Downstream article-detection relies on links arrays; QuotedTweetSnapshot has none, so the filter is blind to article presence inside quotes.

  2. Thread badges absent on quoted tweets. conversationId is the disambiguator for "is this part of a thread?" Outer tweets get it; quoted tweets don't. Quoted tweets that ARE in threads stay un-badged.

  3. View counts dropped on quoted tweets. Once view_count always NULL — view_counts_everywhere_api_enabled flag missing from GRAPHQL_FEATURES #144 lands and views.count populates for outer, quoted tweets stay at zero — QuotedTweetSnapshot has no engagement field to receive it.

  4. Auto-prepended @-mentions misattributed. Quoted text like "@user1 @user2 hello world" — without display_text_range, downstream consumers can't tell which leading @-handles were typed by the quoted author vs. auto-prepended by Twitter as reply targets. The same gap exists on outer BookmarkRecord currently — this issue subsumes the outer case as well, since the parsing is identical.

Suggested fix

Layer 1 — type + parser (≈25 lines, no schema work)

Extend QuotedTweetSnapshot at src/types.ts:43-53 to mirror BookmarkRecord:

export interface QuotedTweetSnapshot {
  id: string;
  text: string;
  url: string;
  postedAt?: string | null;
  media?: string[];
  mediaObjects?: BookmarkMediaObject[];

  // existing flat author fields can stay (additive, non-breaking)
  authorHandle?: string;
  authorName?: string;
  authorProfileImageUrl?: string;

  // NEW — parity with BookmarkRecord
  conversationId?: string;
  inReplyToStatusId?: string;
  inReplyToUserId?: string;
  quotedStatusId?: string;
  language?: string;
  sourceApp?: string;
  possiblySensitive?: boolean;
  displayTextRange?: [number, number];
  links?: string[];
  engagement?: BookmarkEngagementSnapshot;
  author?: BookmarkAuthorSnapshot;             // additive; existing flat fields stay populated
}

Parser-side: mirror lines 343-376 of the outer-tweet construction inside the quoted-tweet construction at lines 307-330. Same code, applied to qtTweet/qtLegacy. BookmarkEngagementSnapshot (line 34-41 of types.ts) and BookmarkAuthorSnapshot (extended by #145) are reusable as-is.

Adding author alongside the flat fields keeps the change additive — no breaking changes for downstream consumers reading authorHandle/authorName/authorProfileImageUrl. Both populate from the same qtTweet.core.user_results.result.

Also add displayTextRange?: [number, number] to BookmarkRecord (not just QuotedTweetSnapshot) — same field, same parsing. Single line in the outer construction at line 343 area:

displayTextRange: legacy?.display_text_range,

This subsumes any separate proposal to add display_text_range for outer tweets only.

Layer 2 — SQLite columns (optional)

QuotedTweetSnapshot is already serialized as quoted_tweet_json TEXT (added in schema v4 with 6ef3f42). Layer 1 lands automatically without schema changes — the new fields just become additional keys in the existing JSON blob.

For BookmarkRecord.displayTextRange, optionally add a display_text_range TEXT column if per-bookmark queryability is desired. Not required for the fix.

Dependencies

This issue assumes #144 and #145 land:

This issue can be drafted/reviewed before either dependency lands but data quality depends on both being resolved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions