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
@@ -1,7 +1,9 @@
1
1
  import { Check, ChevronDown } from "lucide-react";
2
+ import { useQuery } from "@tanstack/react-query";
2
3
  import type { ReactNode } from "react";
3
4
  import { useEffect, useMemo, useRef, useState } from "react";
4
5
  import { fetchQueryEnvelope } from "#/lib/api-client";
6
+ import { queryKeys } from "#/lib/query-client";
5
7
  import type { AccountRecord } from "#/lib/types";
6
8
  import { cx } from "#/lib/ui";
7
9
  import { AvatarChip } from "./AvatarChip";
@@ -25,29 +27,19 @@ function avatarName(account: AccountRecord) {
25
27
  }
26
28
 
27
29
  export function AccountSwitcher({ action }: { action?: ReactNode } = {}) {
28
- const [accounts, setAccounts] = useState<AccountRecord[]>([]);
29
30
  const [open, setOpen] = useState(false);
30
31
  const switcherRef = useRef<HTMLDivElement>(null);
32
+ const statusQuery = useQuery({
33
+ queryKey: queryKeys.status,
34
+ queryFn: ({ signal }) => fetchQueryEnvelope({ signal }),
35
+ });
36
+ const accounts = statusQuery.data?.accounts ?? [];
31
37
  const selectedAccountId = useSelectedAccountId(accounts);
32
38
  const selectedAccount = useMemo(
33
39
  () => accounts.find((account) => account.id === selectedAccountId),
34
40
  [accounts, selectedAccountId],
35
41
  );
36
42
 
37
- useEffect(() => {
38
- let active = true;
39
- fetchQueryEnvelope()
40
- .then((meta) => {
41
- if (active) setAccounts(meta.accounts);
42
- })
43
- .catch(() => {
44
- if (active) setAccounts([]);
45
- });
46
- return () => {
47
- active = false;
48
- };
49
- }, []);
50
-
51
43
  useEffect(() => {
52
44
  if (!open) return;
53
45
 
@@ -49,7 +49,7 @@ export function AvatarChip({
49
49
  );
50
50
  }
51
51
 
52
- function avatarPath(profileId: string, avatarUrl: string) {
52
+ export function avatarPath(profileId: string, avatarUrl: string) {
53
53
  const query = new URLSearchParams({
54
54
  profileId,
55
55
  v: avatarUrl,
@@ -0,0 +1,149 @@
1
+ import { type RefObject, useEffect } from "react";
2
+ import { avatarPath } from "./AvatarChip";
3
+
4
+ const MAX_CONCURRENT_PRELOADS = 4;
5
+ const IDLE_TIMEOUT_MS = 2500;
6
+ const PRELOAD_ROOT_MARGIN = "800px 0px";
7
+
8
+ const queuedSources: string[] = [];
9
+ const knownSources = new Set<string>();
10
+ const activeImages = new Set<HTMLImageElement>();
11
+
12
+ let activePreloads = 0;
13
+ let listeningForLoad = false;
14
+ let idleId: number | null = null;
15
+ let fallbackTimerId: number | null = null;
16
+
17
+ function finishPreload(image: HTMLImageElement) {
18
+ if (!activeImages.delete(image)) return;
19
+ activePreloads -= 1;
20
+ drainPreloads();
21
+ }
22
+
23
+ function drainPreloads() {
24
+ while (activePreloads < MAX_CONCURRENT_PRELOADS && queuedSources.length > 0) {
25
+ const src = queuedSources.shift();
26
+ if (!src) return;
27
+
28
+ const image = new Image();
29
+ activePreloads += 1;
30
+ activeImages.add(image);
31
+ image.decoding = "async";
32
+ image.onerror = () => finishPreload(image);
33
+ image.onload = () => {
34
+ if (typeof image.decode !== "function") {
35
+ finishPreload(image);
36
+ return;
37
+ }
38
+ void image
39
+ .decode()
40
+ .catch(() => undefined)
41
+ .finally(() => finishPreload(image));
42
+ };
43
+ image.src = src;
44
+ }
45
+ }
46
+
47
+ function runIdlePreloads() {
48
+ idleId = null;
49
+ fallbackTimerId = null;
50
+ drainPreloads();
51
+ }
52
+
53
+ function scheduleIdlePreloads() {
54
+ if (
55
+ queuedSources.length === 0 ||
56
+ idleId !== null ||
57
+ fallbackTimerId !== null
58
+ ) {
59
+ return;
60
+ }
61
+
62
+ const requestIdle = window.requestIdleCallback?.bind(window);
63
+ if (requestIdle) {
64
+ idleId = requestIdle(runIdlePreloads, {
65
+ timeout: IDLE_TIMEOUT_MS,
66
+ });
67
+ return;
68
+ }
69
+
70
+ fallbackTimerId = window.setTimeout(runIdlePreloads, 0);
71
+ }
72
+
73
+ function handleWindowLoad() {
74
+ listeningForLoad = false;
75
+ scheduleIdlePreloads();
76
+ }
77
+
78
+ function scheduleAfterPageLoad() {
79
+ if (document.readyState === "complete") {
80
+ scheduleIdlePreloads();
81
+ return;
82
+ }
83
+ if (listeningForLoad) return;
84
+ listeningForLoad = true;
85
+ window.addEventListener("load", handleWindowLoad, { once: true });
86
+ }
87
+
88
+ function queueAvatarPreload(profileId?: string, avatarUrl?: string) {
89
+ if (!profileId || !avatarUrl || typeof window === "undefined") return;
90
+ const src = avatarPath(profileId, avatarUrl);
91
+ if (knownSources.has(src)) return;
92
+ knownSources.add(src);
93
+ queuedSources.push(src);
94
+ scheduleAfterPageLoad();
95
+ }
96
+
97
+ export function useAvatarPreload(
98
+ reference: RefObject<Element | null>,
99
+ profileId?: string,
100
+ avatarUrl?: string,
101
+ ) {
102
+ useEffect(() => {
103
+ if (!profileId || !avatarUrl) return;
104
+ const node = reference.current;
105
+ if (!node || typeof IntersectionObserver === "undefined") {
106
+ queueAvatarPreload(profileId, avatarUrl);
107
+ return;
108
+ }
109
+
110
+ const observer = new IntersectionObserver(
111
+ (entries) => {
112
+ if (!entries.some((entry) => entry.isIntersecting)) return;
113
+ observer.disconnect();
114
+ queueAvatarPreload(profileId, avatarUrl);
115
+ },
116
+ { rootMargin: PRELOAD_ROOT_MARGIN },
117
+ );
118
+ observer.observe(node);
119
+ return () => observer.disconnect();
120
+ }, [avatarUrl, profileId, reference]);
121
+ }
122
+
123
+ function resetPreloader() {
124
+ if (listeningForLoad) {
125
+ window.removeEventListener("load", handleWindowLoad);
126
+ }
127
+ if (idleId !== null && "cancelIdleCallback" in window) {
128
+ window.cancelIdleCallback(idleId);
129
+ }
130
+ if (fallbackTimerId !== null) {
131
+ window.clearTimeout(fallbackTimerId);
132
+ }
133
+ for (const image of activeImages) {
134
+ image.onload = null;
135
+ image.onerror = null;
136
+ }
137
+ queuedSources.length = 0;
138
+ knownSources.clear();
139
+ activeImages.clear();
140
+ activePreloads = 0;
141
+ listeningForLoad = false;
142
+ idleId = null;
143
+ fallbackTimerId = null;
144
+ }
145
+
146
+ export const __test__ = {
147
+ queueAvatarPreload,
148
+ resetPreloader,
149
+ };