birdclaw 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +2 -1
  3. package/src/cli/command-context.ts +17 -0
  4. package/src/cli/register-analysis.ts +500 -0
  5. package/src/cli/register-compose.ts +40 -0
  6. package/src/cli/register-graph.ts +132 -0
  7. package/src/cli/register-inbox.ts +41 -0
  8. package/src/cli/register-storage.ts +106 -0
  9. package/src/cli.ts +30 -750
  10. package/src/components/AccountSwitcher.tsx +7 -15
  11. package/src/components/AvatarChip.tsx +1 -1
  12. package/src/components/AvatarPreload.ts +149 -0
  13. package/src/components/MarkdownCitations.tsx +680 -0
  14. package/src/components/MarkdownViewer.tsx +8 -674
  15. package/src/components/ProfileAnalysisClient.ts +191 -0
  16. package/src/components/ProfileAnalysisStream.tsx +16 -185
  17. package/src/components/ProfilePreview.tsx +2 -0
  18. package/src/components/links-controller.ts +162 -0
  19. package/src/components/links-model.ts +198 -0
  20. package/src/components/network-map-controller.ts +84 -0
  21. package/src/components/network-map-model.ts +255 -0
  22. package/src/components/useTimelineRouteData.ts +105 -235
  23. package/src/lib/analysis-runtime.ts +238 -0
  24. package/src/lib/api-client.ts +16 -215
  25. package/src/lib/api-contracts.ts +328 -0
  26. package/src/lib/archive-import-plan.ts +102 -0
  27. package/src/lib/archive-import.ts +170 -239
  28. package/src/lib/authored-live.ts +75 -120
  29. package/src/lib/backup.ts +335 -424
  30. package/src/lib/blocks-write.ts +30 -26
  31. package/src/lib/blocks.ts +18 -20
  32. package/src/lib/database-metrics.ts +88 -0
  33. package/src/lib/database-migrations.ts +34 -0
  34. package/src/lib/database-schema.ts +312 -0
  35. package/src/lib/database-writer.ts +69 -0
  36. package/src/lib/db.ts +84 -330
  37. package/src/lib/dm-read-model.ts +533 -0
  38. package/src/lib/dms-live.ts +34 -97
  39. package/src/lib/follow-graph.ts +17 -27
  40. package/src/lib/import-repository.ts +138 -0
  41. package/src/lib/inbox.ts +2 -1
  42. package/src/lib/live-sync-engine.ts +209 -0
  43. package/src/lib/live-transport-gateway.ts +128 -0
  44. package/src/lib/mention-threads-live.ts +90 -177
  45. package/src/lib/mentions-export.ts +1 -1
  46. package/src/lib/mentions-live.ts +57 -181
  47. package/src/lib/moderation-target.ts +15 -4
  48. package/src/lib/moderation-write.ts +1 -1
  49. package/src/lib/mutes-write.ts +30 -26
  50. package/src/lib/openai-response-runtime.ts +251 -0
  51. package/src/lib/paginated-sync.ts +93 -0
  52. package/src/lib/period-digest.ts +116 -304
  53. package/src/lib/profile-analysis.ts +36 -110
  54. package/src/lib/queries.ts +6 -2381
  55. package/src/lib/query-actions.ts +437 -0
  56. package/src/lib/query-client.tsx +47 -0
  57. package/src/lib/query-read-model-shared.ts +52 -0
  58. package/src/lib/query-read-models.ts +5 -0
  59. package/src/lib/query-resource.ts +41 -0
  60. package/src/lib/query-status.ts +164 -0
  61. package/src/lib/research.ts +1 -1
  62. package/src/lib/runtime-services.ts +20 -0
  63. package/src/lib/search-discussion.ts +75 -279
  64. package/src/lib/server-runtime-services.ts +30 -0
  65. package/src/lib/sqlite.ts +48 -12
  66. package/src/lib/streaming-ingestion.ts +240 -0
  67. package/src/lib/sync-cache.ts +6 -1
  68. package/src/lib/sync-plan.ts +175 -0
  69. package/src/lib/timeline-collections-live.ts +83 -257
  70. package/src/lib/timeline-live.ts +86 -236
  71. package/src/lib/timeline-read-model.ts +1191 -0
  72. package/src/lib/tweet-repository.ts +156 -0
  73. package/src/lib/tweet-search-live.ts +63 -167
  74. package/src/lib/web-sync.ts +67 -50
  75. package/src/lib/whois.ts +2 -1
  76. package/src/routes/__root.tsx +11 -8
  77. package/src/routes/api/action.tsx +1 -1
  78. package/src/routes/api/conversation.tsx +1 -1
  79. package/src/routes/api/query.tsx +32 -26
  80. package/src/routes/api/status.tsx +6 -4
  81. package/src/routes/api/sync.tsx +5 -2
  82. package/src/routes/blocks.tsx +97 -131
  83. package/src/routes/data-sources.tsx +17 -25
  84. package/src/routes/dms.tsx +167 -184
  85. package/src/routes/inbox.tsx +63 -57
  86. package/src/routes/links.tsx +31 -394
  87. package/src/routes/network-map.tsx +41 -344
  88. package/src/routes/rate-limits.tsx +17 -21
  89. package/src/lib/client-cache.ts +0 -109
@@ -0,0 +1,128 @@
1
+ import { Effect } from "effect";
2
+ import {
3
+ getAuthenticatedBirdAccountEffect,
4
+ listBookmarkedTweetsViaBirdEffect,
5
+ listDirectMessagesViaBirdEffect,
6
+ listFollowUsersViaBirdEffect,
7
+ listHomeTimelineViaBirdEffect,
8
+ listLikedTweetsViaBirdEffect,
9
+ listMentionsViaBirdEffect,
10
+ listThreadViaBirdEffect,
11
+ searchTweetsViaBirdEffect,
12
+ } from "./bird";
13
+ import {
14
+ getTransportStatus,
15
+ getTweetByIdEffect,
16
+ listBookmarkedTweetsViaXurl,
17
+ listDirectMessageEventsViaXurlEffect,
18
+ listFollowUsersViaXurl,
19
+ listHomeTimelineViaXurlEffect,
20
+ listLikedTweetsViaXurl,
21
+ listMentionsViaXurl,
22
+ listUserTweets,
23
+ lookupAuthenticatedOAuth2UserEffect,
24
+ lookupAuthenticatedUser,
25
+ lookupUsersByHandles,
26
+ searchRecentByConversationIdEffect,
27
+ searchRecentTweetsEffect,
28
+ } from "./xurl";
29
+
30
+ export interface BirdReadTransport {
31
+ getAuthenticatedAccount: typeof getAuthenticatedBirdAccountEffect;
32
+ listBookmarks: typeof listBookmarkedTweetsViaBirdEffect;
33
+ listDirectMessages: typeof listDirectMessagesViaBirdEffect;
34
+ listFollowUsers: typeof listFollowUsersViaBirdEffect;
35
+ listHomeTimeline: typeof listHomeTimelineViaBirdEffect;
36
+ listLikes: typeof listLikedTweetsViaBirdEffect;
37
+ listMentions: typeof listMentionsViaBirdEffect;
38
+ listThread: typeof listThreadViaBirdEffect;
39
+ searchTweets: typeof searchTweetsViaBirdEffect;
40
+ }
41
+
42
+ export interface XurlReadTransport {
43
+ getTransportStatus(
44
+ ...args: Parameters<typeof getTransportStatus>
45
+ ): Effect.Effect<Awaited<ReturnType<typeof getTransportStatus>>, Error>;
46
+ getTweetById: typeof getTweetByIdEffect;
47
+ listBookmarks(
48
+ ...args: Parameters<typeof listBookmarkedTweetsViaXurl>
49
+ ): Effect.Effect<
50
+ Awaited<ReturnType<typeof listBookmarkedTweetsViaXurl>>,
51
+ Error
52
+ >;
53
+ listDirectMessages: typeof listDirectMessageEventsViaXurlEffect;
54
+ listFollowUsers(
55
+ ...args: Parameters<typeof listFollowUsersViaXurl>
56
+ ): Effect.Effect<Awaited<ReturnType<typeof listFollowUsersViaXurl>>, Error>;
57
+ listHomeTimeline: typeof listHomeTimelineViaXurlEffect;
58
+ listLikes(
59
+ ...args: Parameters<typeof listLikedTweetsViaXurl>
60
+ ): Effect.Effect<Awaited<ReturnType<typeof listLikedTweetsViaXurl>>, Error>;
61
+ listMentions(
62
+ ...args: Parameters<typeof listMentionsViaXurl>
63
+ ): Effect.Effect<Awaited<ReturnType<typeof listMentionsViaXurl>>, Error>;
64
+ listUserTweets(
65
+ ...args: Parameters<typeof listUserTweets>
66
+ ): Effect.Effect<Awaited<ReturnType<typeof listUserTweets>>, Error>;
67
+ lookupAuthenticatedOAuth2User: typeof lookupAuthenticatedOAuth2UserEffect;
68
+ lookupAuthenticatedUser(
69
+ ...args: Parameters<typeof lookupAuthenticatedUser>
70
+ ): Effect.Effect<Awaited<ReturnType<typeof lookupAuthenticatedUser>>, Error>;
71
+ lookupUsersByHandles(
72
+ ...args: Parameters<typeof lookupUsersByHandles>
73
+ ): Effect.Effect<Awaited<ReturnType<typeof lookupUsersByHandles>>, Error>;
74
+ searchConversation: typeof searchRecentByConversationIdEffect;
75
+ searchRecentTweets: typeof searchRecentTweetsEffect;
76
+ }
77
+
78
+ export interface LiveTransportGateway {
79
+ bird: BirdReadTransport;
80
+ xurl: XurlReadTransport;
81
+ }
82
+
83
+ function fromPromise<T>(run: () => PromiseLike<T>): Effect.Effect<T, Error> {
84
+ return Effect.tryPromise({
85
+ try: run,
86
+ catch: (error) =>
87
+ error instanceof Error ? error : new Error(String(error)),
88
+ });
89
+ }
90
+
91
+ export const liveTransportGateway: LiveTransportGateway = {
92
+ bird: {
93
+ getAuthenticatedAccount: () => getAuthenticatedBirdAccountEffect(),
94
+ listBookmarks: (options) => listBookmarkedTweetsViaBirdEffect(options),
95
+ listDirectMessages: (options) => listDirectMessagesViaBirdEffect(options),
96
+ listFollowUsers: (options) => listFollowUsersViaBirdEffect(options),
97
+ listHomeTimeline: (options) => listHomeTimelineViaBirdEffect(options),
98
+ listLikes: (options) => listLikedTweetsViaBirdEffect(options),
99
+ listMentions: (options) => listMentionsViaBirdEffect(options),
100
+ listThread: (options) => listThreadViaBirdEffect(options),
101
+ searchTweets: (query, options) => searchTweetsViaBirdEffect(query, options),
102
+ },
103
+ xurl: {
104
+ getTransportStatus: (...args) =>
105
+ fromPromise(() => getTransportStatus(...args)),
106
+ getTweetById: (id, options) => getTweetByIdEffect(id, options),
107
+ listBookmarks: (...args) =>
108
+ fromPromise(() => listBookmarkedTweetsViaXurl(...args)),
109
+ listDirectMessages: (options) =>
110
+ listDirectMessageEventsViaXurlEffect(options),
111
+ listFollowUsers: (...args) =>
112
+ fromPromise(() => listFollowUsersViaXurl(...args)),
113
+ listHomeTimeline: (options) => listHomeTimelineViaXurlEffect(options),
114
+ listLikes: (...args) => fromPromise(() => listLikedTweetsViaXurl(...args)),
115
+ listMentions: (...args) => fromPromise(() => listMentionsViaXurl(...args)),
116
+ listUserTweets: (...args) => fromPromise(() => listUserTweets(...args)),
117
+ lookupAuthenticatedOAuth2User: (username) =>
118
+ lookupAuthenticatedOAuth2UserEffect(username),
119
+ lookupAuthenticatedUser: (...args) =>
120
+ fromPromise(() => lookupAuthenticatedUser(...args)),
121
+ lookupUsersByHandles: (...args) =>
122
+ fromPromise(() => lookupUsersByHandles(...args)),
123
+ searchConversation: (conversationId, options) =>
124
+ searchRecentByConversationIdEffect(conversationId, options),
125
+ searchRecentTweets: (query, options) =>
126
+ searchRecentTweetsEffect(query, options),
127
+ },
128
+ };
@@ -1,10 +1,11 @@
1
1
  import type { Database } from "./sqlite";
2
2
  import { Effect } from "effect";
3
- import { listThreadViaBirdEffect } from "./bird";
3
+ import { databaseWriteEffect } from "./database-writer";
4
4
  import { getNativeDb } from "./db";
5
5
  import { runEffectPromise } from "./effect-runtime";
6
- import { buildMediaJsonFromIncludes, countTweetMedia } from "./media-includes";
7
- import { tweetEntitiesFromXurl } from "./tweet-render";
6
+ import { liveTransportGateway } from "./live-transport-gateway";
7
+ import { resolveLiveSyncAccount } from "./live-sync-engine";
8
+ import { runSyncPlanEffect } from "./sync-plan";
8
9
  import type {
9
10
  XurlMentionData,
10
11
  XurlMentionsResponse,
@@ -12,9 +13,7 @@ import type {
12
13
  XurlMediaItem,
13
14
  XurlTweetsResponse,
14
15
  } from "./types";
15
- import { upsertTweetAccountEdge } from "./tweet-account-edges";
16
- import { ensureStubProfileForXUser, upsertProfileFromXUser } from "./x-profile";
17
- import { getTweetByIdEffect, searchRecentByConversationIdEffect } from "./xurl";
16
+ import { ingestTweetPayload } from "./tweet-repository";
18
17
 
19
18
  const DEFAULT_LIMIT = 30;
20
19
  const DEFAULT_DELAY_MS = 1500;
@@ -107,40 +106,6 @@ function getRemainingThreadTimeoutMs(
107
106
  }
108
107
  return remainingMs;
109
108
  }
110
- function replaceTweetFts(db: Database, tweetId: string, text: string) {
111
- db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
112
- db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(
113
- tweetId,
114
- text,
115
- );
116
- }
117
-
118
- function resolveAccount(db: Database, accountId?: string) {
119
- const row = accountId
120
- ? (db
121
- .prepare("select id, handle from accounts where id = ?")
122
- .get(accountId) as { id: string; handle: string } | undefined)
123
- : (db
124
- .prepare(
125
- `
126
- select id, handle
127
- from accounts
128
- order by is_default desc, created_at asc
129
- limit 1
130
- `,
131
- )
132
- .get() as { id: string; handle: string } | undefined);
133
-
134
- if (!row) {
135
- throw new Error(`Unknown account: ${accountId ?? "default"}`);
136
- }
137
-
138
- return {
139
- accountId: row.id,
140
- handle: row.handle.replace(/^@/, "").toLowerCase(),
141
- };
142
- }
143
-
144
109
  function getReplyToId(tweet: XurlMentionData) {
145
110
  return tweet.referenced_tweets?.find((entry) => entry.type === "replied_to")
146
111
  ?.id;
@@ -354,84 +319,32 @@ function mergeMentionThreadIntoLocalStore({
354
319
  const usersById = new Map(
355
320
  (payload.includes?.users ?? []).map((user) => [user.id, user]),
356
321
  );
357
- const upsertTweet = db.prepare(
358
- `
359
- insert into tweets (
360
- id, account_id, author_profile_id, kind, text, created_at,
361
- is_replied, reply_to_id, like_count, media_count, bookmarked, liked,
362
- entities_json, media_json, quoted_tweet_id
363
- ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, null)
364
- on conflict(id) do update set
365
- account_id = excluded.account_id,
366
- author_profile_id = excluded.author_profile_id,
367
- kind = case
368
- when tweets.kind in ('authored', 'home', 'mention') then tweets.kind
369
- when excluded.kind in ('authored', 'home', 'mention') then excluded.kind
370
- else coalesce(nullif(tweets.kind, ''), excluded.kind)
371
- end,
372
- text = excluded.text,
373
- created_at = excluded.created_at,
374
- is_replied = max(tweets.is_replied, excluded.is_replied),
375
- reply_to_id = coalesce(excluded.reply_to_id, tweets.reply_to_id),
376
- like_count = excluded.like_count,
377
- media_count = max(tweets.media_count, excluded.media_count),
378
- entities_json = excluded.entities_json,
379
- media_json = case
380
- when excluded.media_json not in ('', '[]', 'null') then excluded.media_json
381
- else tweets.media_json
382
- end,
383
- bookmarked = tweets.bookmarked,
384
- liked = tweets.liked
385
- `,
386
- );
387
-
388
- db.transaction(() => {
389
- const seenAt = new Date().toISOString();
390
- for (const tweet of payload.data) {
391
- const author =
392
- usersById.get(tweet.author_id) ??
393
- ({
394
- id: tweet.author_id,
395
- username: `user_${tweet.author_id}`,
396
- name: `user_${tweet.author_id}`,
397
- } as const);
398
- const profile = usersById.has(tweet.author_id)
399
- ? upsertProfileFromXUser(db, author)
400
- : ensureStubProfileForXUser(db, tweet.author_id);
401
- const handle = author.username.toLowerCase();
402
- const kind = mentionIds.has(tweet.id)
403
- ? "mention"
404
- : handle === accountHandle
405
- ? "home"
406
- : "thread";
407
- const replyToId = getReplyToId(tweet);
408
- upsertTweet.run(
409
- tweet.id,
410
- accountId,
411
- profile.profile.id,
412
- kind,
413
- tweet.text,
414
- tweet.created_at,
415
- replyToId ? 1 : 0,
416
- replyToId ?? null,
417
- Number(tweet.public_metrics?.like_count ?? 0),
418
- countTweetMedia(tweet),
419
- JSON.stringify(tweetEntitiesFromXurl(tweet.entities)),
420
- buildMediaJsonFromIncludes(tweet, payload.includes?.media),
421
- );
422
- if (writeThreadContextEdges) {
423
- upsertTweetAccountEdge(db, {
424
- accountId,
425
- tweetId: tweet.id,
426
- kind: "thread_context",
427
- source,
428
- seenAt,
429
- rawJson: JSON.stringify(tweet),
430
- });
431
- }
432
- replaceTweetFts(db, tweet.id, tweet.text);
433
- }
434
- })();
322
+ const groups = new Map<"mention" | "home" | "thread", typeof payload.data>([
323
+ ["mention", []],
324
+ ["home", []],
325
+ ["thread", []],
326
+ ]);
327
+ for (const tweet of payload.data) {
328
+ const handle = usersById.get(tweet.author_id)?.username.toLowerCase();
329
+ const kind = mentionIds.has(tweet.id)
330
+ ? "mention"
331
+ : handle === accountHandle
332
+ ? "home"
333
+ : "thread";
334
+ groups.get(kind)?.push(tweet);
335
+ }
336
+ for (const [kind, data] of groups) {
337
+ if (data.length === 0) continue;
338
+ ingestTweetPayload(db, {
339
+ accountId,
340
+ payload: { ...payload, data },
341
+ kind,
342
+ edgeKind: writeThreadContextEdges ? "thread_context" : undefined,
343
+ markRepliesAsReplied: true,
344
+ replaceSecondaryKind: true,
345
+ source,
346
+ });
347
+ }
435
348
  }
436
349
 
437
350
  function fetchConversationViaRecentSearchEffect({
@@ -448,39 +361,29 @@ function fetchConversationViaRecentSearchEffect({
448
361
  deadlineMs: number;
449
362
  }) {
450
363
  return Effect.gen(function* () {
451
- const pages: XurlTweetsResponse[] = [];
452
- let nextToken: string | undefined;
453
- let pageCount = 0;
454
-
455
- do {
456
- const payload = yield* searchRecentByConversationIdEffect(
457
- conversationId,
458
- {
459
- maxResults: MAX_XURL_SEARCH_RESULTS,
460
- paginationToken: nextToken,
461
- timeoutMs: yield* trySync(() =>
462
- getRemainingThreadTimeoutMs(deadlineMs, timeoutMs),
364
+ const result = yield* runSyncPlanEffect({
365
+ fetchPage: ({ cursor }) =>
366
+ trySync(() => getRemainingThreadTimeoutMs(deadlineMs, timeoutMs)).pipe(
367
+ Effect.flatMap((remainingTimeoutMs) =>
368
+ liveTransportGateway.xurl.searchConversation(conversationId, {
369
+ maxResults: MAX_XURL_SEARCH_RESULTS,
370
+ paginationToken: cursor,
371
+ timeoutMs: remainingTimeoutMs,
372
+ }),
463
373
  ),
464
- },
465
- );
466
- pages.push(payload);
467
- nextToken =
468
- typeof payload.meta?.next_token === "string"
469
- ? payload.meta.next_token
470
- : undefined;
471
- pageCount += 1;
472
- } while (
473
- (all || maxPages !== undefined) &&
474
- nextToken &&
475
- (maxPages === undefined || pageCount < maxPages)
476
- );
477
-
478
- const payload = mergePayloads(pages);
374
+ ),
375
+ getNextCursor: (page) =>
376
+ typeof page.meta?.next_token === "string"
377
+ ? page.meta.next_token
378
+ : undefined,
379
+ maxPages: all || maxPages !== undefined ? maxPages : 1,
380
+ });
381
+ const payload = mergePayloads(result.pages);
479
382
  const paginationRequested = all || maxPages !== undefined;
480
383
  return {
481
384
  payload,
482
- pages: pageCount,
483
- truncated: paginationRequested && Boolean(nextToken),
385
+ pages: result.pages.length,
386
+ truncated: paginationRequested && Boolean(result.nextCursor),
484
387
  generalReadTweets: payload.data.length,
485
388
  };
486
389
  });
@@ -512,11 +415,14 @@ function fetchParentChainViaXurlEffect({
512
415
  let shouldUseRawAnchor = Boolean(rawAnchorPayload);
513
416
 
514
417
  if (!nextParentId) {
515
- const anchorPayload = yield* getTweetByIdEffect(mention.id, {
516
- timeoutMs: yield* trySync(() =>
517
- getRemainingThreadTimeoutMs(deadlineMs, timeoutMs),
518
- ),
519
- });
418
+ const anchorPayload = yield* liveTransportGateway.xurl.getTweetById(
419
+ mention.id,
420
+ {
421
+ timeoutMs: yield* trySync(() =>
422
+ getRemainingThreadTimeoutMs(deadlineMs, timeoutMs),
423
+ ),
424
+ },
425
+ );
520
426
  pages.push(anchorPayload);
521
427
  generalReadTweets += anchorPayload.data.length;
522
428
  const anchorTweet = anchorPayload.data[0];
@@ -548,11 +454,14 @@ function fetchParentChainViaXurlEffect({
548
454
  }
549
455
 
550
456
  fallbackDepth += 1;
551
- const parentPayload = yield* getTweetByIdEffect(nextParentId, {
552
- timeoutMs: yield* trySync(() =>
553
- getRemainingThreadTimeoutMs(deadlineMs, timeoutMs),
554
- ),
555
- });
457
+ const parentPayload = yield* liveTransportGateway.xurl.getTweetById(
458
+ nextParentId,
459
+ {
460
+ timeoutMs: yield* trySync(() =>
461
+ getRemainingThreadTimeoutMs(deadlineMs, timeoutMs),
462
+ ),
463
+ },
464
+ );
556
465
  pages.push(parentPayload);
557
466
  generalReadTweets += parentPayload.data.length;
558
467
  const parentTweet = parentPayload.data[0];
@@ -743,7 +652,9 @@ export function syncMentionThreadsEffect({
743
652
  parseNonNegativeInteger(maxPages, "--max-pages"),
744
653
  );
745
654
  const db = yield* trySync(() => getNativeDb());
746
- const resolvedAccount = yield* trySync(() => resolveAccount(db, account));
655
+ const resolvedAccount = yield* trySync(() =>
656
+ resolveLiveSyncAccount(db, account),
657
+ );
747
658
  const mentions = yield* trySync(() =>
748
659
  tweetIds
749
660
  ? listMentionsByIds(
@@ -779,22 +690,24 @@ export function syncMentionThreadsEffect({
779
690
  }
780
691
  const fetchEffect: Effect.Effect<ThreadFetchResult, unknown, never> =
781
692
  parsedMode === "bird"
782
- ? listThreadViaBirdEffect({
783
- tweetId: mention.id,
784
- all,
785
- maxPages: parsedMaxPages,
786
- timeoutMs: parsedTimeoutMs,
787
- }).pipe(
788
- Effect.map((payload) => ({
789
- strategy: "bird" as const,
790
- payload,
791
- pages: undefined,
792
- fallbackDepth: undefined,
793
- generalReadTweets: 0,
794
- truncated: undefined,
795
- warnings: [] as string[],
796
- })),
797
- )
693
+ ? liveTransportGateway.bird
694
+ .listThread({
695
+ tweetId: mention.id,
696
+ all,
697
+ maxPages: parsedMaxPages,
698
+ timeoutMs: parsedTimeoutMs,
699
+ })
700
+ .pipe(
701
+ Effect.map((payload) => ({
702
+ strategy: "bird" as const,
703
+ payload,
704
+ pages: undefined,
705
+ fallbackDepth: undefined,
706
+ generalReadTweets: 0,
707
+ truncated: undefined,
708
+ warnings: [] as string[],
709
+ })),
710
+ )
798
711
  : fetchThreadContextViaXurlEffect({
799
712
  mention,
800
713
  all,
@@ -804,11 +717,11 @@ export function syncMentionThreadsEffect({
804
717
  });
805
718
  const fetched = yield* fetchEffect.pipe(
806
719
  Effect.flatMap((fetchResult) =>
807
- trySync(() =>
720
+ databaseWriteEffect((writeDb) =>
808
721
  mergeMentionThreadIntoLocalStore({
809
- db,
722
+ db: writeDb,
810
723
  accountId: resolvedAccount.accountId,
811
- accountHandle: resolvedAccount.handle,
724
+ accountHandle: resolvedAccount.username.toLowerCase(),
812
725
  mentionIds: mentionIdSet,
813
726
  payload: fetchResult.payload,
814
727
  source: parsedMode,
@@ -1,4 +1,4 @@
1
- import { listTimelineItems } from "./queries";
1
+ import { listTimelineItems } from "./timeline-read-model";
2
2
  import { renderTweetMarkdown, renderTweetPlainText } from "./tweet-render";
3
3
  import type {
4
4
  ReplyFilter,