Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 8 additions & 37 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { CSS } from "@dnd-kit/utilities";
import {
DEFAULT_RUNTIME_MODE,
DEFAULT_MODEL_BY_PROVIDER,
type DesktopUpdateState,
ProjectId,
ThreadId,
type GitStatusResult,
Expand All @@ -39,7 +38,8 @@ import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/rea
import { useLocation, useNavigate, useParams } from "@tanstack/react-router";
import { useAppSettings } from "../appSettings";
import { isElectron } from "../env";
import { APP_STAGE_LABEL, APP_VERSION } from "../branding";
import { APP_STAGE_LABEL } from "../branding";
import { resolveDisplayedAppVersion, useDesktopUpdateState } from "../hooks/useDesktopUpdateState";
import { isMacPlatform, newCommandId, newProjectId, newThreadId } from "../lib/utils";
import { useStore } from "../store";
import { isChatNewLocalShortcut, isChatNewShortcut, shortcutLabelForCommand } from "../keybindings";
Expand Down Expand Up @@ -301,7 +301,7 @@ export default function Sidebar() {
const renamingInputRef = useRef<HTMLInputElement | null>(null);
const dragInProgressRef = useRef(false);
const suppressProjectClickAfterDragRef = useRef(false);
const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null);
const desktopUpdateState = useDesktopUpdateState();
const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds);
const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread);
const rangeSelectTo = useThreadSelectionStore((s) => s.rangeSelectTo);
Expand Down Expand Up @@ -1078,40 +1078,11 @@ export default function Sidebar() {
threads,
]);

useEffect(() => {
if (!isElectron) return;
const bridge = window.desktopBridge;
if (
!bridge ||
typeof bridge.getUpdateState !== "function" ||
typeof bridge.onUpdateState !== "function"
) {
return;
}

let disposed = false;
let receivedSubscriptionUpdate = false;
const unsubscribe = bridge.onUpdateState((nextState) => {
if (disposed) return;
receivedSubscriptionUpdate = true;
setDesktopUpdateState(nextState);
});

void bridge
.getUpdateState()
.then((nextState) => {
if (disposed || receivedSubscriptionUpdate) return;
setDesktopUpdateState(nextState);
})
.catch(() => undefined);

return () => {
disposed = true;
unsubscribe();
};
}, []);

const showDesktopUpdateButton = isElectron && shouldShowDesktopUpdateButton(desktopUpdateState);
const displayedAppVersion = resolveDisplayedAppVersion({
desktopUpdateState,
isDesktopRuntime: isElectron,
});

const desktopUpdateTooltip = desktopUpdateState
? getDesktopUpdateButtonTooltip(desktopUpdateState)
Expand Down Expand Up @@ -1239,7 +1210,7 @@ export default function Sidebar() {
}
/>
<TooltipPopup side="bottom" sideOffset={2}>
Version {APP_VERSION}
Version {displayedAppVersion}
</TooltipPopup>
</Tooltip>
</div>
Expand Down
52 changes: 52 additions & 0 deletions apps/web/src/hooks/useDesktopUpdateState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import type { DesktopUpdateState } from "@t3tools/contracts";

import { resolveDisplayedAppVersion } from "./useDesktopUpdateState";

const baseDesktopUpdateState: DesktopUpdateState = {
enabled: true,
status: "up-to-date",
currentVersion: "0.0.11",
hostArch: "arm64",
appArch: "arm64",
runningUnderArm64Translation: false,
availableVersion: null,
downloadedVersion: null,
downloadPercent: null,
checkedAt: null,
message: null,
errorContext: null,
canRetry: false,
};

describe("resolveDisplayedAppVersion", () => {
it("keeps the fallback version outside desktop runtime", () => {
expect(
resolveDisplayedAppVersion({
desktopUpdateState: baseDesktopUpdateState,
fallbackVersion: "0.0.10",
isDesktopRuntime: false,
}),
).toBe("0.0.10");
});

it("prefers the desktop runtime version when available", () => {
expect(
resolveDisplayedAppVersion({
desktopUpdateState: baseDesktopUpdateState,
fallbackVersion: "0.0.10",
isDesktopRuntime: true,
}),
).toBe("0.0.11");
});

it("falls back when the runtime state is missing", () => {
expect(
resolveDisplayedAppVersion({
desktopUpdateState: null,
fallbackVersion: "0.0.10",
isDesktopRuntime: true,
}),
).toBe("0.0.10");
});
});
63 changes: 63 additions & 0 deletions apps/web/src/hooks/useDesktopUpdateState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect, useState } from "react";
import type { DesktopUpdateState } from "@t3tools/contracts";

import { APP_VERSION } from "../branding";
import { isElectron } from "../env";

interface ResolveDisplayedAppVersionInput {
readonly desktopUpdateState: DesktopUpdateState | null;
readonly fallbackVersion?: string;
readonly isDesktopRuntime: boolean;
}

export function resolveDisplayedAppVersion({
desktopUpdateState,
fallbackVersion = APP_VERSION,
isDesktopRuntime,
}: ResolveDisplayedAppVersionInput): string {
if (!isDesktopRuntime) {
return fallbackVersion;
}

const runtimeVersion = desktopUpdateState?.currentVersion.trim();
return runtimeVersion && runtimeVersion.length > 0 ? runtimeVersion : fallbackVersion;
}

export function useDesktopUpdateState(): DesktopUpdateState | null {
const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null);

useEffect(() => {
if (!isElectron) return;
const bridge = window.desktopBridge;
if (
!bridge ||
typeof bridge.getUpdateState !== "function" ||
typeof bridge.onUpdateState !== "function"
) {
return;
}

let disposed = false;
let receivedSubscriptionUpdate = false;
const unsubscribe = bridge.onUpdateState((nextState) => {
if (disposed) return;
receivedSubscriptionUpdate = true;
setDesktopUpdateState(nextState);
});

void bridge
.getUpdateState()
.then((nextState) => {
if (disposed || receivedSubscriptionUpdate) return;
setDesktopUpdateState(nextState);
})
.catch(() => undefined);

return () => {
disposed = true;
unsubscribe();
};
}, []);

return desktopUpdateState;
}
11 changes: 9 additions & 2 deletions apps/web/src/routes/_chat.settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { getModelOptions, normalizeModelSlug } from "@t3tools/shared/model";

import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings";
import { isElectron } from "../env";
import { resolveDisplayedAppVersion, useDesktopUpdateState } from "../hooks/useDesktopUpdateState";
import { useTheme } from "../hooks/useTheme";
import { serverConfigQueryOptions } from "../lib/serverReactQuery";
import { ensureNativeApi } from "../nativeApi";
import { preferredTerminalEditor } from "../terminal-links";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Switch } from "../components/ui/switch";
import { APP_VERSION } from "../branding";
import { SidebarInset } from "~/components/ui/sidebar";

const THEME_OPTIONS = [
Expand Down Expand Up @@ -84,6 +84,7 @@ function SettingsRouteView() {
const { theme, setTheme, resolvedTheme } = useTheme();
const { settings, defaults, updateSettings } = useAppSettings();
const serverConfigQuery = useQuery(serverConfigQueryOptions());
const desktopUpdateState = useDesktopUpdateState();
const [isOpeningKeybindings, setIsOpeningKeybindings] = useState(false);
const [openKeybindingsError, setOpenKeybindingsError] = useState<string | null>(null);
const [customModelInputByProvider, setCustomModelInputByProvider] = useState<
Expand All @@ -98,6 +99,10 @@ function SettingsRouteView() {
const codexBinaryPath = settings.codexBinaryPath;
const codexHomePath = settings.codexHomePath;
const keybindingsConfigPath = serverConfigQuery.data?.keybindingsConfigPath ?? null;
const displayedAppVersion = resolveDisplayedAppVersion({
desktopUpdateState,
isDesktopRuntime: isElectron,
});

const openKeybindingsFile = useCallback(() => {
if (!keybindingsConfigPath) return;
Expand Down Expand Up @@ -573,7 +578,9 @@ function SettingsRouteView() {
Current version of the application.
</p>
</div>
<code className="text-xs font-medium text-muted-foreground">{APP_VERSION}</code>
<code className="text-xs font-medium text-muted-foreground">
{displayedAppVersion}
</code>
</div>
</section>
</div>
Expand Down
Loading