BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: controller pattern

+274 -170
+1
package.json
··· 48 48 "eslint-plugin-react": "^7.37.5", 49 49 "eslint-plugin-solid": "^0.14.5", 50 50 "eslint-plugin-unicorn": "^64.0.0", 51 + "fallow": "^2.19.0", 51 52 "globals": "^17.3.0", 52 53 "jsdom": "^28.1.0", 53 54 "tailwindcss": "^4.2.1",
+76
pnpm-lock.yaml
··· 96 96 eslint-plugin-unicorn: 97 97 specifier: ^64.0.0 98 98 version: 64.0.0(eslint@10.1.0(jiti@2.6.1)) 99 + fallow: 100 + specifier: ^2.19.0 101 + version: 2.19.0 99 102 globals: 100 103 specifier: ^17.3.0 101 104 version: 17.4.0 ··· 445 448 peerDependenciesMeta: 446 449 '@noble/hashes': 447 450 optional: true 451 + 452 + '@fallow-cli/darwin-arm64@2.19.0': 453 + resolution: {integrity: sha512-f9D0jajY2SgtkrrTm92HI25gXSSLEYRz6jyywxaV40jsgLSCWDF8gpuor93Mjn4l49vSpMn1m/Hd383+LU2/IA==} 454 + cpu: [arm64] 455 + os: [darwin] 456 + 457 + '@fallow-cli/darwin-x64@2.19.0': 458 + resolution: {integrity: sha512-zv4ZQNZUZOz/wPS3N+VkjsRZHBOCbKrZK3AH1EbEGTU6TgvE9CdoS1HP148R5Y5dd/Tbrcqc7RdzSZLeELldYQ==} 459 + cpu: [x64] 460 + os: [darwin] 461 + 462 + '@fallow-cli/linux-arm64-gnu@2.19.0': 463 + resolution: {integrity: sha512-rQYRlFTJJiEdM3iwlgWXRZqZol4X3fiANjll4Ezpgsbo4Qe8t8BtCHwyR4iMPIT4/QgFw3iSGu/xtMvrUfwCZQ==} 464 + cpu: [arm64] 465 + os: [linux] 466 + 467 + '@fallow-cli/linux-arm64-musl@2.19.0': 468 + resolution: {integrity: sha512-t2CFtefFYisGgpYr+LBFwXRjX9noWrIS8uvUtuIytMOlukiaBwH1FlmXuHp99pLvZLJ36ageZNnSwcikvNTXcA==} 469 + cpu: [arm64] 470 + os: [linux] 471 + 472 + '@fallow-cli/linux-x64-gnu@2.19.0': 473 + resolution: {integrity: sha512-BMvKKC4DpvnZvF6cq4V3URKahLZbluzk2dJAqo3u4q7/pHRIjosx8NNqj2LdwOGoAcW/Z3o6LVUkL2Kea/BnNA==} 474 + cpu: [x64] 475 + os: [linux] 476 + 477 + '@fallow-cli/linux-x64-musl@2.19.0': 478 + resolution: {integrity: sha512-qUrIMJTFGkv+O5ySJkGLz1z2pA8CY2Sl8FBZqaOJnP3yJn7v7zYhy/zerhk+bmAr+OlGW+y0nYBOsxMF/EIelw==} 479 + cpu: [x64] 480 + os: [linux] 481 + 482 + '@fallow-cli/win32-x64-msvc@2.19.0': 483 + resolution: {integrity: sha512-W+xDGrTxePpIitaR/PsV1OaixXUzQLZwqjvAswk0xuFhykpom4K1uVe2BtlL0Kk96mWbUKhB7Fu/hwrw24U0bg==} 484 + cpu: [x64] 485 + os: [win32] 448 486 449 487 '@fontsource-variable/google-sans@5.2.1': 450 488 resolution: {integrity: sha512-dMyMlJVRJgQvvYlJLcrg2oiVzlKAuIMrbDxIMchEiMBGmpOA6VnVblj3cPfJyA0/fNPQS6krZaiJyJjHJZmkzw==} ··· 1430 1468 extendable-error@0.1.7: 1431 1469 resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} 1432 1470 1471 + fallow@2.19.0: 1472 + resolution: {integrity: sha512-APjtt1JNIsW/s44se/u8nvhC6+RpJI4biAyLekNsrM3T8CFh1uBRtoSbOFCvL0mbavoPpOtvR53N25svcFpdBw==} 1473 + engines: {node: '>=16'} 1474 + hasBin: true 1475 + 1433 1476 fast-deep-equal@3.1.3: 1434 1477 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1435 1478 ··· 3043 3086 3044 3087 '@exodus/bytes@1.15.0': {} 3045 3088 3089 + '@fallow-cli/darwin-arm64@2.19.0': 3090 + optional: true 3091 + 3092 + '@fallow-cli/darwin-x64@2.19.0': 3093 + optional: true 3094 + 3095 + '@fallow-cli/linux-arm64-gnu@2.19.0': 3096 + optional: true 3097 + 3098 + '@fallow-cli/linux-arm64-musl@2.19.0': 3099 + optional: true 3100 + 3101 + '@fallow-cli/linux-x64-gnu@2.19.0': 3102 + optional: true 3103 + 3104 + '@fallow-cli/linux-x64-musl@2.19.0': 3105 + optional: true 3106 + 3107 + '@fallow-cli/win32-x64-msvc@2.19.0': 3108 + optional: true 3109 + 3046 3110 '@fontsource-variable/google-sans@5.2.1': {} 3047 3111 3048 3112 '@humanfs/core@0.19.1': {} ··· 4151 4215 expect-type@1.3.0: {} 4152 4216 4153 4217 extendable-error@0.1.7: {} 4218 + 4219 + fallow@2.19.0: 4220 + dependencies: 4221 + detect-libc: 2.1.2 4222 + optionalDependencies: 4223 + '@fallow-cli/darwin-arm64': 2.19.0 4224 + '@fallow-cli/darwin-x64': 2.19.0 4225 + '@fallow-cli/linux-arm64-gnu': 2.19.0 4226 + '@fallow-cli/linux-arm64-musl': 2.19.0 4227 + '@fallow-cli/linux-x64-gnu': 2.19.0 4228 + '@fallow-cli/linux-x64-musl': 2.19.0 4229 + '@fallow-cli/win32-x64-msvc': 2.19.0 4154 4230 4155 4231 fast-deep-equal@3.1.3: {} 4156 4232
+1
pnpm-workspace.yaml
··· 1 1 onlyBuiltDependencies: 2 2 - dprint 3 3 - esbuild 4 + - fallow
+2 -2
src/components/deck/AddColumnPanel.tsx
··· 1 1 import { ActorSuggestionList, useActorSuggestions } from "$/components/actors/actor-search"; 2 2 import { getFeedGenerators, getPreferences } from "$/lib/api/feeds"; 3 - import type { SearchMode } from "$/lib/api/search"; 4 3 import type { ColumnKind } from "$/lib/api/types/columns"; 4 + import type { SearchMode } from "$/lib/api/types/search"; 5 5 import { getFeedName } from "$/lib/feeds"; 6 6 import type { FeedGeneratorView, LoginSuggestion, SavedFeedItem } from "$/lib/types"; 7 7 import * as logger from "@tauri-apps/plugin-log"; ··· 506 506 role="dialog" 507 507 aria-modal="true" 508 508 aria-labelledby="add-column-panel-title" 509 - class="relative z-10 flex h-full w-full max-w-88 flex-col bg-(--surface-container-highest) shadow-[-18px_0_48px_rgba(0,0,0,0.38)] backdrop-blur-[20px]" 509 + class="relative z-10 flex h-full w-full max-w-88 flex-col bg-surface-container-highest shadow-[-18px_0_48px_rgba(0,0,0,0.38)] backdrop-blur-[20px]" 510 510 initial={{ opacity: 0, x: 32 }} 511 511 animate={{ opacity: 1, x: 0 }} 512 512 exit={{ opacity: 0, x: 40 }}
+2 -2
src/components/notifications/notification-grouping.ts
··· 25 25 sampleRecordText: string | null; 26 26 }; 27 27 28 - export const MENTION_REASONS = new Set(["mention", "reply", "quote"]); 28 + const MENTION_REASONS = new Set(["mention", "reply", "quote"]); 29 29 30 30 export function isMentionNotification(notification: NotificationView) { 31 31 return MENTION_REASONS.has(notification.reason); ··· 36 36 return Number.isNaN(timestamp) ? 0 : timestamp; 37 37 } 38 38 39 - export function compareByNewest(left: { latestIndexedAt: string }, right: { latestIndexedAt: string }) { 39 + function compareByNewest(left: { latestIndexedAt: string }, right: { latestIndexedAt: string }) { 40 40 const timestampDelta = toTimestamp(right.latestIndexedAt) - toTimestamp(left.latestIndexedAt); 41 41 if (timestampDelta !== 0) { 42 42 return timestampDelta;
+7 -1
src/components/saved/SavedPostsPanel.test.tsx
··· 12 12 13 13 vi.mock( 14 14 "$/lib/api/search", 15 - () => ({ getSyncStatus: getSyncStatusMock, listSavedPosts: listSavedPostsMock, syncPosts: syncPostsMock }), 15 + () => ({ 16 + SearchController: { 17 + getSyncStatus: getSyncStatusMock, 18 + listSavedPosts: listSavedPostsMock, 19 + syncPosts: syncPostsMock, 20 + }, 21 + }), 16 22 ); 17 23 vi.mock( 18 24 "$/components/posts/useThreadOverlayNavigation",
+7 -7
src/components/saved/SavedPostsPanel.tsx
··· 5 5 import { Icon } from "$/components/shared/Icon"; 6 6 import { PostCount } from "$/components/shared/PostCount"; 7 7 import { useAppSession } from "$/contexts/app-session"; 8 - import { getSyncStatus, listSavedPosts, syncPosts } from "$/lib/api/search"; 9 - import type { LocalPostResult, SavedPostSource, SyncStatus } from "$/lib/api/search"; 8 + import { SearchController } from "$/lib/api/search"; 9 + import type { LocalPostResult, SavedPostSource, SyncStatus } from "$/lib/api/types/search"; 10 10 import { formatRelativeTime } from "$/lib/feeds"; 11 11 import { subscribeBookmarkChanged } from "$/lib/post-events"; 12 12 import { normalizeError } from "$/lib/utils/text"; ··· 191 191 setState("syncStatusLoading", true); 192 192 193 193 try { 194 - const status = await getSyncStatus(did); 194 + const status = await SearchController.getSyncStatus(did); 195 195 if (did !== activeDid) { 196 196 return; 197 197 } ··· 253 253 setState("tabs", source, "error", null); 254 254 255 255 try { 256 - const page = await listSavedPosts(source, PAGE_SIZE, offset); 256 + const page = await SearchController.listSavedPosts(source, PAGE_SIZE, offset); 257 257 if (did !== activeDid || requestId !== browseRequestIds[source]) { 258 258 return; 259 259 } ··· 296 296 setState("searchTabs", source, "error", null); 297 297 298 298 try { 299 - const page = await listSavedPosts(source, PAGE_SIZE, offset, query); 299 + const page = await SearchController.listSavedPosts(source, PAGE_SIZE, offset, query); 300 300 if (did !== activeDid || requestId !== searchRequestIds[source] || trimmedQuery() !== query) { 301 301 return; 302 302 } ··· 362 362 setState("refreshing", true); 363 363 364 364 try { 365 - await syncPosts(session.activeDid, "bookmark"); 366 - await syncPosts(session.activeDid, "like"); 365 + await SearchController.syncPosts(session.activeDid, "bookmark"); 366 + await SearchController.syncPosts(session.activeDid, "like"); 367 367 await Promise.all([ 368 368 loadSyncStatus(session.activeDid), 369 369 isSearching()
+6 -4
src/components/search/EmbeddingsSettings.test.tsx
··· 13 13 vi.mock( 14 14 "$/lib/api/search", 15 15 () => ({ 16 - getEmbeddingsConfig: getEmbeddingsConfigMock, 17 - prepareEmbeddingsModel: prepareEmbeddingsModelMock, 18 - setEmbeddingsEnabled: setEmbeddingsEnabledMock, 19 - setEmbeddingsPreflightSeen: setEmbeddingsPreflightSeenMock, 16 + SearchController: { 17 + getEmbeddingsConfig: getEmbeddingsConfigMock, 18 + prepareEmbeddingsModel: prepareEmbeddingsModelMock, 19 + setEmbeddingsEnabled: setEmbeddingsEnabledMock, 20 + setEmbeddingsPreflightSeen: setEmbeddingsPreflightSeenMock, 21 + }, 20 22 }), 21 23 ); 22 24
+1 -1
src/components/search/EmbeddingsSettings.tsx
··· 1 1 /* eslint react/jsx-max-depth: ["error", { "max": 5 }] */ 2 2 import { Icon } from "$/components/shared/Icon"; 3 3 import { useAppPreferences } from "$/contexts/app-preferences"; 4 - import type { EmbeddingsConfig } from "$/lib/api/search"; 4 + import type { EmbeddingsConfig } from "$/lib/api/types/search"; 5 5 import { formatBytes, formatEtaSeconds, formatProgress } from "$/lib/utils/text"; 6 6 import { createEffect, createMemo, createSignal, Match, onCleanup, onMount, Show, Switch } from "solid-js"; 7 7 import { Motion, Presence } from "solid-motionone";
+1 -1
src/components/search/HashtagPanel.test.tsx
··· 8 8 const searchPostsNetworkMock = vi.hoisted(() => vi.fn()); 9 9 const threadOverlayMock = vi.hoisted(() => ({ openThread: vi.fn() })); 10 10 11 - vi.mock("$/lib/api/search", () => ({ searchPostsNetwork: searchPostsNetworkMock })); 11 + vi.mock("$/lib/api/search", () => ({ SearchController: { searchPostsNetwork: searchPostsNetworkMock } })); 12 12 vi.mock( 13 13 "$/components/posts/useThreadOverlayNavigation", 14 14 () => ({ useThreadOverlayNavigation: () => threadOverlayMock }),
+4 -3
src/components/search/HashtagPanel.tsx
··· 1 1 import { PostCard } from "$/components/feeds/PostCard"; 2 2 import { useThreadOverlayNavigation } from "$/components/posts/useThreadOverlayNavigation"; 3 3 import { Icon } from "$/components/shared/Icon"; 4 - import { type NetworkSearchResult, searchPostsNetwork } from "$/lib/api/search"; 4 + import { SearchController } from "$/lib/api/search"; 5 + import type { NetworkSearchResult } from "$/lib/api/types/search"; 5 6 import { 6 7 buildHashtagQuery, 7 8 buildPostSearchRoute, 8 9 decodeHashtagRouteTag, 9 10 formatHashtagLabel, 10 11 parsePostSearchFilters, 11 - type PostSearchFilters, 12 12 toLocalDayStartIso, 13 13 toLocalDayUntilIso, 14 14 } from "$/lib/search-routes"; 15 + import type { PostSearchFilters } from "$/lib/search-routes"; 15 16 import { normalizeError } from "$/lib/utils/text"; 16 17 import { useLocation, useNavigate, useParams } from "@solidjs/router"; 17 18 import * as logger from "@tauri-apps/plugin-log"; ··· 57 58 58 59 async function performSearch(f: PostSearchFilters, t: string) { 59 60 try { 60 - const results = await searchPostsNetwork({ 61 + const results = await SearchController.searchPostsNetwork({ 61 62 author: f.author || null, 62 63 limit: 25, 63 64 mentions: f.mentions || null,
+1 -1
src/components/search/LocalPostResultsList.tsx
··· 1 - import type { LocalPostResult } from "$/lib/api/search"; 1 + import type { LocalPostResult } from "$/lib/api/types/search"; 2 2 import { For } from "solid-js"; 3 3 import { Motion } from "solid-motionone"; 4 4 import { SearchResultCard } from "./SearchResultCard";
+1 -1
src/components/search/PostSearchFilters.tsx
··· 1 - import type { NetworkSearchSort } from "$/lib/api/search"; 1 + import type { NetworkSearchSort } from "$/lib/api/types/search"; 2 2 import { normalizeTagToken, type PostSearchFilters } from "$/lib/search-routes"; 3 3 import { createEffect, createMemo, createSignal, For, Show } from "solid-js"; 4 4 import { ArrowIcon, Icon } from "../shared/Icon";
+7 -5
src/components/search/SearchPanel.test.tsx
··· 16 16 vi.mock( 17 17 "$/lib/api/search", 18 18 () => ({ 19 - getSyncStatus: getSyncStatusMock, 20 - searchActors: searchActorsMock, 21 - searchPosts: searchPostsMock, 22 - searchPostsNetwork: searchPostsNetworkMock, 23 - syncPosts: syncPostsMock, 19 + SearchController: { 20 + getSyncStatus: getSyncStatusMock, 21 + searchActors: searchActorsMock, 22 + searchPosts: searchPostsMock, 23 + searchPostsNetwork: searchPostsNetworkMock, 24 + syncPosts: syncPostsMock, 25 + }, 24 26 }), 25 27 ); 26 28 vi.mock("$/lib/api/actors", () => ({ searchActorSuggestions: searchActorSuggestionsMock }));
+16 -25
src/components/search/SearchPanel.tsx
··· 5 5 import { Icon, SearchModeIcon } from "$/components/shared/Icon"; 6 6 import { useAppPreferences } from "$/contexts/app-preferences"; 7 7 import { useAppSession } from "$/contexts/app-session"; 8 - import { 9 - type ActorResult, 10 - type ActorSearchResult, 11 - getSyncStatus, 12 - type LocalPostResult, 13 - type NetworkSearchParams, 14 - type NetworkSearchResult, 15 - searchActors, 16 - type SearchMode, 17 - searchPosts, 18 - searchPostsNetwork, 19 - type SyncStatus, 20 - } from "$/lib/api/search"; 8 + import { SearchController } from "$/lib/api/search"; 9 + import type { 10 + ActorResult, 11 + ActorSearchResult, 12 + LocalPostResult, 13 + NetworkSearchParams, 14 + NetworkSearchResult, 15 + SearchMode, 16 + SyncStatus, 17 + } from "$/lib/api/types/search"; 21 18 import { formatRelativeTime } from "$/lib/feeds"; 22 19 import { buildProfileRoute, getProfileRouteActor } from "$/lib/profile"; 23 - import { 24 - buildSearchRoute, 25 - parseSearchRouteState, 26 - type PostSearchFilters, 27 - type SearchTab, 28 - toLocalDayStartIso, 29 - toLocalDayUntilIso, 30 - } from "$/lib/search-routes"; 20 + import { buildSearchRoute, parseSearchRouteState, toLocalDayStartIso, toLocalDayUntilIso } from "$/lib/search-routes"; 21 + import type { PostSearchFilters, SearchTab } from "$/lib/search-routes"; 31 22 import type { ProfileViewBasic } from "$/lib/types"; 32 23 import { normalizeError } from "$/lib/utils/text"; 33 24 import { useLocation, useNavigate } from "@solidjs/router"; ··· 151 142 setSearch({ error: null, loading: true }); 152 143 153 144 try { 154 - const response = await searchActors(searchQuery, 25); 145 + const response = await SearchController.searchActors(searchQuery, 25); 155 146 setSearch({ 156 147 actorResults: response, 157 148 error: null, ··· 194 185 195 186 try { 196 187 if (state.mode === "network") { 197 - const response = await searchPostsNetwork(buildNetworkSearchParams(state)); 188 + const response = await SearchController.searchPostsNetwork(buildNetworkSearchParams(state)); 198 189 setSearch({ 199 190 actorResults: null, 200 191 hasSearched: true, ··· 203 194 results: [], 204 195 }); 205 196 } else { 206 - const response = await searchPosts(searchQuery, state.mode, 50); 197 + const response = await SearchController.searchPosts(searchQuery, state.mode, 50); 207 198 setSearch({ 208 199 actorResults: null, 209 200 hasSearched: true, ··· 342 333 } 343 334 344 335 if (props.embedded && session.activeDid) { 345 - void getSyncStatus(session.activeDid).then((status) => { 336 + void SearchController.getSyncStatus(session.activeDid).then((status) => { 346 337 setSearch("syncStatus", status); 347 338 }).catch((error) => { 348 339 logger.warn("failed to load embedded search sync status", { keyValues: { error: normalizeError(error) } });
+7 -1
src/components/search/SyncStatusPanel.test.tsx
··· 8 8 9 9 vi.mock( 10 10 "$/lib/api/search", 11 - () => ({ getSyncStatus: getSyncStatusMock, syncPosts: syncPostsMock, reindexEmbeddings: reindexEmbeddingsMock }), 11 + () => ({ 12 + SearchController: { 13 + getSyncStatus: getSyncStatusMock, 14 + syncPosts: syncPostsMock, 15 + reindexEmbeddings: reindexEmbeddingsMock, 16 + }, 17 + }), 12 18 ); 13 19 14 20 vi.mock("@tauri-apps/plugin-log", () => ({ info: vi.fn(), error: vi.fn(), warn: vi.fn() }));
+6 -5
src/components/search/SyncStatusPanel.tsx
··· 1 1 import { Icon } from "$/components/shared/Icon"; 2 - import { getSyncStatus, reindexEmbeddings, syncPosts, type SyncStatus } from "$/lib/api/search"; 2 + import { SearchController } from "$/lib/api/search"; 3 + import type { SyncStatus } from "$/lib/api/types/search"; 3 4 import { formatRelativeTime } from "$/lib/feeds"; 4 5 import * as logger from "@tauri-apps/plugin-log"; 5 6 import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"; ··· 123 124 124 125 async function loadSyncStatus() { 125 126 try { 126 - const status = await getSyncStatus(props.did); 127 + const status = await SearchController.getSyncStatus(props.did); 127 128 setSyncStatus(status); 128 129 props.onStatusChange?.(status); 129 130 } catch (error) { ··· 134 135 async function handleSync() { 135 136 setIsSyncing(true); 136 137 try { 137 - await syncPosts(props.did, "like"); 138 - await syncPosts(props.did, "bookmark"); 138 + await SearchController.syncPosts(props.did, "like"); 139 + await SearchController.syncPosts(props.did, "bookmark"); 139 140 await loadSyncStatus(); 140 141 } catch (error) { 141 142 logger.error("sync failed", { keyValues: { error: String(error) } }); ··· 147 148 async function handleReindex() { 148 149 setIsReindexing(true); 149 150 try { 150 - const count = await reindexEmbeddings(); 151 + const count = await SearchController.reindexEmbeddings(); 151 152 logger.info("reindex complete", { keyValues: { count: String(count) } }); 152 153 await loadSyncStatus(); 153 154 } catch (error) {
+1 -1
src/components/settings/SettingsInlineFeedback.tsx
··· 1 1 import { createSignal, onCleanup, Show } from "solid-js"; 2 2 import { Icon } from "../shared/Icon"; 3 3 4 - export type SettingsFeedback = { kind: "error" | "success"; message: string }; 4 + type SettingsFeedback = { kind: "error" | "success"; message: string }; 5 5 6 6 export function useTransientFeedback(timeoutMs = 5000) { 7 7 const [feedback, setFeedback] = createSignal<SettingsFeedback | null>(null);
+1 -1
src/components/shared/Icon.tsx
··· 1 - import type { SearchMode } from "$/lib/api/search"; 2 1 import type { ExplorerTargetKind } from "$/lib/api/types/explorer"; 2 + import type { SearchMode } from "$/lib/api/types/search"; 3 3 import { type JSX, Match, splitProps, Switch } from "solid-js"; 4 4 5 5 type ActionIconKind = "add" | "edit" | "delete" | "save" | "cancel";
+6 -11
src/contexts/app-preferences.tsx
··· 1 - import { 2 - getEmbeddingsConfig, 3 - prepareEmbeddingsModel as prepareEmbeddingsModelRequest, 4 - setEmbeddingsEnabled as setEmbeddingsEnabledRequest, 5 - setEmbeddingsPreflightSeen as setEmbeddingsPreflightSeenRequest, 6 - } from "$/lib/api/search"; 7 - import type { EmbeddingsConfig } from "$/lib/api/search"; 1 + import { SearchController } from "$/lib/api/search"; 8 2 import { SettingsController } from "$/lib/api/settings"; 3 + import type { EmbeddingsConfig } from "$/lib/api/types/search"; 9 4 import type { AppSettings } from "$/lib/types"; 10 5 import * as logger from "@tauri-apps/plugin-log"; 11 6 import { createContext, onMount, type ParentProps, splitProps, untrack, useContext } from "solid-js"; ··· 76 71 setPreferences("embeddingsLoading", true); 77 72 78 73 try { 79 - const nextConfig = await getEmbeddingsConfig(); 74 + const nextConfig = await SearchController.getEmbeddingsConfig(); 80 75 setPreferences("embeddingsConfig", nextConfig); 81 76 setPreferences("settings", (current) => { 82 77 if (!current) { ··· 94 89 95 90 async function prepareEmbeddingsModel() { 96 91 try { 97 - const nextConfig = await prepareEmbeddingsModelRequest(); 92 + const nextConfig = await SearchController.prepareEmbeddingsModel(); 98 93 setPreferences("embeddingsConfig", nextConfig); 99 94 setPreferences("settings", (current) => { 100 95 if (!current) { ··· 110 105 111 106 async function setEmbeddingsEnabled(enabled: boolean) { 112 107 try { 113 - await setEmbeddingsEnabledRequest(enabled); 108 + await SearchController.setEmbeddingsEnabled(enabled); 114 109 setPreferences("settings", (current) => { 115 110 if (!current) { 116 111 return current; ··· 128 123 129 124 async function setEmbeddingsPreflightSeen(seen: boolean) { 130 125 try { 131 - await setEmbeddingsPreflightSeenRequest(seen); 126 + await SearchController.setEmbeddingsPreflightSeen(seen); 132 127 setPreferences("embeddingsConfig", (current) => current ? { ...current, preflightSeen: seen } : current); 133 128 } catch (error) { 134 129 logger.error("failed to set embeddings preflight seen", {
+4 -4
src/lib/api/drafts.ts
··· 1 1 import type { Draft, DraftInput } from "$/lib/types"; 2 2 import { invoke } from "@tauri-apps/api/core"; 3 3 4 - export function listDrafts(accountDid: string): Promise<Draft[]> { 4 + function listDrafts(accountDid: string): Promise<Draft[]> { 5 5 return invoke("list_drafts", { accountDid }); 6 6 } 7 7 8 - export function getDraft(id: string): Promise<Draft> { 8 + function getDraft(id: string): Promise<Draft> { 9 9 return invoke("get_draft", { id }); 10 10 } 11 11 12 - export function saveDraft(input: DraftInput): Promise<Draft> { 12 + function saveDraft(input: DraftInput): Promise<Draft> { 13 13 return invoke("save_draft", { input }); 14 14 } 15 15 16 - export function deleteDraft(id: string): Promise<void> { 16 + function deleteDraft(id: string): Promise<void> { 17 17 return invoke("delete_draft", { id }); 18 18 } 19 19
+1 -1
src/lib/api/media.ts
··· 1 1 import { invoke } from "@tauri-apps/api/core"; 2 2 3 - export type DownloadResult = { path: string; bytes: number }; 3 + type DownloadResult = { path: string; bytes: number }; 4 4 5 5 export type DownloadProgress = { 6 6 url: string;
+36 -84
src/lib/api/search.ts
··· 1 - import type { PostView } from "$/lib/types"; 2 1 import { invoke } from "@tauri-apps/api/core"; 3 - 4 - export type SearchMode = "network" | "keyword" | "semantic" | "hybrid"; 5 - export type NetworkSearchSort = "top" | "latest"; 6 - 7 - export type NetworkSearchResult = { cursor?: string | null; hitsTotal?: number | null; posts: PostView[] }; 8 - export type NetworkSearchParams = { 9 - query: string; 10 - sort?: NetworkSearchSort; 11 - since?: string | null; 12 - until?: string | null; 13 - mentions?: string | null; 14 - author?: string | null; 15 - tags?: string[]; 16 - limit?: number; 17 - cursor?: string | null; 18 - }; 19 - 20 - export type ActorResult = { 21 - did: string; 22 - handle: string; 23 - displayName?: string | null; 24 - avatar?: string | null; 25 - description?: string | null; 26 - }; 27 - 28 - export type ActorSearchResult = { cursor?: string | null; actors: ActorResult[] }; 29 - 30 - export type SavedPostSource = "like" | "bookmark"; 31 - 32 - export type LocalPostResult = { 33 - uri: string; 34 - cid: string; 35 - authorDid: string; 36 - authorHandle?: string | null; 37 - text?: string | null; 38 - createdAt?: string | null; 39 - source: SavedPostSource; 40 - score: number; 41 - keywordMatch: boolean; 42 - semanticMatch: boolean; 43 - }; 44 - 45 - type SavedPostsPage = { posts: LocalPostResult[]; total: number; nextOffset?: number | null }; 46 - 47 - export type SyncStatus = { 48 - did: string; 49 - source: SavedPostSource; 50 - cursor?: string | null; 51 - lastSyncedAt?: string | null; 52 - postCount?: number; 53 - }; 54 - 55 - export type EmbeddingsConfig = { 56 - enabled: boolean; 57 - preflightSeen: boolean; 58 - modelName: string; 59 - dimensions: number; 60 - modelSizeBytes?: number | null; 61 - downloaded: boolean; 62 - downloadActive: boolean; 63 - downloadProgress?: number | null; 64 - downloadEtaSeconds?: number | null; 65 - downloadFile?: string | null; 66 - downloadFileIndex?: number | null; 67 - downloadFileTotal?: number | null; 68 - lastError?: string | null; 69 - }; 2 + import type { 3 + ActorSearchResult, 4 + EmbeddingsConfig, 5 + LocalPostResult, 6 + NetworkSearchParams, 7 + NetworkSearchResult, 8 + SavedPostSource, 9 + SavedPostsPage, 10 + SearchMode, 11 + SyncStatus, 12 + } from "./types/search"; 70 13 71 - export function searchPostsNetwork(params: NetworkSearchParams): Promise<NetworkSearchResult> { 14 + function searchPostsNetwork(params: NetworkSearchParams): Promise<NetworkSearchResult> { 72 15 return invoke("search_posts_network", { 73 16 queryParams: { 74 17 author: params.author ?? null, ··· 84 27 }); 85 28 } 86 29 87 - export function searchPosts(query: string, mode: SearchMode, limit: number): Promise<LocalPostResult[]> { 30 + function searchPosts(query: string, mode: SearchMode, limit: number): Promise<LocalPostResult[]> { 88 31 return invoke("search_posts", { query, mode, limit }); 89 32 } 90 33 91 - export function listSavedPosts( 92 - source: SavedPostSource, 93 - limit: number, 94 - offset = 0, 95 - query?: string, 96 - ): Promise<SavedPostsPage> { 34 + function listSavedPosts(source: SavedPostSource, limit: number, offset = 0, query?: string): Promise<SavedPostsPage> { 97 35 return invoke("list_saved_posts", { source, limit, offset, query: query?.trim() ? query.trim() : null }); 98 36 } 99 37 100 - export function searchActors(query: string, limit?: number, cursor?: string | null): Promise<ActorSearchResult> { 38 + function searchActors(query: string, limit?: number, cursor?: string | null): Promise<ActorSearchResult> { 101 39 return invoke("search_actors", { query, limit: limit ?? null, cursor: cursor ?? null }); 102 40 } 103 41 104 - export function syncPosts(did: string, source: SavedPostSource): Promise<SyncStatus> { 42 + function syncPosts(did: string, source: SavedPostSource): Promise<SyncStatus> { 105 43 return invoke("sync_posts", { did, source }); 106 44 } 107 45 108 - export function getSyncStatus(did: string): Promise<SyncStatus[]> { 46 + function getSyncStatus(did: string): Promise<SyncStatus[]> { 109 47 return invoke("get_sync_status", { did }); 110 48 } 111 49 112 - export function reindexEmbeddings(): Promise<number> { 50 + function reindexEmbeddings(): Promise<number> { 113 51 return invoke("reindex_embeddings"); 114 52 } 115 53 116 - export function setEmbeddingsEnabled(enabled: boolean): Promise<void> { 54 + function setEmbeddingsEnabled(enabled: boolean): Promise<void> { 117 55 return invoke("set_embeddings_enabled", { enabled }); 118 56 } 119 57 120 - export function setEmbeddingsPreflightSeen(seen: boolean): Promise<void> { 58 + function setEmbeddingsPreflightSeen(seen: boolean): Promise<void> { 121 59 return invoke("set_embeddings_preflight_seen", { seen }); 122 60 } 123 61 124 - export function getEmbeddingsConfig(): Promise<EmbeddingsConfig> { 62 + function getEmbeddingsConfig(): Promise<EmbeddingsConfig> { 125 63 return invoke("get_embeddings_config"); 126 64 } 127 65 128 - export function prepareEmbeddingsModel(): Promise<EmbeddingsConfig> { 66 + function prepareEmbeddingsModel(): Promise<EmbeddingsConfig> { 129 67 return invoke("prepare_embeddings_model"); 130 68 } 69 + 70 + export const SearchController = { 71 + searchPostsNetwork, 72 + searchPosts, 73 + listSavedPosts, 74 + searchActors, 75 + syncPosts, 76 + getSyncStatus, 77 + reindexEmbeddings, 78 + setEmbeddingsEnabled, 79 + setEmbeddingsPreflightSeen, 80 + getEmbeddingsConfig, 81 + prepareEmbeddingsModel, 82 + };
+7 -7
src/lib/api/settings.ts
··· 1 1 import type { AppSettings, CacheClearScope, CacheSize, ExportFormat, LogEntry, LogLevelFilter } from "$/lib/types"; 2 2 import { invoke } from "@tauri-apps/api/core"; 3 3 4 - export function getSettings() { 4 + function getSettings() { 5 5 return invoke<AppSettings>("get_settings"); 6 6 } 7 7 8 - export function updateSetting(key: string, value: string) { 8 + function updateSetting(key: string, value: string) { 9 9 return invoke("update_setting", { key, value }); 10 10 } 11 11 12 - export function getCacheSize() { 12 + function getCacheSize() { 13 13 return invoke<CacheSize>("get_cache_size"); 14 14 } 15 15 16 - export function clearCache(scope: CacheClearScope) { 16 + function clearCache(scope: CacheClearScope) { 17 17 return invoke("clear_cache", { scope }); 18 18 } 19 19 20 - export function exportData(format: ExportFormat, path?: string) { 20 + function exportData(format: ExportFormat, path?: string) { 21 21 const now = Date.now(); 22 22 return invoke("export_data", { format, path: path ?? `lazurite_${now}_export.${format}` }); 23 23 } ··· 26 26 return invoke("reset_app"); 27 27 } 28 28 29 - export async function resetAndRestartApp() { 29 + async function resetAndRestartApp() { 30 30 await resetApp(); 31 31 restartClient("/auth"); 32 32 } 33 33 34 - export function getLogEntries(limit: number, level?: LogLevelFilter) { 34 + function getLogEntries(limit: number, level?: LogLevelFilter) { 35 35 const filterLevel = level === "all" ? null : level; 36 36 return invoke<LogEntry[]>("get_log_entries", { limit, level: filterLevel }); 37 37 }
+1 -1
src/lib/api/types/columns.ts
··· 1 - import type { SearchMode } from "../search"; 1 + import type { SearchMode } from "./search"; 2 2 3 3 export type ColumnKind = "feed" | "explorer" | "diagnostics" | "messages" | "search" | "profile"; 4 4
+70
src/lib/api/types/search.ts
··· 1 + import type { PostView } from "$/lib/types"; 2 + 3 + export type SearchMode = "network" | "keyword" | "semantic" | "hybrid"; 4 + 5 + export type NetworkSearchSort = "top" | "latest"; 6 + 7 + export type NetworkSearchResult = { cursor?: string | null; hitsTotal?: number | null; posts: PostView[] }; 8 + 9 + export type NetworkSearchParams = { 10 + query: string; 11 + sort?: NetworkSearchSort; 12 + since?: string | null; 13 + until?: string | null; 14 + mentions?: string | null; 15 + author?: string | null; 16 + tags?: string[]; 17 + limit?: number; 18 + cursor?: string | null; 19 + }; 20 + 21 + export type ActorResult = { 22 + did: string; 23 + handle: string; 24 + displayName?: string | null; 25 + avatar?: string | null; 26 + description?: string | null; 27 + }; 28 + 29 + export type ActorSearchResult = { cursor?: string | null; actors: ActorResult[] }; 30 + 31 + export type SavedPostSource = "like" | "bookmark"; 32 + 33 + export type LocalPostResult = { 34 + uri: string; 35 + cid: string; 36 + authorDid: string; 37 + authorHandle?: string | null; 38 + text?: string | null; 39 + createdAt?: string | null; 40 + source: SavedPostSource; 41 + score: number; 42 + keywordMatch: boolean; 43 + semanticMatch: boolean; 44 + }; 45 + 46 + export type SavedPostsPage = { posts: LocalPostResult[]; total: number; nextOffset?: number | null }; 47 + 48 + export type SyncStatus = { 49 + did: string; 50 + source: SavedPostSource; 51 + cursor?: string | null; 52 + lastSyncedAt?: string | null; 53 + postCount?: number; 54 + }; 55 + 56 + export type EmbeddingsConfig = { 57 + enabled: boolean; 58 + preflightSeen: boolean; 59 + modelName: string; 60 + dimensions: number; 61 + modelSizeBytes?: number | null; 62 + downloaded: boolean; 63 + downloadActive: boolean; 64 + downloadProgress?: number | null; 65 + downloadEtaSeconds?: number | null; 66 + downloadFile?: string | null; 67 + downloadFileIndex?: number | null; 68 + downloadFileTotal?: number | null; 69 + lastError?: string | null; 70 + };
+1 -1
src/lib/search-routes.ts
··· 1 - import type { NetworkSearchSort, SearchMode } from "$/lib/api/search"; 1 + import type { NetworkSearchSort, SearchMode } from "$/lib/api/types/search"; 2 2 3 3 export type SearchTab = "posts" | "profiles"; 4 4 const SEARCH_ROUTE = "/search";