birdclaw 0.4.1 → 0.5.1

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 (85) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +113 -7
  3. package/bin/birdclaw.mjs +50 -11
  4. package/package.json +30 -28
  5. package/playwright.config.ts +1 -0
  6. package/public/birdclaw-mark.png +0 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/logo192.png +0 -0
  9. package/public/logo512.png +0 -0
  10. package/public/manifest.json +2 -2
  11. package/scripts/browser-perf.mjs +399 -0
  12. package/scripts/build-docs-site.mjs +940 -0
  13. package/scripts/docs-site-assets.mjs +311 -0
  14. package/scripts/run-vitest.mjs +21 -0
  15. package/scripts/sanitize-node-options.mjs +23 -0
  16. package/scripts/start-test-server.mjs +29 -0
  17. package/src/cli.ts +496 -19
  18. package/src/components/AppNav.tsx +66 -29
  19. package/src/components/AvatarChip.tsx +10 -5
  20. package/src/components/BrandMark.tsx +67 -0
  21. package/src/components/ConversationThread.tsx +126 -0
  22. package/src/components/DmWorkspace.tsx +118 -105
  23. package/src/components/EmbeddedTweetCard.tsx +20 -14
  24. package/src/components/FeedState.tsx +147 -0
  25. package/src/components/InboxCard.tsx +104 -90
  26. package/src/components/LinkPreviewCard.tsx +270 -0
  27. package/src/components/ProfilePreview.tsx +8 -3
  28. package/src/components/SavedTimelineView.tsx +89 -71
  29. package/src/components/SyncNowButton.tsx +105 -0
  30. package/src/components/ThemeSlider.tsx +10 -59
  31. package/src/components/TimelineCard.tsx +326 -86
  32. package/src/components/TimelineRouteFrame.tsx +156 -0
  33. package/src/components/TweetMediaGrid.tsx +120 -23
  34. package/src/components/TweetRichText.tsx +19 -4
  35. package/src/components/useTimelineRouteData.ts +137 -0
  36. package/src/lib/api-client.ts +229 -0
  37. package/src/lib/archive-finder.ts +24 -20
  38. package/src/lib/archive-import.ts +1582 -67
  39. package/src/lib/authored-live.ts +1074 -0
  40. package/src/lib/backup.ts +316 -14
  41. package/src/lib/bird-actions.ts +1 -10
  42. package/src/lib/bird-command.ts +57 -0
  43. package/src/lib/bird.ts +89 -5
  44. package/src/lib/config.ts +1 -1
  45. package/src/lib/conversation-surface.ts +174 -0
  46. package/src/lib/db.ts +193 -4
  47. package/src/lib/follow-graph.ts +1053 -0
  48. package/src/lib/link-index.ts +11 -98
  49. package/src/lib/link-insights.ts +834 -0
  50. package/src/lib/link-preview-metadata.ts +334 -0
  51. package/src/lib/media-fetch.ts +787 -0
  52. package/src/lib/media-includes.ts +165 -0
  53. package/src/lib/mention-threads-live.ts +535 -43
  54. package/src/lib/mentions-live.ts +623 -19
  55. package/src/lib/moderation-target.ts +6 -0
  56. package/src/lib/profile-hydration.ts +1 -1
  57. package/src/lib/profile-resolver.ts +115 -1
  58. package/src/lib/queries.ts +326 -35
  59. package/src/lib/seed.ts +127 -8
  60. package/src/lib/timeline-collections-live.ts +145 -22
  61. package/src/lib/timeline-live.ts +10 -15
  62. package/src/lib/tweet-account-edges.ts +9 -2
  63. package/src/lib/tweet-render.ts +97 -0
  64. package/src/lib/types.ts +185 -2
  65. package/src/lib/ui.ts +383 -147
  66. package/src/lib/url-expansion-store.ts +110 -0
  67. package/src/lib/url-expansion.ts +20 -3
  68. package/src/lib/web-sync.ts +443 -0
  69. package/src/lib/x-profile.ts +7 -2
  70. package/src/lib/xurl.ts +296 -12
  71. package/src/routeTree.gen.ts +126 -0
  72. package/src/routes/__root.tsx +7 -3
  73. package/src/routes/api/conversation.tsx +34 -0
  74. package/src/routes/api/link-insights.tsx +79 -0
  75. package/src/routes/api/link-preview.tsx +43 -0
  76. package/src/routes/api/profile-hydrate.tsx +51 -0
  77. package/src/routes/api/sync.tsx +59 -0
  78. package/src/routes/blocks.tsx +111 -86
  79. package/src/routes/dms.tsx +172 -87
  80. package/src/routes/inbox.tsx +98 -86
  81. package/src/routes/index.tsx +22 -115
  82. package/src/routes/links.tsx +928 -0
  83. package/src/routes/mentions.tsx +22 -115
  84. package/src/styles.css +169 -43
  85. package/vite.config.ts +8 -0
@@ -1,5 +1,9 @@
1
1
  import { getNativeDb } from "./db";
2
2
  import type { Database } from "./sqlite";
3
+ import {
4
+ normalizeUrlExpansionForIndex,
5
+ upsertUrlExpansion,
6
+ } from "./url-expansion-store";
3
7
  import {
4
8
  expandUrls,
5
9
  extractUrls,
@@ -95,101 +99,6 @@ function isIndexedUrl(url: string, includeAllUrls: boolean) {
95
99
  }
96
100
  }
97
101
 
98
- function getTweetTarget(url: string) {
99
- try {
100
- const parsed = new URL(url);
101
- const host = parsed.hostname.toLowerCase();
102
- if (
103
- host !== "x.com" &&
104
- host !== "twitter.com" &&
105
- host !== "mobile.twitter.com" &&
106
- host !== "www.x.com" &&
107
- host !== "www.twitter.com"
108
- ) {
109
- return {};
110
- }
111
-
112
- const parts = parsed.pathname.split("/").filter(Boolean);
113
- const statusIndex = parts.findIndex(
114
- (part) => part === "status" || part === "statuses",
115
- );
116
- if (statusIndex === -1 || !parts[statusIndex + 1]) {
117
- return {};
118
- }
119
-
120
- const tweetId = parts[statusIndex + 1]?.match(/^\d+/)?.[0];
121
- const handle =
122
- parts[statusIndex - 1] && parts[statusIndex - 1] !== "i"
123
- ? parts[statusIndex - 1]
124
- : undefined;
125
- return {
126
- expandedTweetId: tweetId,
127
- expandedHandle: handle,
128
- };
129
- } catch {
130
- return {};
131
- }
132
- }
133
-
134
- function normalizeExpansion(item: {
135
- url: string;
136
- expandedUrl: string;
137
- finalUrl: string;
138
- status: "hit" | "miss" | "error";
139
- title?: string;
140
- description?: string | null;
141
- error?: string;
142
- source: string;
143
- updatedAt: string;
144
- }): LinkIndexItem {
145
- const target = getTweetTarget(item.finalUrl);
146
- return {
147
- shortUrl: item.url,
148
- expandedUrl: item.expandedUrl,
149
- finalUrl: item.finalUrl,
150
- status: item.status,
151
- expandedTweetId: target.expandedTweetId ?? null,
152
- expandedHandle: target.expandedHandle ?? null,
153
- title: item.title ?? null,
154
- description: item.description ?? null,
155
- error: item.error ?? null,
156
- source: item.source,
157
- updatedAt: item.updatedAt,
158
- };
159
- }
160
-
161
- function upsertExpansion(db: Database, item: LinkIndexItem) {
162
- db.prepare(`
163
- insert into url_expansions (
164
- short_url, expanded_url, final_url, status, expanded_tweet_id,
165
- expanded_handle, title, description, error, source, updated_at
166
- ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
167
- on conflict(short_url) do update set
168
- expanded_url = excluded.expanded_url,
169
- final_url = excluded.final_url,
170
- status = excluded.status,
171
- expanded_tweet_id = excluded.expanded_tweet_id,
172
- expanded_handle = excluded.expanded_handle,
173
- title = excluded.title,
174
- description = excluded.description,
175
- error = excluded.error,
176
- source = excluded.source,
177
- updated_at = excluded.updated_at
178
- `).run(
179
- item.shortUrl,
180
- item.expandedUrl,
181
- item.finalUrl,
182
- item.status,
183
- item.expandedTweetId,
184
- item.expandedHandle,
185
- item.title,
186
- item.description,
187
- item.error,
188
- item.source,
189
- item.updatedAt,
190
- );
191
- }
192
-
193
102
  function toTweetEntityUrls(entitiesJson: string): SourceUrl[] {
194
103
  const entities = parseJsonField<TweetEntities>(entitiesJson, {});
195
104
  const urls = Array.isArray(entities.urls) ? entities.urls : [];
@@ -315,9 +224,9 @@ function rebuildOccurrences(
315
224
  );
316
225
  occurrences++;
317
226
  if (entry.expandedUrl) {
318
- upsertExpansion(
227
+ upsertUrlExpansion(
319
228
  db,
320
- normalizeExpansion({
229
+ normalizeUrlExpansionForIndex({
321
230
  url: entry.url,
322
231
  expandedUrl: entry.expandedUrl,
323
232
  finalUrl: entry.expandedUrl,
@@ -379,7 +288,7 @@ async function expandWithConcurrency(
379
288
  if (result.status === "error") {
380
289
  counts.errors++;
381
290
  }
382
- upsertExpansion(db, normalizeExpansion(result));
291
+ upsertUrlExpansion(db, normalizeUrlExpansionForIndex(result));
383
292
  }
384
293
  }
385
294
 
@@ -532,6 +441,8 @@ function toLinkSearchItem(row: Record<string, unknown>): LinkSearchItem {
532
441
  expandedHandle: getString(row.expanded_handle) ?? null,
533
442
  title: getString(row.title) ?? null,
534
443
  description: getString(row.description) ?? null,
444
+ imageUrl: getString(row.image_url) ?? null,
445
+ siteName: getString(row.site_name) ?? null,
535
446
  error: getString(row.error) ?? null,
536
447
  source: String(row.expansion_source),
537
448
  updatedAt: String(row.updated_at),
@@ -630,6 +541,8 @@ export function searchLinks(query: string, options: LinkSearchOptions = {}) {
630
541
  e.expanded_handle,
631
542
  e.title,
632
543
  e.description,
544
+ e.image_url,
545
+ e.site_name,
633
546
  e.error,
634
547
  e.source as expansion_source,
635
548
  e.updated_at,