Merge origin/main into dev (second round)
Resolve 21 conflicts from latest main (provider profiles, Windows fixes, 0.1.55-rc.1). Fix all type errors and syntax issues from merge artifacts.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { AppState, Platform } from "react-native";
|
||||
import { AppState } from "react-native";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import {
|
||||
shouldClearAgentAttention,
|
||||
type AgentAttentionClearTrigger,
|
||||
} from "@/utils/agent-attention";
|
||||
import { getIsAppActivelyVisible } from "@/utils/app-visibility";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
type AttentionReason = "finished" | "error" | "permission" | null | undefined;
|
||||
|
||||
@@ -70,7 +71,7 @@ export function useAgentAttentionClear({
|
||||
|
||||
const appStateSubscription = AppState.addEventListener("change", updateVisibility);
|
||||
|
||||
if (Platform.OS === "web" && typeof document !== "undefined") {
|
||||
if (isWeb && typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", updateVisibility);
|
||||
window.addEventListener("focus", updateVisibility);
|
||||
window.addEventListener("blur", updateVisibility);
|
||||
|
||||
@@ -1,12 +1,117 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __private__ } from "./use-agent-form-state";
|
||||
import {
|
||||
AGENT_PROVIDER_DEFINITIONS,
|
||||
type AgentProviderDefinition,
|
||||
} from "@server/server/agent/provider-manifest";
|
||||
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||
import { buildProviderDefinitions } from "@/utils/provider-definitions";
|
||||
import type { AgentProviderDefinition } from "@server/server/agent/provider-manifest";
|
||||
import type {
|
||||
AgentModelDefinition,
|
||||
AgentProvider,
|
||||
ProviderSnapshotEntry,
|
||||
} from "@server/server/agent/agent-sdk-types";
|
||||
|
||||
const TEST_CODEX_DEFINITION: AgentProviderDefinition = {
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
description: "Codex test provider",
|
||||
defaultModeId: "auto",
|
||||
modes: [
|
||||
{ id: "auto", label: "Auto", icon: "ShieldAlert", colorTier: "moderate" },
|
||||
{ id: "full-access", label: "Full Access", icon: "ShieldAlert", colorTier: "dangerous" },
|
||||
],
|
||||
};
|
||||
|
||||
const TEST_CLAUDE_DEFINITION: AgentProviderDefinition = {
|
||||
id: "claude",
|
||||
label: "Claude",
|
||||
description: "Claude test provider",
|
||||
defaultModeId: "default",
|
||||
modes: [
|
||||
{ id: "default", label: "Always Ask", icon: "ShieldCheck", colorTier: "safe" },
|
||||
{ id: "acceptEdits", label: "Accept File Edits", icon: "ShieldAlert", colorTier: "moderate" },
|
||||
{ id: "plan", label: "Plan Mode", icon: "ShieldCheck", colorTier: "planning" },
|
||||
{ id: "bypassPermissions", label: "Bypass", icon: "ShieldAlert", colorTier: "dangerous" },
|
||||
],
|
||||
};
|
||||
|
||||
function makeProviderMap(
|
||||
...definitions: AgentProviderDefinition[]
|
||||
): Map<AgentProvider, AgentProviderDefinition> {
|
||||
return new Map(definitions.map((d) => [d.id as AgentProvider, d]));
|
||||
}
|
||||
|
||||
const codexProviderMap = makeProviderMap(TEST_CODEX_DEFINITION);
|
||||
const claudeProviderMap = makeProviderMap(TEST_CLAUDE_DEFINITION);
|
||||
|
||||
describe("useAgentFormState", () => {
|
||||
describe("buildProviderDefinitions", () => {
|
||||
it("returns empty array when snapshot data is unavailable", () => {
|
||||
expect(buildProviderDefinitions(undefined)).toEqual([]);
|
||||
expect(buildProviderDefinitions([])).toEqual([]);
|
||||
});
|
||||
|
||||
it("builds custom provider definitions from snapshot metadata", () => {
|
||||
const entries: ProviderSnapshotEntry[] = [
|
||||
{
|
||||
provider: "zai",
|
||||
status: "ready",
|
||||
label: "ZAI",
|
||||
description: "Claude with ZAI config",
|
||||
defaultModeId: "default",
|
||||
modes: [
|
||||
{
|
||||
id: "default",
|
||||
label: "Default",
|
||||
description: "Safe mode",
|
||||
icon: "ShieldCheck",
|
||||
colorTier: "safe",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: "claude",
|
||||
status: "ready",
|
||||
label: "Claude",
|
||||
description: "Anthropic Claude",
|
||||
defaultModeId: "default",
|
||||
modes: [{ id: "default", label: "Always Ask", icon: "ShieldCheck", colorTier: "safe" }],
|
||||
},
|
||||
];
|
||||
|
||||
const definitions = buildProviderDefinitions(entries);
|
||||
|
||||
expect(definitions).toEqual([
|
||||
{
|
||||
id: "zai",
|
||||
label: "ZAI",
|
||||
description: "Claude with ZAI config",
|
||||
defaultModeId: "default",
|
||||
modes: [
|
||||
{
|
||||
id: "default",
|
||||
label: "Default",
|
||||
description: "Safe mode",
|
||||
icon: "ShieldCheck",
|
||||
colorTier: "safe",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "claude",
|
||||
label: "Claude",
|
||||
description: "Anthropic Claude",
|
||||
defaultModeId: "default",
|
||||
modes: [
|
||||
{
|
||||
id: "default",
|
||||
label: "Always Ask",
|
||||
icon: "ShieldCheck",
|
||||
colorTier: "safe",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("__private__.combineInitialValues", () => {
|
||||
it("returns undefined when no initial values and no initial server id", () => {
|
||||
expect(__private__.combineInitialValues(undefined, null)).toBeUndefined();
|
||||
@@ -78,6 +183,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("");
|
||||
@@ -106,6 +212,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("gpt-5.3-codex");
|
||||
@@ -134,6 +241,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.thinkingOptionId).toBe("xhigh");
|
||||
@@ -161,6 +269,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("gpt-5.3-codex");
|
||||
@@ -188,6 +297,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("gpt-5.3-codex");
|
||||
@@ -215,6 +325,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("gpt-5.3-codex");
|
||||
@@ -256,6 +367,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
claudeProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("default");
|
||||
@@ -263,11 +375,6 @@ describe("useAgentFormState", () => {
|
||||
});
|
||||
|
||||
it("resolves provider only from allowed provider map", () => {
|
||||
const allowedProviderMap = new Map<AgentProvider, AgentProviderDefinition>(
|
||||
AGENT_PROVIDER_DEFINITIONS.filter((definition) => definition.id === "claude").map(
|
||||
(definition) => [definition.id as AgentProvider, definition],
|
||||
),
|
||||
);
|
||||
const resolved = __private__.resolveFormState(
|
||||
undefined,
|
||||
{ provider: "codex" },
|
||||
@@ -289,7 +396,7 @@ describe("useAgentFormState", () => {
|
||||
workingDir: "",
|
||||
},
|
||||
new Set<string>(),
|
||||
allowedProviderMap,
|
||||
claudeProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.provider).toBe("claude");
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AGENT_PROVIDER_DEFINITIONS,
|
||||
type AgentProviderDefinition,
|
||||
} from "@server/server/agent/provider-manifest";
|
||||
import type { AgentProviderDefinition } from "@server/server/agent/provider-manifest";
|
||||
import type {
|
||||
AgentMode,
|
||||
AgentModelDefinition,
|
||||
@@ -10,6 +7,7 @@ import type {
|
||||
ProviderSnapshotEntry,
|
||||
} from "@server/server/agent/agent-sdk-types";
|
||||
import { useHosts } from "@/runtime/host-runtime";
|
||||
import { buildProviderDefinitions } from "@/utils/provider-definitions";
|
||||
import { useProvidersSnapshot } from "./use-providers-snapshot";
|
||||
import {
|
||||
useFormPreferences,
|
||||
@@ -99,13 +97,8 @@ export type UseAgentFormStateResult = {
|
||||
persistFormPreferences: () => Promise<void>;
|
||||
};
|
||||
|
||||
const allProviderDefinitions = AGENT_PROVIDER_DEFINITIONS;
|
||||
const allProviderDefinitionMap = new Map<AgentProvider, AgentProviderDefinition>(
|
||||
allProviderDefinitions.map((definition) => [definition.id, definition]),
|
||||
);
|
||||
const fallbackDefinition = allProviderDefinitions[0];
|
||||
const DEFAULT_PROVIDER: AgentProvider = fallbackDefinition?.id ?? "claude";
|
||||
const DEFAULT_MODE_FOR_DEFAULT_PROVIDER = fallbackDefinition?.defaultModeId ?? "";
|
||||
const DEFAULT_PROVIDER: AgentProvider = "claude";
|
||||
const DEFAULT_MODE_FOR_DEFAULT_PROVIDER = "default";
|
||||
|
||||
function normalizeSelectedModelId(modelId: string | null | undefined): string {
|
||||
const normalized = typeof modelId === "string" ? modelId.trim() : "";
|
||||
@@ -180,7 +173,7 @@ function resolveFormState(
|
||||
userModified: UserModifiedFields,
|
||||
currentState: FormState,
|
||||
validServerIds: Set<string>,
|
||||
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition> = allProviderDefinitionMap,
|
||||
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition>,
|
||||
): FormState {
|
||||
// Start with current state - we only update non-user-modified fields
|
||||
const result = { ...currentState };
|
||||
@@ -374,10 +367,10 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
} = useProvidersSnapshot(formState.serverId);
|
||||
|
||||
const allProviderEntries = useMemo(() => snapshotEntries ?? [], [snapshotEntries]);
|
||||
const snapshotProviderDefinitions = useMemo(() => {
|
||||
const snapshotProviders = new Set((snapshotEntries ?? []).map((entry) => entry.provider));
|
||||
return allProviderDefinitions.filter((definition) => snapshotProviders.has(definition.id));
|
||||
}, [snapshotEntries]);
|
||||
const snapshotProviderDefinitions = useMemo(
|
||||
() => buildProviderDefinitions(snapshotEntries),
|
||||
[snapshotEntries],
|
||||
);
|
||||
const snapshotProviderDefinitionMap = useMemo(
|
||||
() =>
|
||||
new Map<AgentProvider, AgentProviderDefinition>(
|
||||
@@ -386,17 +379,18 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
[snapshotProviderDefinitions],
|
||||
);
|
||||
const snapshotSelectableProviderDefinitionMap = useMemo(() => {
|
||||
if (!snapshotEntries?.length) {
|
||||
return snapshotProviderDefinitionMap;
|
||||
}
|
||||
const readyProviders = new Set(
|
||||
(snapshotEntries ?? [])
|
||||
.filter((entry) => entry.status === "ready")
|
||||
.map((entry) => entry.provider),
|
||||
snapshotEntries.filter((entry) => entry.status === "ready").map((entry) => entry.provider),
|
||||
);
|
||||
return new Map<AgentProvider, AgentProviderDefinition>(
|
||||
snapshotProviderDefinitions
|
||||
.filter((definition) => readyProviders.has(definition.id))
|
||||
.map((definition) => [definition.id, definition]),
|
||||
);
|
||||
}, [snapshotEntries, snapshotProviderDefinitions]);
|
||||
}, [snapshotEntries, snapshotProviderDefinitionMap, snapshotProviderDefinitions]);
|
||||
const snapshotAllProviderModels = useMemo(() => {
|
||||
const map = new Map<string, AgentModelDefinition[]>();
|
||||
for (const entry of snapshotEntries ?? []) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import {
|
||||
@@ -10,13 +9,14 @@ import {
|
||||
rejectInitDeferred,
|
||||
} from "@/utils/agent-initialization";
|
||||
import { deriveInitialTimelineRequest } from "@/contexts/session-timeline-bootstrap-policy";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
const INIT_TIMEOUT_MS = 5 * 60_000;
|
||||
const NATIVE_INITIAL_TIMELINE_LIMIT = 200;
|
||||
const UNBOUNDED_TIMELINE_LIMIT = 0;
|
||||
|
||||
function resolveInitialTimelineLimit(): number {
|
||||
return Platform.OS === "web" ? UNBOUNDED_TIMELINE_LIMIT : NATIVE_INITIAL_TIMELINE_LIMIT;
|
||||
return isWeb ? UNBOUNDED_TIMELINE_LIMIT : NATIVE_INITIAL_TIMELINE_LIMIT;
|
||||
}
|
||||
|
||||
export const __private__ = {
|
||||
|
||||
@@ -123,7 +123,9 @@ function buildDraftComposerCommandConfig(input: {
|
||||
return {
|
||||
provider: input.provider,
|
||||
cwd,
|
||||
...(input.modeOptions.length > 0 && input.selectedMode !== "" ? { modeId: input.selectedMode } : {}),
|
||||
...(input.modeOptions.length > 0 && input.selectedMode !== ""
|
||||
? { modeId: input.selectedMode }
|
||||
: {}),
|
||||
...(input.effectiveModelId ? { model: input.effectiveModelId } : {}),
|
||||
...(input.effectiveThinkingOptionId
|
||||
? { thinkingOptionId: input.effectiveThinkingOptionId }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useSyncExternalStore } from "react";
|
||||
import { AppState, Platform } from "react-native";
|
||||
import { AppState } from "react-native";
|
||||
import { getIsAppActivelyVisible } from "@/utils/app-visibility";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
let current = getIsAppActivelyVisible();
|
||||
const listeners = new Set<() => void>();
|
||||
@@ -29,7 +30,7 @@ export function useAppVisible(): boolean {
|
||||
useEffect(() => {
|
||||
const appStateSubscription = AppState.addEventListener("change", notify);
|
||||
|
||||
if (Platform.OS === "web" && typeof document !== "undefined") {
|
||||
if (isWeb && typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", notify);
|
||||
window.addEventListener("focus", notify);
|
||||
window.addEventListener("blur", notify);
|
||||
@@ -37,7 +38,7 @@ export function useAppVisible(): boolean {
|
||||
|
||||
return () => {
|
||||
appStateSubscription.remove();
|
||||
if (Platform.OS === "web" && typeof document !== "undefined") {
|
||||
if (isWeb && typeof document !== "undefined") {
|
||||
document.removeEventListener("visibilitychange", notify);
|
||||
window.removeEventListener("focus", notify);
|
||||
window.removeEventListener("blur", notify);
|
||||
|
||||
@@ -135,18 +135,8 @@ describe("useArchiveAgent", () => {
|
||||
|
||||
it("can apply archived agent close results without invalidating cached lists", () => {
|
||||
const queryClient = new QueryClient();
|
||||
useSessionStore
|
||||
.getState()
|
||||
.initializeSession("server-a", {} as DaemonClient);
|
||||
useSessionStore.getState().setAgents(
|
||||
"server-a",
|
||||
new Map([
|
||||
[
|
||||
"agent-1",
|
||||
makeAgent(),
|
||||
],
|
||||
]),
|
||||
);
|
||||
useSessionStore.getState().initializeSession("server-a", {} as DaemonClient);
|
||||
useSessionStore.getState().setAgents("server-a", new Map([["agent-1", makeAgent()]]));
|
||||
queryClient.setQueryData(["sidebarAgentsList", "server-a"], {
|
||||
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
||||
});
|
||||
|
||||
@@ -181,7 +181,11 @@ function restoreArchivedAgentListCacheSnapshot(
|
||||
serverId: string,
|
||||
snapshot: ArchivedAgentListCacheSnapshot,
|
||||
): void {
|
||||
restoreCachedListSnapshot(queryClient, ["sidebarAgentsList", serverId], snapshot.sidebarAgentsList);
|
||||
restoreCachedListSnapshot(
|
||||
queryClient,
|
||||
["sidebarAgentsList", serverId],
|
||||
snapshot.sidebarAgentsList,
|
||||
);
|
||||
restoreCachedListSnapshot(queryClient, ["allAgents", serverId], snapshot.allAgents);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import { AppState, Platform } from "react-native";
|
||||
import { AppState } from "react-native";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { isWeb, isNative } from "@/constants/platform";
|
||||
|
||||
const HEARTBEAT_INTERVAL_MS = 15_000;
|
||||
const ACTIVITY_HEARTBEAT_THROTTLE_MS = 5_000;
|
||||
@@ -32,7 +33,7 @@ export function useClientActivity({
|
||||
const prevFocusedAgentIdRef = useRef<string | null>(focusedAgentId);
|
||||
const lastImmediateHeartbeatAtRef = useRef<number>(0);
|
||||
|
||||
const deviceType = Platform.OS === "web" ? "web" : "mobile";
|
||||
const deviceType = isWeb ? "web" : "mobile";
|
||||
|
||||
const recordUserActivity = useCallback(() => {
|
||||
lastActivityAtRef.current = new Date();
|
||||
@@ -96,7 +97,7 @@ export function useClientActivity({
|
||||
|
||||
// Track user activity on web for accurate staleness.
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web") return;
|
||||
if (isNative) return;
|
||||
if (typeof document === "undefined") return;
|
||||
|
||||
const handleUserActivity = () => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { useShallow } from "zustand/shallow";
|
||||
import { getIsElectronRuntimeMac } from "@/constants/layout";
|
||||
import { useAggregatedAgents } from "./use-aggregated-agents";
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
deriveMacDockBadgeCountFromWorkspaceStatuses,
|
||||
type DesktopBadgeWorkspaceStatus,
|
||||
} from "@/utils/desktop-badge-state";
|
||||
import { isNative } from "@/constants/platform";
|
||||
|
||||
type FaviconStatus = "none" | "running" | "attention";
|
||||
type ColorScheme = "dark" | "light";
|
||||
@@ -76,18 +76,14 @@ function updateFavicon(status: FaviconStatus, colorScheme: ColorScheme) {
|
||||
}
|
||||
|
||||
function getSystemColorScheme(): ColorScheme {
|
||||
if (
|
||||
Platform.OS !== "web" ||
|
||||
typeof window === "undefined" ||
|
||||
typeof window.matchMedia !== "function"
|
||||
) {
|
||||
if (isNative || typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
||||
return "dark";
|
||||
}
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
async function updateMacDockBadge(count?: number) {
|
||||
if (Platform.OS !== "web" || !getIsElectronRuntimeMac()) return;
|
||||
if (isNative || !getIsElectronRuntimeMac()) return;
|
||||
|
||||
const desktopWindow = getDesktopHost()?.window?.getCurrentWindow?.();
|
||||
if (!desktopWindow || typeof desktopWindow.setBadgeCount !== "function") {
|
||||
@@ -119,7 +115,7 @@ export function useFaviconStatus() {
|
||||
|
||||
// Listen for system color scheme changes
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web" || typeof window === "undefined") return;
|
||||
if (isNative || typeof window === "undefined") return;
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handler = (e: MediaQueryListEvent) => {
|
||||
@@ -132,7 +128,7 @@ export function useFaviconStatus() {
|
||||
|
||||
// Update favicon when agents or color scheme changes
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web") return;
|
||||
if (isNative) return;
|
||||
|
||||
const status = deriveFaviconStatus(agents);
|
||||
updateFavicon(status, colorScheme);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import type { ImageAttachment } from "@/components/message-input";
|
||||
import { getDesktopHost } from "@/desktop/host";
|
||||
import { persistAttachmentFromBlob, persistAttachmentFromFileUri } from "@/attachments/service";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
interface UseFileDropZoneOptions {
|
||||
onFilesDropped: (files: ImageAttachment[]) => void;
|
||||
@@ -14,7 +14,7 @@ interface UseFileDropZoneReturn {
|
||||
containerRef: React.RefObject<HTMLElement | null>;
|
||||
}
|
||||
|
||||
const IS_WEB = Platform.OS === "web";
|
||||
const IS_WEB = isWeb;
|
||||
const IMAGE_MIME_BY_EXTENSION: Record<string, string> = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
openImagePathsWithDesktopDialog,
|
||||
type PickedImageAttachmentInput,
|
||||
} from "@/hooks/image-attachment-picker";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
interface UseImageAttachmentPickerResult {
|
||||
pickImages: () => Promise<PickedImageAttachmentInput[] | null>;
|
||||
@@ -45,7 +46,7 @@ export function useImageAttachmentPicker(): UseImageAttachmentPickerResult {
|
||||
isPickingRef.current = true;
|
||||
|
||||
try {
|
||||
if (Platform.OS === "web" && isElectronRuntime()) {
|
||||
if (isWeb && isElectronRuntime()) {
|
||||
const selectedPaths = await openImagePathsWithDesktopDialog();
|
||||
if (selectedPaths.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { usePathname } from "expo-router";
|
||||
import { usePathname, useRouter } from "expo-router";
|
||||
import { getIsElectronRuntime } from "@/constants/layout";
|
||||
import { useHosts } from "@/runtime/host-runtime";
|
||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
||||
import {
|
||||
buildHostSettingsRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
parseServerIdFromPathname,
|
||||
parseHostWorkspaceRouteFromPathname,
|
||||
@@ -26,6 +26,7 @@ import { resolveKeyboardFocusScope } from "@/keyboard/focus-scope";
|
||||
import { getShortcutOs } from "@/utils/shortcut-platform";
|
||||
import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
|
||||
import { useKeyboardShortcutOverrides } from "@/hooks/use-keyboard-shortcut-overrides";
|
||||
import { isNative } from "@/constants/platform";
|
||||
|
||||
export function useKeyboardShortcuts({
|
||||
enabled,
|
||||
@@ -47,6 +48,7 @@ export function useKeyboardShortcuts({
|
||||
cycleTheme?: () => void;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const hosts = useHosts();
|
||||
const resetModifiers = useKeyboardShortcutsStore((s) => s.resetModifiers);
|
||||
const { overrides } = useKeyboardShortcutOverrides();
|
||||
@@ -65,7 +67,7 @@ export function useKeyboardShortcuts({
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
if (Platform.OS !== "web") return;
|
||||
if (isNative) return;
|
||||
if (isMobile) return;
|
||||
|
||||
const isDesktopApp = getIsElectronRuntime();
|
||||
@@ -244,6 +246,16 @@ export function useKeyboardShortcuts({
|
||||
case "sidebar.toggle.left":
|
||||
toggleAgentList();
|
||||
return true;
|
||||
case "settings.toggle":
|
||||
if (pathname.endsWith("/settings")) {
|
||||
router.back();
|
||||
return true;
|
||||
}
|
||||
if (!activeServerId) {
|
||||
return false;
|
||||
}
|
||||
router.push(buildHostSettingsRoute(activeServerId));
|
||||
return true;
|
||||
case "sidebar.toggle.both":
|
||||
if (toggleBothSidebars) {
|
||||
toggleBothSidebars();
|
||||
|
||||
@@ -93,7 +93,9 @@ describe("openProjectDirectly", () => {
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(useSessionStore.getState().sessions[SERVER_ID]?.hasHydratedWorkspaces).toBe(true);
|
||||
expect(Array.from(useSessionStore.getState().sessions[SERVER_ID]?.workspaces.values() ?? [])).toEqual([
|
||||
expect(
|
||||
Array.from(useSessionStore.getState().sessions[SERVER_ID]?.workspaces.values() ?? []),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "1",
|
||||
projectId: "1",
|
||||
|
||||
@@ -2,7 +2,11 @@ import { useCallback } from "react";
|
||||
import { router } from "expo-router";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||
import { normalizeWorkspaceDescriptor, type WorkspaceDescriptor, useSessionStore } from "@/stores/session-store";
|
||||
import {
|
||||
normalizeWorkspaceDescriptor,
|
||||
type WorkspaceDescriptor,
|
||||
useSessionStore,
|
||||
} from "@/stores/session-store";
|
||||
import {
|
||||
buildWorkspaceTabPersistenceKey,
|
||||
useWorkspaceLayoutStore,
|
||||
|
||||
@@ -4,6 +4,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as Notifications from "expo-notifications";
|
||||
import Constants from "expo-constants";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
const STORAGE_PREFIX = "@paseo:expo-push-token:";
|
||||
|
||||
@@ -31,7 +32,7 @@ export function usePushTokenRegistration(params: { client: DaemonClient; serverI
|
||||
const lastSentTokenRef = useRef<string | null>(null);
|
||||
|
||||
const registerIfPossible = useCallback(async () => {
|
||||
if (Platform.OS === "web") return;
|
||||
if (isWeb) return;
|
||||
if (!client.isConnected) return;
|
||||
const token = tokenRef.current;
|
||||
if (!token) return;
|
||||
@@ -41,7 +42,7 @@ export function usePushTokenRegistration(params: { client: DaemonClient; serverI
|
||||
}, [client]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === "web") return;
|
||||
if (isWeb) return;
|
||||
|
||||
const storageKey = `${STORAGE_PREFIX}${serverId}`;
|
||||
let cancelled = false;
|
||||
|
||||
@@ -236,7 +236,6 @@ function getWorkspaceOrderScopeKey(serverId: string, projectKey: string): string
|
||||
|
||||
function toWorkspaceDescriptor(payload: WorkspaceDescriptorPayload): WorkspaceDescriptor {
|
||||
return normalizeWorkspaceDescriptor(payload);
|
||||
|
||||
}
|
||||
|
||||
export function useSidebarWorkspacesList(options?: {
|
||||
|
||||
Reference in New Issue
Block a user