mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
[codex] Reduce desktop release startup work (#269)
This commit is contained in:
@@ -32,6 +32,12 @@ if (publicKey && endpoint) {
|
||||
const missing = [];
|
||||
if (!publicKey) missing.push("SPROUT_UPDATER_PUBLIC_KEY");
|
||||
if (!endpoint) missing.push("SPROUT_UPDATER_ENDPOINT");
|
||||
if (releaseConfig.plugins) {
|
||||
delete releaseConfig.plugins.updater;
|
||||
if (Object.keys(releaseConfig.plugins).length === 0) {
|
||||
delete releaseConfig.plugins;
|
||||
}
|
||||
}
|
||||
console.log(`Updater config skipped (missing: ${missing.join(", ")})`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_RELAY_URL");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_RELAY_HTTP");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_PUBLIC_KEY");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_ENDPOINT");
|
||||
println!("cargo:rustc-check-cfg=cfg(sprout_updater_enabled)");
|
||||
|
||||
if let Ok(relay_url) = std::env::var("SPROUT_RELAY_URL") {
|
||||
println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_URL={relay_url}");
|
||||
@@ -10,5 +13,18 @@ fn main() {
|
||||
println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_HTTP={relay_http}");
|
||||
}
|
||||
|
||||
let updater_public_key = std::env::var("SPROUT_UPDATER_PUBLIC_KEY")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
let updater_endpoint = std::env::var("SPROUT_UPDATER_ENDPOINT")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
if updater_public_key.is_some() && updater_endpoint.is_some() {
|
||||
println!("cargo:rustc-cfg=sprout_updater_enabled");
|
||||
}
|
||||
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@@ -288,13 +288,19 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_process::init());
|
||||
|
||||
// The updater config is only generated for signed release builds.
|
||||
// Only register the updater in release builds that were compiled with a
|
||||
// real updater configuration. Local unsigned builds omit that config and
|
||||
// should still launch for debugging.
|
||||
#[cfg(sprout_updater_enabled)]
|
||||
let builder = if cfg!(debug_assertions) {
|
||||
builder
|
||||
} else {
|
||||
builder.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
};
|
||||
|
||||
#[cfg(not(sprout_updater_enabled))]
|
||||
let builder = builder;
|
||||
|
||||
let shutdown_started = Arc::new(AtomicBool::new(false));
|
||||
let restore_shutdown_started = Arc::clone(&shutdown_started);
|
||||
let app = builder
|
||||
|
||||
@@ -30,11 +30,6 @@
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"endpoints": []
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
import { usePresenceSession } from "@/features/presence/hooks";
|
||||
import { useProfileQuery } from "@/features/profile/hooks";
|
||||
import type { SettingsSection } from "@/features/settings/ui/SettingsPanels";
|
||||
import { SettingsScreen } from "@/features/settings/ui/SettingsScreen";
|
||||
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
@@ -46,6 +45,11 @@ import {
|
||||
type AppView = "home" | "channel" | "agents" | "workflows";
|
||||
const DEFAULT_SETTINGS_SECTION: SettingsSection = "profile";
|
||||
|
||||
const LazySettingsScreen = React.lazy(async () => {
|
||||
const module = await import("@/features/settings/ui/SettingsScreen");
|
||||
return { default: module.SettingsScreen };
|
||||
});
|
||||
|
||||
function toSearchHit(target: DesktopNotificationTarget): SearchHit | null {
|
||||
if (!target.eventId) {
|
||||
return null;
|
||||
@@ -250,14 +254,32 @@ export function AppShell() {
|
||||
React.useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
void relayClient.preconnect().catch((error) => {
|
||||
if (!isCancelled) {
|
||||
console.error("Failed to preconnect to relay", error);
|
||||
const startPreconnect = () => {
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
void relayClient.preconnect().catch((error) => {
|
||||
if (!isCancelled) {
|
||||
console.error("Failed to preconnect to relay", error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if ("requestIdleCallback" in window) {
|
||||
const idleId = window.requestIdleCallback(startPreconnect, {
|
||||
timeout: 1_500,
|
||||
});
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
window.cancelIdleCallback(idleId);
|
||||
};
|
||||
}
|
||||
|
||||
const timeoutId = globalThis.setTimeout(startPreconnect, 250);
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
globalThis.clearTimeout(timeoutId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -486,29 +508,31 @@ export function AppShell() {
|
||||
/>
|
||||
|
||||
{settingsOpen ? (
|
||||
<SettingsScreen
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
fallbackDisplayName={identityQuery.data?.displayName}
|
||||
isUpdatingDesktopNotifications={
|
||||
notificationSettings.isUpdatingDesktopEnabled
|
||||
}
|
||||
notificationErrorMessage={notificationSettings.errorMessage}
|
||||
notificationPermission={notificationSettings.permission}
|
||||
notificationSettings={notificationSettings.settings}
|
||||
onClose={handleCloseSettings}
|
||||
onSectionChange={setSettingsSection}
|
||||
onSetDesktopNotificationsEnabled={
|
||||
notificationSettings.setDesktopEnabled
|
||||
}
|
||||
onSetHomeBadgeEnabled={notificationSettings.setHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={
|
||||
notificationSettings.setMentionsEnabled
|
||||
}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
notificationSettings.setNeedsActionEnabled
|
||||
}
|
||||
section={settingsSection}
|
||||
/>
|
||||
<React.Suspense fallback={null}>
|
||||
<LazySettingsScreen
|
||||
currentPubkey={identityQuery.data?.pubkey}
|
||||
fallbackDisplayName={identityQuery.data?.displayName}
|
||||
isUpdatingDesktopNotifications={
|
||||
notificationSettings.isUpdatingDesktopEnabled
|
||||
}
|
||||
notificationErrorMessage={notificationSettings.errorMessage}
|
||||
notificationPermission={notificationSettings.permission}
|
||||
notificationSettings={notificationSettings.settings}
|
||||
onClose={handleCloseSettings}
|
||||
onSectionChange={setSettingsSection}
|
||||
onSetDesktopNotificationsEnabled={
|
||||
notificationSettings.setDesktopEnabled
|
||||
}
|
||||
onSetHomeBadgeEnabled={notificationSettings.setHomeBadgeEnabled}
|
||||
onSetMentionNotificationsEnabled={
|
||||
notificationSettings.setMentionsEnabled
|
||||
}
|
||||
onSetNeedsActionNotificationsEnabled={
|
||||
notificationSettings.setNeedsActionEnabled
|
||||
}
|
||||
section={settingsSection}
|
||||
/>
|
||||
</React.Suspense>
|
||||
) : null}
|
||||
</SidebarProvider>
|
||||
</AppShellProvider>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { getCachedSearchHitEvent } from "@/app/navigation/searchHitEventCache";
|
||||
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||
import { useChannelsQuery } from "@/features/channels/hooks";
|
||||
import { ChannelScreen } from "@/features/channels/ui/ChannelScreen";
|
||||
import { useProfileQuery } from "@/features/profile/hooks";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
import { getEventById } from "@/shared/api/tauri";
|
||||
import type { RelayEvent } from "@/shared/api/types";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
type ChannelRouteScreenProps = {
|
||||
channelId: string;
|
||||
selectedPostId: string | null;
|
||||
targetMessageId: string | null;
|
||||
targetReplyId: string | null;
|
||||
};
|
||||
|
||||
export function ChannelRouteScreen({
|
||||
channelId,
|
||||
selectedPostId,
|
||||
targetMessageId,
|
||||
targetReplyId,
|
||||
}: ChannelRouteScreenProps) {
|
||||
const { closeForumPost, goForumPost } = useAppNavigation();
|
||||
const channelsQuery = useChannelsQuery();
|
||||
const identityQuery = useIdentityQuery();
|
||||
const profileQuery = useProfileQuery();
|
||||
const channels = channelsQuery.data ?? [];
|
||||
const activeChannel =
|
||||
channels.find((channel) => channel.id === channelId) ?? null;
|
||||
const [targetMessageEvent, setTargetMessageEvent] =
|
||||
React.useState<RelayEvent | null>(() =>
|
||||
getCachedSearchHitEvent(targetMessageId),
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
if (!targetMessageId || selectedPostId) {
|
||||
setTargetMessageEvent(null);
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}
|
||||
|
||||
setTargetMessageEvent(getCachedSearchHitEvent(targetMessageId));
|
||||
void getEventById(targetMessageId)
|
||||
.then((event) => {
|
||||
if (!isCancelled) {
|
||||
setTargetMessageEvent(event);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!isCancelled) {
|
||||
console.error(
|
||||
"Failed to load route target event",
|
||||
targetMessageId,
|
||||
error,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [selectedPostId, targetMessageId]);
|
||||
|
||||
if (channelsQuery.isPending && !activeChannel) {
|
||||
return (
|
||||
<ViewLoadingFallback
|
||||
includeHeader
|
||||
kind={selectedPostId ? "forum" : "channel"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChannelScreen
|
||||
activeChannel={activeChannel}
|
||||
currentIdentity={identityQuery.data}
|
||||
currentProfile={profileQuery.data}
|
||||
onCloseForumPost={() => {
|
||||
void closeForumPost(channelId);
|
||||
}}
|
||||
onSelectForumPost={(postId) => {
|
||||
void goForumPost(channelId, postId);
|
||||
}}
|
||||
selectedForumPostId={selectedPostId}
|
||||
targetForumReplyId={targetReplyId}
|
||||
targetMessageEvent={targetMessageEvent}
|
||||
targetMessageId={targetMessageId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||
import { useChannelsQuery } from "@/features/channels/hooks";
|
||||
import { WorkflowsScreen } from "@/features/workflows/ui/WorkflowsScreen";
|
||||
|
||||
type WorkflowsRouteScreenProps = {
|
||||
selectedWorkflowId: string | null;
|
||||
};
|
||||
|
||||
export function WorkflowsRouteScreen({
|
||||
selectedWorkflowId,
|
||||
}: WorkflowsRouteScreenProps) {
|
||||
const { closeWorkflowDetail, goWorkflow } = useAppNavigation();
|
||||
const channelsQuery = useChannelsQuery();
|
||||
const channels = channelsQuery.data ?? [];
|
||||
const memberChannels = channels.filter((channel) => channel.isMember);
|
||||
|
||||
return (
|
||||
<WorkflowsScreen
|
||||
channels={memberChannels}
|
||||
onCloseWorkflow={closeWorkflowDetail}
|
||||
onSelectWorkflow={(workflowId) => {
|
||||
void goWorkflow(workflowId);
|
||||
}}
|
||||
selectedWorkflowId={selectedWorkflowId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { ChannelRouteScreen } from "@/app/routes/channels.$channelId";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
type ForumPostRouteSearch = {
|
||||
replyId?: string;
|
||||
@@ -22,16 +23,25 @@ export const Route = createFileRoute("/channels/$channelId/posts/$postId")({
|
||||
component: ForumPostRouteComponent,
|
||||
});
|
||||
|
||||
const ChannelRouteScreen = React.lazy(async () => {
|
||||
const module = await import("./ChannelRouteScreen");
|
||||
return { default: module.ChannelRouteScreen };
|
||||
});
|
||||
|
||||
function ForumPostRouteComponent() {
|
||||
const { channelId, postId } = Route.useParams();
|
||||
const search = Route.useSearch();
|
||||
|
||||
return (
|
||||
<ChannelRouteScreen
|
||||
channelId={channelId}
|
||||
selectedPostId={postId}
|
||||
targetMessageId={null}
|
||||
targetReplyId={search.replyId ?? null}
|
||||
/>
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback includeHeader kind="forum" />}
|
||||
>
|
||||
<ChannelRouteScreen
|
||||
channelId={channelId}
|
||||
selectedPostId={postId}
|
||||
targetMessageId={null}
|
||||
targetReplyId={search.replyId ?? null}
|
||||
/>
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { getCachedSearchHitEvent } from "@/app/navigation/searchHitEventCache";
|
||||
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||
import { useChannelsQuery } from "@/features/channels/hooks";
|
||||
import { ChannelScreen } from "@/features/channels/ui/ChannelScreen";
|
||||
import { useProfileQuery } from "@/features/profile/hooks";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
import { getEventById } from "@/shared/api/tauri";
|
||||
import type { RelayEvent } from "@/shared/api/types";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
type ChannelRouteSearch = {
|
||||
@@ -31,94 +23,25 @@ export const Route = createFileRoute("/channels/$channelId")({
|
||||
component: ChannelRouteComponent,
|
||||
});
|
||||
|
||||
export function ChannelRouteScreen({
|
||||
channelId,
|
||||
selectedPostId,
|
||||
targetMessageId,
|
||||
targetReplyId,
|
||||
}: {
|
||||
channelId: string;
|
||||
selectedPostId: string | null;
|
||||
targetMessageId: string | null;
|
||||
targetReplyId: string | null;
|
||||
}) {
|
||||
const { closeForumPost, goForumPost } = useAppNavigation();
|
||||
const channelsQuery = useChannelsQuery();
|
||||
const identityQuery = useIdentityQuery();
|
||||
const profileQuery = useProfileQuery();
|
||||
const channels = channelsQuery.data ?? [];
|
||||
const activeChannel =
|
||||
channels.find((channel) => channel.id === channelId) ?? null;
|
||||
const [targetMessageEvent, setTargetMessageEvent] =
|
||||
React.useState<RelayEvent | null>(() =>
|
||||
getCachedSearchHitEvent(targetMessageId),
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
if (!targetMessageId || selectedPostId) {
|
||||
setTargetMessageEvent(null);
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}
|
||||
|
||||
setTargetMessageEvent(getCachedSearchHitEvent(targetMessageId));
|
||||
void getEventById(targetMessageId)
|
||||
.then((event) => {
|
||||
if (!isCancelled) {
|
||||
setTargetMessageEvent(event);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!isCancelled) {
|
||||
console.error(
|
||||
"Failed to load route target event",
|
||||
targetMessageId,
|
||||
error,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [selectedPostId, targetMessageId]);
|
||||
|
||||
if (channelsQuery.isPending && !activeChannel) {
|
||||
return <ViewLoadingFallback includeHeader kind="channel" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChannelScreen
|
||||
activeChannel={activeChannel}
|
||||
currentIdentity={identityQuery.data}
|
||||
currentProfile={profileQuery.data}
|
||||
onCloseForumPost={() => {
|
||||
void closeForumPost(channelId);
|
||||
}}
|
||||
onSelectForumPost={(postId) => {
|
||||
void goForumPost(channelId, postId);
|
||||
}}
|
||||
selectedForumPostId={selectedPostId}
|
||||
targetForumReplyId={targetReplyId}
|
||||
targetMessageEvent={targetMessageEvent}
|
||||
targetMessageId={targetMessageId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const ChannelRouteScreen = React.lazy(async () => {
|
||||
const module = await import("./ChannelRouteScreen");
|
||||
return { default: module.ChannelRouteScreen };
|
||||
});
|
||||
|
||||
function ChannelRouteComponent() {
|
||||
const { channelId } = Route.useParams();
|
||||
const search = Route.useSearch();
|
||||
|
||||
return (
|
||||
<ChannelRouteScreen
|
||||
channelId={channelId}
|
||||
selectedPostId={null}
|
||||
targetMessageId={search.messageId ?? null}
|
||||
targetReplyId={null}
|
||||
/>
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback includeHeader kind="channel" />}
|
||||
>
|
||||
<ChannelRouteScreen
|
||||
channelId={channelId}
|
||||
selectedPostId={null}
|
||||
targetMessageId={search.messageId ?? null}
|
||||
targetReplyId={null}
|
||||
/>
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { WorkflowsRouteScreen } from "@/app/routes/workflows";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
export const Route = createFileRoute("/workflows/$workflowId")({
|
||||
component: WorkflowDetailRouteComponent,
|
||||
});
|
||||
|
||||
const WorkflowsRouteScreen = React.lazy(async () => {
|
||||
const module = await import("./WorkflowsRouteScreen");
|
||||
return { default: module.WorkflowsRouteScreen };
|
||||
});
|
||||
|
||||
function WorkflowDetailRouteComponent() {
|
||||
const { workflowId } = Route.useParams();
|
||||
|
||||
return <WorkflowsRouteScreen selectedWorkflowId={workflowId} />;
|
||||
return (
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback includeHeader kind="workflows" />}
|
||||
>
|
||||
<WorkflowsRouteScreen selectedWorkflowId={workflowId} />
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,23 @@
|
||||
import * as React from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||
import { useChannelsQuery } from "@/features/channels/hooks";
|
||||
import { WorkflowsScreen } from "@/features/workflows/ui/WorkflowsScreen";
|
||||
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
export const Route = createFileRoute("/workflows")({
|
||||
component: WorkflowsRouteComponent,
|
||||
});
|
||||
|
||||
export function WorkflowsRouteScreen({
|
||||
selectedWorkflowId,
|
||||
}: {
|
||||
selectedWorkflowId: string | null;
|
||||
}) {
|
||||
const { closeWorkflowDetail, goWorkflow } = useAppNavigation();
|
||||
const channelsQuery = useChannelsQuery();
|
||||
const channels = channelsQuery.data ?? [];
|
||||
const memberChannels = channels.filter((channel) => channel.isMember);
|
||||
|
||||
return (
|
||||
<WorkflowsScreen
|
||||
channels={memberChannels}
|
||||
onCloseWorkflow={closeWorkflowDetail}
|
||||
onSelectWorkflow={(workflowId) => {
|
||||
void goWorkflow(workflowId);
|
||||
}}
|
||||
selectedWorkflowId={selectedWorkflowId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const WorkflowsRouteScreen = React.lazy(async () => {
|
||||
const module = await import("./WorkflowsRouteScreen");
|
||||
return { default: module.WorkflowsRouteScreen };
|
||||
});
|
||||
|
||||
function WorkflowsRouteComponent() {
|
||||
return <WorkflowsRouteScreen selectedWorkflowId={null} />;
|
||||
return (
|
||||
<React.Suspense
|
||||
fallback={<ViewLoadingFallback includeHeader kind="workflows" />}
|
||||
>
|
||||
<WorkflowsRouteScreen selectedWorkflowId={null} />
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ function getNextZoomFactor(action: ZoomAction, zoomFactor: number) {
|
||||
export function useWebviewZoomShortcuts() {
|
||||
const zoomFactorRef = React.useRef(DEFAULT_ZOOM_FACTOR);
|
||||
|
||||
React.useEffect(() => {
|
||||
React.useLayoutEffect(() => {
|
||||
const webview = getCurrentWebview();
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
|
||||
@@ -6,7 +6,11 @@ import { useUsersBatchQuery } from "@/features/profile/hooks";
|
||||
import type { HomeFeedResponse } from "@/shared/api/types";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Skeleton } from "@/shared/ui/skeleton";
|
||||
import { FeedSection } from "./FeedSection";
|
||||
|
||||
const FeedSection = React.lazy(async () => {
|
||||
const module = await import("./FeedSection");
|
||||
return { default: module.FeedSection };
|
||||
});
|
||||
|
||||
type FeedFilter = "all" | "mention" | "needs_action";
|
||||
|
||||
@@ -117,42 +121,44 @@ export function HomeView({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-5 ${singleColumn ? "" : "xl:grid-cols-2"}`}>
|
||||
{showMentions ? (
|
||||
<FeedSection
|
||||
availableChannelIds={availableChannelIds}
|
||||
currentPubkey={currentPubkey}
|
||||
profiles={feedProfiles}
|
||||
doneSet={doneSet}
|
||||
emptyDescription="When someone mentions you, it will land here."
|
||||
emptyTitle="No mentions right now"
|
||||
icon={AtSign}
|
||||
items={feed.feed.mentions}
|
||||
onMarkDone={markDone}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onUndoDone={undoDone}
|
||||
showDoneAction={false}
|
||||
title="Mentions"
|
||||
/>
|
||||
) : null}
|
||||
{showNeedsAction ? (
|
||||
<FeedSection
|
||||
availableChannelIds={availableChannelIds}
|
||||
currentPubkey={currentPubkey}
|
||||
profiles={feedProfiles}
|
||||
doneSet={doneSet}
|
||||
emptyDescription="Approval requests and reminders will appear here."
|
||||
emptyTitle="Nothing needs action"
|
||||
icon={CircleAlert}
|
||||
items={feed.feed.needsAction}
|
||||
onMarkDone={markDone}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onUndoDone={undoDone}
|
||||
showDoneAction={true}
|
||||
title="Needs Action"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<React.Suspense fallback={null}>
|
||||
<div className={`grid gap-5 ${singleColumn ? "" : "xl:grid-cols-2"}`}>
|
||||
{showMentions ? (
|
||||
<FeedSection
|
||||
availableChannelIds={availableChannelIds}
|
||||
currentPubkey={currentPubkey}
|
||||
profiles={feedProfiles}
|
||||
doneSet={doneSet}
|
||||
emptyDescription="When someone mentions you, it will land here."
|
||||
emptyTitle="No mentions right now"
|
||||
icon={AtSign}
|
||||
items={feed.feed.mentions}
|
||||
onMarkDone={markDone}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onUndoDone={undoDone}
|
||||
showDoneAction={false}
|
||||
title="Mentions"
|
||||
/>
|
||||
) : null}
|
||||
{showNeedsAction ? (
|
||||
<FeedSection
|
||||
availableChannelIds={availableChannelIds}
|
||||
currentPubkey={currentPubkey}
|
||||
profiles={feedProfiles}
|
||||
doneSet={doneSet}
|
||||
emptyDescription="Approval requests and reminders will appear here."
|
||||
emptyTitle="Nothing needs action"
|
||||
icon={CircleAlert}
|
||||
items={feed.feed.needsAction}
|
||||
onMarkDone={markDone}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onUndoDone={undoDone}
|
||||
showDoneAction={true}
|
||||
title="Needs Action"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,9 @@ import { installRelayBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
||||
import { openSettings } from "../helpers/settings";
|
||||
import { assertRelaySeeded } from "../helpers/seed";
|
||||
|
||||
const isCi = Boolean(process.env.CI);
|
||||
const relaySeedHookTimeoutMs = isCi ? 90_000 : 30_000;
|
||||
|
||||
async function createStream(
|
||||
page: import("@playwright/test").Page,
|
||||
channelName: string,
|
||||
@@ -156,6 +159,7 @@ async function getLoggedNotificationCount(
|
||||
}
|
||||
|
||||
test.beforeAll(async () => {
|
||||
test.setTimeout(relaySeedHookTimeoutMs);
|
||||
await assertRelaySeeded();
|
||||
});
|
||||
|
||||
|
||||
@@ -280,12 +280,14 @@ test("opens settings with the keyboard shortcut and updates theme", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("Home");
|
||||
|
||||
await page.keyboard.press(
|
||||
process.platform === "darwin" ? "Meta+," : "Control+,",
|
||||
);
|
||||
|
||||
await expect(page.getByTestId("settings-view")).toBeVisible();
|
||||
await expect(page.getByTestId("settings-nav-appearance")).toBeVisible();
|
||||
await page.getByTestId("settings-nav-appearance").click();
|
||||
|
||||
// Default theme is catppuccin-macchiato (dark)
|
||||
@@ -347,6 +349,7 @@ test("opens settings with the keyboard shortcut and updates theme", async ({
|
||||
|
||||
test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("Home");
|
||||
|
||||
await page.keyboard.press(
|
||||
process.platform === "darwin" ? "Meta+Shift+Equal" : "Control+Shift+Equal",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { assertRelaySeeded } from "../helpers/seed";
|
||||
|
||||
const isCi = Boolean(process.env.CI);
|
||||
const relayDeliveryTimeoutMs = isCi ? 15_000 : 5_000;
|
||||
const relaySeedHookTimeoutMs = isCi ? 90_000 : 30_000;
|
||||
|
||||
async function expectTimelineToContain(page: Page, text: string) {
|
||||
await expect(page.getByTestId("message-timeline")).toContainText(text, {
|
||||
@@ -94,6 +95,7 @@ async function scrollTimelineAwayFromBottom(page: Page, minDistance = 160) {
|
||||
}
|
||||
|
||||
test.beforeAll(async () => {
|
||||
test.setTimeout(relaySeedHookTimeoutMs);
|
||||
await assertRelaySeeded();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user