diff --git a/site/AGENTS.md b/site/AGENTS.md index 8f81b92f66..3f41d17150 100644 --- a/site/AGENTS.md +++ b/site/AGENTS.md @@ -62,6 +62,10 @@ When investigating or editing TypeScript/React code, always use the TypeScript l - Destructure imports when possible (eg. import { foo } from 'bar') - Prefer `for...of` over `forEach` for iteration - **Biome** handles both linting and formatting (not ESLint/Prettier) +- Access browser globals like `location`, `navigator`, and `document` + directly. Do not prefix them with `window.` (e.g., write + `location.href`, not `window.location.href`). They are globally + available in every browser context. - Always use react-query for data fetching. Do not attempt to manage any data life cycle manually. Do not ever call an `API` function directly within a component. diff --git a/site/src/hooks/useClipboard.test.tsx b/site/src/hooks/useClipboard.test.tsx index 1c4692e3d2..dde538614c 100644 --- a/site/src/hooks/useClipboard.test.tsx +++ b/site/src/hooks/useClipboard.test.tsx @@ -129,7 +129,7 @@ type RenderResult = ReturnType["result"]; // execCommand is the workaround for copying text to the clipboard on HTTP-only // connections const originalExecCommand = global.document.execCommand; -const originalNavigator = window.navigator; +const originalNavigator = navigator; // Not a big fan of describe.each most of the time, but since we need to test // the exact same test cases against different inputs, and we want them to run diff --git a/site/src/hooks/useClipboard.ts b/site/src/hooks/useClipboard.ts index e1ee01242e..2ce4750cc7 100644 --- a/site/src/hooks/useClipboard.ts +++ b/site/src/hooks/useClipboard.ts @@ -61,7 +61,7 @@ export const useClipboard = ( }; try { - await window.navigator.clipboard.writeText(textToCopy); + await navigator.clipboard.writeText(textToCopy); markSuccess(); } catch (err) { const fallbackCopySuccessful = simulateClipboardWrite(textToCopy); diff --git a/site/src/index.tsx b/site/src/index.tsx index 220e5370f2..a8c7f9c52c 100644 --- a/site/src/index.tsx +++ b/site/src/index.tsx @@ -23,7 +23,7 @@ window.addEventListener("vite:preloadError", () => { const now = Date.now(); if (!last || now - Number(last) > 10_000) { sessionStorage.setItem(key, String(now)); - window.location.reload(); + location.reload(); } }); diff --git a/site/src/modules/apps/apps.ts b/site/src/modules/apps/apps.ts index df710aa4b9..d3f5b7c8e2 100644 --- a/site/src/modules/apps/apps.ts +++ b/site/src/modules/apps/apps.ts @@ -138,7 +138,7 @@ export const getAppHref = ( } if (host && app.subdomain && app.subdomain_name) { - const baseUrl = `${window.location.protocol}//${host.replace(/\*/g, app.subdomain_name)}`; + const baseUrl = `${location.protocol}//${host.replace(/\*/g, app.subdomain_name)}`; const url = new URL(baseUrl); url.pathname = "/"; return url.toString(); diff --git a/site/src/modules/dashboard/Navbar/MobileMenu.tsx b/site/src/modules/dashboard/Navbar/MobileMenu.tsx index 416b499f21..0fd1993892 100644 --- a/site/src/modules/dashboard/Navbar/MobileMenu.tsx +++ b/site/src/modules/dashboard/Navbar/MobileMenu.tsx @@ -335,7 +335,7 @@ const UserSettingsSub: FC = ({ export const includeOrigin = (target: string): string => { if (target.startsWith("/")) { - const baseUrl = window.location.origin; + const baseUrl = location.origin; return `${baseUrl}${target}`; } return target; diff --git a/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx b/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx index 39d1a2d08a..68096455d6 100644 --- a/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx +++ b/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx @@ -236,7 +236,7 @@ const TaskSidebarMenuItem: FC = ({ task }) => { diff --git a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx index a4476ec663..f5827e29f9 100644 --- a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx +++ b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx @@ -65,7 +65,7 @@ export const TemplateFiles: FC = ({ { - window.location.hash = path; + location.hash = path; document.getElementById(path)?.scrollIntoView({ behavior: "smooth", block: "start", diff --git a/site/src/modules/terminal/WorkspaceTerminalAlerts.tsx b/site/src/modules/terminal/WorkspaceTerminalAlerts.tsx index 9f0f89d394..945db7a8d3 100644 --- a/site/src/modules/terminal/WorkspaceTerminalAlerts.tsx +++ b/site/src/modules/terminal/WorkspaceTerminalAlerts.tsx @@ -195,7 +195,7 @@ const RefreshSessionButton: FC = () => { size="sm" onClick={() => { setIsRefreshing(true); - window.location.reload(); + location.reload(); }} > diff --git a/site/src/pages/AgentsPage/components/AgentChatInput.tsx b/site/src/pages/AgentsPage/components/AgentChatInput.tsx index 1a5a1f0fdc..3b20ad4691 100644 --- a/site/src/pages/AgentsPage/components/AgentChatInput.tsx +++ b/site/src/pages/AgentsPage/components/AgentChatInput.tsx @@ -359,7 +359,7 @@ export const AgentChatInput: FC = ({ // Listen for OAuth2 completion postMessage from popup. useEffect(() => { const handler = (event: MessageEvent) => { - if (event.origin !== window.location.origin) return; + if (event.origin !== location.origin) return; if ( event.data?.type === "mcp-oauth2-complete" && typeof event.data.serverID === "string" diff --git a/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx b/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx index 81e2d6fd44..e1a580e4ff 100644 --- a/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx +++ b/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx @@ -13,7 +13,7 @@ export const ChatAccessDeniedAlert: FC = () => { window.location.reload()}> + } diff --git a/site/src/pages/AgentsPage/components/MCPServerPicker.tsx b/site/src/pages/AgentsPage/components/MCPServerPicker.tsx index a9783cc547..ab72e847b3 100644 --- a/site/src/pages/AgentsPage/components/MCPServerPicker.tsx +++ b/site/src/pages/AgentsPage/components/MCPServerPicker.tsx @@ -204,7 +204,7 @@ export const MCPServerPicker: FC = ({ // Listen for OAuth2 completion postMessage from popup. useEffect(() => { const handler = (event: MessageEvent) => { - if (event.origin !== window.location.origin) return; + if (event.origin !== location.origin) return; if ( event.data?.type === "mcp-oauth2-complete" && typeof event.data.serverID === "string" diff --git a/site/src/pages/CliInstallPage/CliInstallPage.tsx b/site/src/pages/CliInstallPage/CliInstallPage.tsx index a62bd8ae9c..b48fb8b753 100644 --- a/site/src/pages/CliInstallPage/CliInstallPage.tsx +++ b/site/src/pages/CliInstallPage/CliInstallPage.tsx @@ -4,7 +4,7 @@ import { pageTitle } from "#/utils/page"; import { CliInstallPageView } from "./CliInstallPageView"; const CliInstallPage: FC = () => { - const origin = isChromatic() ? "https://example.com" : window.location.origin; + const origin = isChromatic() ? "https://example.com" : location.origin; return ( <> diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index 5f9e2f1eed..f66a09c2da 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -94,7 +94,7 @@ const ExternalAuthPage: FC = () => { variant="outline" onClick={() => { // Redirect to the auth flow again. *crosses fingers* - window.location.href = `/external-auth/${provider}/callback`; + location.href = `/external-auth/${provider}/callback`; }} > Retry @@ -102,7 +102,7 @@ const ExternalAuthPage: FC = () => { ); } - window.location.href = `/external-auth/${provider}/callback`; + location.href = `/external-auth/${provider}/callback`; return null; } diff --git a/site/src/pages/LoginOAuthDevicePage/LoginOAuthDevicePage.tsx b/site/src/pages/LoginOAuthDevicePage/LoginOAuthDevicePage.tsx index 0ee776d0c0..6452c0e5d2 100644 --- a/site/src/pages/LoginOAuthDevicePage/LoginOAuthDevicePage.tsx +++ b/site/src/pages/LoginOAuthDevicePage/LoginOAuthDevicePage.tsx @@ -62,10 +62,10 @@ const LoginOauthDevicePageWithState: FC<{ state: string }> = ({ state }) => { if (!exchangeExternalAuthDeviceQuery.isSuccess) { return; } - // We use window.location.href in lieu of a navigate hook + // We use location.href in lieu of a navigate hook // because we need to refresh the page after the GitHub // callback query sets a session cookie. - window.location.href = exchangeExternalAuthDeviceQuery.data.redirect_url; + location.href = exchangeExternalAuthDeviceQuery.data.redirect_url; }, [ exchangeExternalAuthDeviceQuery.isSuccess, exchangeExternalAuthDeviceQuery.data?.redirect_url, diff --git a/site/src/pages/LoginPage/LoginPage.test.tsx b/site/src/pages/LoginPage/LoginPage.test.tsx index 3d27e98440..88ab785260 100644 --- a/site/src/pages/LoginPage/LoginPage.test.tsx +++ b/site/src/pages/LoginPage/LoginPage.test.tsx @@ -13,8 +13,8 @@ import LoginPage from "./LoginPage"; describe("LoginPage", () => { // Capture original values before any test stubs take effect. - const origLocationOrigin = window.location.origin; - const origLocationHref = window.location.href; + const origLocationOrigin = location.origin; + const origLocationHref = location.href; const locationHrefSpy = vi.fn(); beforeEach(() => { diff --git a/site/src/pages/SetupPage/SetupPage.test.tsx b/site/src/pages/SetupPage/SetupPage.test.tsx index 374957d723..e291285413 100644 --- a/site/src/pages/SetupPage/SetupPage.test.tsx +++ b/site/src/pages/SetupPage/SetupPage.test.tsx @@ -124,7 +124,7 @@ describe("Setup Page", () => { it("calls sendBeacon with telemetry", async () => { const sendBeacon = vi.fn(); - Object.defineProperty(window.navigator, "sendBeacon", { + Object.defineProperty(navigator, "sendBeacon", { value: sendBeacon, }); renderWithRouter( diff --git a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPageExperimental.tsx b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPageExperimental.tsx index e93e5c5c79..957a6a7167 100644 --- a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPageExperimental.tsx +++ b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPageExperimental.tsx @@ -281,7 +281,7 @@ function getClipboardCopyContent( organization: string, buttonValues: ButtonValues | undefined, ): string { - const deploymentUrl = `${window.location.protocol}//${window.location.host}`; + const deploymentUrl = `${location.protocol}//${location.host}`; const createWorkspaceUrl = `${deploymentUrl}/templates/${organization}/${templateName}/workspace`; const createWorkspaceParams = new URLSearchParams(buttonValues); const buttonUrl = `${createWorkspaceUrl}?${createWorkspaceParams.toString()}`; diff --git a/site/src/pages/TerminalPage/TerminalPage.test.tsx b/site/src/pages/TerminalPage/TerminalPage.test.tsx index 2154291aff..2156cec244 100644 --- a/site/src/pages/TerminalPage/TerminalPage.test.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.test.tsx @@ -38,9 +38,8 @@ Object.defineProperty(window, "matchMedia", { }); const createWorkspaceTerminalWebSocket = () => { - const websocketProtocol = - window.location.protocol === "https:" ? "wss" : "ws"; - const websocketUrl = `${websocketProtocol}://${window.location.host}/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty?reconnect=${reconnectToken}&height=24&width=80`; + const websocketProtocol = location.protocol === "https:" ? "wss" : "ws"; + const websocketUrl = `${websocketProtocol}://${location.host}/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty?reconnect=${reconnectToken}&height=24&width=80`; return new WS(websocketUrl); }; diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx index 9eb32fc47a..aefbc49e87 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx @@ -42,7 +42,7 @@ const SecurityPage: FC = () => { toast.success("Updated password."); // Refresh the browser session. We need to improve the AuthProvider // to include better API to handle these scenarios - window.location.href = location.origin; + location.href = location.origin; }, }, }} diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx index 4226868ee9..5c5b4ad570 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx @@ -36,10 +36,10 @@ export const redirectToOIDCAuth = ( ) => { switch (toType) { case "github": - window.location.href = `/api/v2/users/oauth2/github/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`; + location.href = `/api/v2/users/oauth2/github/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`; break; case "oidc": - window.location.href = `/api/v2/users/oidc/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`; + location.href = `/api/v2/users/oidc/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`; break; default: throw new Error(`Unknown login type ${toType}`);