diff --git a/package-lock.json b/package-lock.json index 1538447..1e345b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "@types/react-dom": "^19.2.3", "@types/shell-quote": "^1.7.5", "@types/uuid": "^10.0.0", + "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.3.4", "@vitest/ui": "^4.0.15", "autoprefixer": "^10.4.20", @@ -4389,6 +4390,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", diff --git a/package.json b/package.json index 46d704a..8320d18 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@types/react-dom": "^19.2.3", "@types/shell-quote": "^1.7.5", "@types/uuid": "^10.0.0", + "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.3.4", "@vitest/ui": "^4.0.15", "autoprefixer": "^10.4.20", diff --git a/src/components/CommandLauncher.tsx b/src/components/CommandLauncher.tsx index a4f487c..6e4e1f5 100644 --- a/src/components/CommandLauncher.tsx +++ b/src/components/CommandLauncher.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { Command } from "cmdk"; import { useAtom } from "jotai"; import { useLiveQuery } from "dexie-react-hooks"; +import { useNavigate, useLocation } from "react-router"; import db from "@/services/db"; import { useGrimoire } from "@/core/state"; import { manPages } from "@/types/man"; @@ -11,6 +12,19 @@ import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { VisuallyHidden } from "@/components/ui/visually-hidden"; import "./command-launcher.css"; +/** Check if current path is a NIP-19 preview route (no window system) */ +function isNip19PreviewRoute(pathname: string): boolean { + // NIP-19 preview routes are single-segment paths starting with npub1, note1, nevent1, naddr1 + const segment = pathname.slice(1); // Remove leading / + if (segment.includes("/")) return false; // Multi-segment paths are not NIP-19 previews + return ( + segment.startsWith("npub1") || + segment.startsWith("note1") || + segment.startsWith("nevent1") || + segment.startsWith("naddr1") + ); +} + interface CommandLauncherProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -23,6 +37,8 @@ export default function CommandLauncher({ const [input, setInput] = useState(""); const [editMode, setEditMode] = useAtom(commandLauncherEditModeAtom); const { state, addWindow, updateWindow } = useGrimoire(); + const navigate = useNavigate(); + const location = useLocation(); // Fetch spells with aliases const aliasedSpells = @@ -107,6 +123,12 @@ export default function CommandLauncher({ }); setEditMode(null); // Clear edit mode } else { + // If on a NIP-19 preview route (no window system), navigate to dashboard first + // The window will appear after navigation since state persists + if (isNip19PreviewRoute(location.pathname)) { + navigate("/"); + } + // Normal mode: create new window addWindow( recognizedCommand.appId, diff --git a/src/test/setup.ts b/src/test/setup.ts index eba54c2..cc8847a 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,7 +1,12 @@ /** * Vitest setup file * - * Polyfills IndexedDB for Node.js test environment. - * This allows Dexie to work in tests without a browser. + * Polyfills browser APIs for Node.js test environment. */ + +// Polyfill IndexedDB - allows Dexie to work in tests import "fake-indexeddb/auto"; + +// Polyfill WebSocket - required by nostr-tools relay code +import { WebSocket } from "ws"; +globalThis.WebSocket = WebSocket as unknown as typeof globalThis.WebSocket;