Skip to content

Commit df778e7

Browse files
committed
feat(app): suppress native context menu
1 parent 225fd2c commit df778e7

4 files changed

Lines changed: 60 additions & 7 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// @vitest-environment jsdom
2+
import { cleanup, render } from "@testing-library/react";
3+
import { afterEach, expect, test, vi } from "vite-plus/test";
4+
import { NativeContextMenu } from "./native-context-menu";
5+
6+
afterEach(() => {
7+
cleanup();
8+
vi.unstubAllEnvs();
9+
});
10+
11+
function openContextMenu(): MouseEvent {
12+
const event = new MouseEvent("contextmenu", { bubbles: true, cancelable: true });
13+
document.body.dispatchEvent(event);
14+
return event;
15+
}
16+
17+
test("suppresses the platform context menu by default", () => {
18+
const view = render(<NativeContextMenu />);
19+
expect(openContextMenu().defaultPrevented).toBe(true);
20+
21+
view.unmount();
22+
expect(openContextMenu().defaultPrevented).toBe(false);
23+
});
24+
25+
test("allows the platform context menu through the debug override", () => {
26+
vi.stubEnv("VITE_PYOPS_NATIVE_CONTEXT_MENU", "true");
27+
render(<NativeContextMenu />);
28+
expect(openContextMenu().defaultPrevented).toBe(false);
29+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useEffect } from "react";
2+
3+
/** Keep browser development and the Tauri WebView behavior aligned: PyOps owns
4+
* right-click interactions, so suppress the platform menu unless a developer
5+
* explicitly enables it for inspection/debugging. `preventDefault()` does not
6+
* stop propagation, so component `onContextMenu` handlers still run. */
7+
export function NativeContextMenu() {
8+
useEffect(() => {
9+
if (import.meta.env.VITE_PYOPS_NATIVE_CONTEXT_MENU === "true") return;
10+
const suppress = (event: MouseEvent) => event.preventDefault();
11+
document.addEventListener("contextmenu", suppress);
12+
return () => document.removeEventListener("contextmenu", suppress);
13+
}, []);
14+
15+
return null;
16+
}

app/src/routes/__root.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AppNav } from "../components/app-nav";
99
import { CommandPalette } from "../components/command-palette";
1010
import { DbMigrationsBanner } from "../components/db-migrations-banner";
1111
import { DriftModal } from "../components/drift-modal";
12+
import { NativeContextMenu } from "../components/native-context-menu";
1213
import { RouteError } from "../components/route-error";
1314
import { RoutePending } from "../components/route-pending";
1415
import { ShortcutHelpSheet } from "../components/shortcut-help-sheet";
@@ -85,6 +86,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
8586
</div>
8687
<CommandPalette />
8788
<ShortcutHelpSheet />
89+
<NativeContextMenu />
8890
<UndoHotkey />
8991
<Toaster />
9092
<DriftModal />

docs/development/configuration.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ Set local source-run values in `app/.env.local` or in the environment that launc
1515

1616
## Runtime and resource paths
1717

18-
| Variable | Development default | Purpose |
19-
| ------------------------ | ---------------------------------------- | ---------------------------------------------------------------- |
20-
| `DATABASE_URL` | Active project database | Bypass project selection and connect directly to one SQLite file |
21-
| `PYOPS_RESOURCE_DIR` | Current `app/` working directory | Root for read-only resources bundled with the server |
22-
| `PYOPS_MIGRATIONS_DIR` | `<resource-dir>/drizzle` | Override the Drizzle migration directory |
23-
| `PYOPS_MOD_DIR` | `../mod` from the resource directory | Override the Companion mod source installed by Settings |
24-
| `FACTORIO_SCRIPT_OUTPUT` | Platform Factorio `script-output` folder | Override where Assistant screenshot tools read Factorio captures |
18+
| Variable | Development default | Purpose |
19+
| -------------------------------- | ---------------------------------------- | ---------------------------------------------------------------- |
20+
| `DATABASE_URL` | Active project database | Bypass project selection and connect directly to one SQLite file |
21+
| `PYOPS_RESOURCE_DIR` | Current `app/` working directory | Root for read-only resources bundled with the server |
22+
| `PYOPS_MIGRATIONS_DIR` | `<resource-dir>/drizzle` | Override the Drizzle migration directory |
23+
| `PYOPS_MOD_DIR` | `../mod` from the resource directory | Override the Companion mod source installed by Settings |
24+
| `FACTORIO_SCRIPT_OUTPUT` | Platform Factorio `script-output` folder | Override where Assistant screenshot tools read Factorio captures |
25+
| `VITE_PYOPS_NATIVE_CONTEXT_MENU` | `false` | Set to `true` to restore the browser context menu for debugging |
2526

2627
`PYOPS_DATA_DIR` is the writable root. `PYOPS_RESOURCE_DIR` is the read-only bundle root.
2728
Keep that boundary intact: packaged resources may not be writable, while project databases,
@@ -31,6 +32,11 @@ The Tauri launcher sets the data, migration, and mod paths for the packaged app.
3132
code should import the resolved constants from `app/src/server/paths.server.ts` rather than
3233
joining new paths from `process.cwd()`.
3334

35+
PyOps suppresses the platform browser/WebView context menu by default so `vp dev` matches
36+
the desktop shell and only application-owned right-click menus appear. Set
37+
`VITE_PYOPS_NATIVE_CONTEXT_MENU=true` in `app/.env.local` and restart the development server
38+
when browser inspection requires the native menu.
39+
3440
::: warning Direct database mode bypasses projects
3541
When `DATABASE_URL` is set, the database layer uses that file instead of the project
3642
selected in `app-config.json`. Use `PYOPS_DATA_DIR` when you need an isolated but otherwise

0 commit comments

Comments
 (0)