From b212fdc8e3daeb8e9dfe73f013d068dc12214a8f Mon Sep 17 00:00:00 2001 From: Michael Zazon Date: Tue, 24 Mar 2026 21:28:12 -0400 Subject: [PATCH 1/3] fix: replace crypto.randomUUID with fallback for non-secure contexts crypto.randomUUID() is only available in secure contexts (HTTPS or localhost). When the Docker container is accessed over plain HTTP from another machine on the network, the browser does not expose crypto.randomUUID, causing a TypeError that breaks session clicks and other tab/pane interactions. Add a generateUUID() utility that uses crypto.randomUUID() when available and falls back to crypto.getRandomValues() (which works in all contexts). Replace all renderer call sites. Fixes #132 --- .../utils/trigger.ts | 3 ++- .../settings/sections/WorkspaceSection.tsx | 3 ++- src/renderer/store/slices/paneSlice.ts | 5 ++-- src/renderer/store/slices/tabSlice.ts | 5 ++-- src/renderer/types/tabs.ts | 7 +++--- src/renderer/utils/uuid.ts | 25 +++++++++++++++++++ 6 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 src/renderer/utils/uuid.ts diff --git a/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts index e0d42b77..4fe36c09 100644 --- a/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts +++ b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts @@ -3,12 +3,13 @@ */ import type { NotificationTrigger, TriggerContentType, TriggerMode } from '@renderer/types/data'; +import { generateUUID } from '@renderer/utils/uuid'; /** * Generates a UUID v4 for new triggers. */ export function generateId(): string { - return crypto.randomUUID(); + return generateUUID(); } /** diff --git a/src/renderer/components/settings/sections/WorkspaceSection.tsx b/src/renderer/components/settings/sections/WorkspaceSection.tsx index aab157af..b95c48a1 100644 --- a/src/renderer/components/settings/sections/WorkspaceSection.tsx +++ b/src/renderer/components/settings/sections/WorkspaceSection.tsx @@ -13,6 +13,7 @@ import { useCallback, useEffect, useState } from 'react'; import { api } from '@renderer/api'; +import { generateUUID } from '@renderer/utils/uuid'; import { confirm } from '@renderer/components/common/ConfirmDialog'; import { useStore } from '@renderer/store'; import { Edit2, Loader2, Plus, Save, Server, Trash2, X } from 'lucide-react'; @@ -102,7 +103,7 @@ export const WorkspaceSection = (): React.JSX.Element => { const handleAdd = async (): Promise => { const newProfile: SshConnectionProfile = { - id: crypto.randomUUID(), + id: generateUUID(), name: formName.trim(), host: formHost.trim(), port: parseInt(formPort, 10) || 22, diff --git a/src/renderer/store/slices/paneSlice.ts b/src/renderer/store/slices/paneSlice.ts index 85d6628e..73f1368b 100644 --- a/src/renderer/store/slices/paneSlice.ts +++ b/src/renderer/store/slices/paneSlice.ts @@ -4,6 +4,7 @@ */ import { MAX_PANES } from '@renderer/types/panes'; +import { generateUUID } from '@renderer/utils/uuid'; import { createEmptyPane, @@ -140,7 +141,7 @@ export const createPaneSlice: StateCreator = (set, }; // Create new pane with the tab - const newPaneId = crypto.randomUUID(); + const newPaneId = generateUUID(); const newPane = { ...createEmptyPane(newPaneId), tabs: [tab], @@ -277,7 +278,7 @@ export const createPaneSlice: StateCreator = (set, newSourceActiveTabId = newSourceTabs[oldIndex]?.id ?? newSourceTabs[oldIndex - 1]?.id ?? null; } - const newPaneId = crypto.randomUUID(); + const newPaneId = generateUUID(); const newPane = { ...createEmptyPane(newPaneId), tabs: [tab], diff --git a/src/renderer/store/slices/tabSlice.ts b/src/renderer/store/slices/tabSlice.ts index 7cb9f4b9..23bdafd7 100644 --- a/src/renderer/store/slices/tabSlice.ts +++ b/src/renderer/store/slices/tabSlice.ts @@ -12,6 +12,7 @@ import { findTabBySessionAndProject, truncateLabel, } from '@renderer/types/tabs'; +import { generateUUID } from '@renderer/utils/uuid'; import { findPane, @@ -172,7 +173,7 @@ export const createTabSlice: StateCreator = (set, ge // Create new tab with generated id and timestamp const newTab: Tab = { ...tab, - id: crypto.randomUUID(), + id: generateUUID(), label: truncateLabel(tab.label), createdAt: Date.now(), }; @@ -365,7 +366,7 @@ export const createTabSlice: StateCreator = (set, ge if (!focusedPane) return; const newTab: Tab = { - id: crypto.randomUUID(), + id: generateUUID(), type: 'dashboard', label: 'Dashboard', createdAt: Date.now(), diff --git a/src/renderer/types/tabs.ts b/src/renderer/types/tabs.ts index 4e60603e..8057637f 100644 --- a/src/renderer/types/tabs.ts +++ b/src/renderer/types/tabs.ts @@ -5,6 +5,7 @@ import type { Session } from './data'; import type { TriggerColor } from '@shared/constants/triggerColors'; +import { generateUUID } from '@renderer/utils/uuid'; // ============================================================================= // Navigation Request Types @@ -52,7 +53,7 @@ export interface SearchNavigationPayload { * The nonce ensures repeated clicks produce new navigations. */ export interface TabNavigationRequest { - /** Unique nonce per click/action (crypto.randomUUID) */ + /** Unique nonce per click/action (generateUUID) */ id: string; /** Kind of navigation */ kind: 'error' | 'search' | 'autoBottom'; @@ -194,7 +195,7 @@ export function createErrorNavigationRequest( highlightColor?: TriggerColor ): TabNavigationRequest { return { - id: crypto.randomUUID(), + id: generateUUID(), kind: 'error', source, highlight: highlightColor ?? 'red', @@ -209,7 +210,7 @@ export function createSearchNavigationRequest( payload: SearchNavigationPayload ): TabNavigationRequest { return { - id: crypto.randomUUID(), + id: generateUUID(), kind: 'search', source: 'commandPalette', highlight: 'yellow', diff --git a/src/renderer/utils/uuid.ts b/src/renderer/utils/uuid.ts new file mode 100644 index 00000000..43cf0105 --- /dev/null +++ b/src/renderer/utils/uuid.ts @@ -0,0 +1,25 @@ +/** + * Generate a UUID v4 string. + * + * `crypto.randomUUID()` is only available in **secure contexts** (HTTPS or + * localhost). When the app is served over plain HTTP on a LAN IP (e.g. + * Docker accessed from another machine), the browser will not expose + * `randomUUID`. This helper falls back to `crypto.getRandomValues()` which + * is available in all modern browsers regardless of secure context. + * + * @see https://github.com/matt1398/claude-devtools/issues/132 + */ +export function generateUUID(): string { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + + // Fallback: construct a v4 UUID from getRandomValues + const bytes = crypto.getRandomValues(new Uint8Array(16)); + // Set version (4) and variant (RFC 4122) + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} From cf75dc60b457ff660d2cc02bfaf1b1cc3be7dab8 Mon Sep 17 00:00:00 2001 From: Michael Zazon Date: Tue, 24 Mar 2026 21:34:37 -0400 Subject: [PATCH 2/3] fix: correct import order in tabs.ts per simple-import-sort Move relative import after alias imports to satisfy the project's import ordering rules. --- src/renderer/types/tabs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/types/tabs.ts b/src/renderer/types/tabs.ts index 8057637f..3227e536 100644 --- a/src/renderer/types/tabs.ts +++ b/src/renderer/types/tabs.ts @@ -3,9 +3,9 @@ * Based on specs/001-tabbed-layout-dashboard/contracts/tab-state.ts */ -import type { Session } from './data'; import type { TriggerColor } from '@shared/constants/triggerColors'; import { generateUUID } from '@renderer/utils/uuid'; +import type { Session } from './data'; // ============================================================================= // Navigation Request Types From 2c0f35163981e900eaf5c5f75f69501a62ea6368 Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 5 Apr 2026 15:34:03 +0900 Subject: [PATCH 3/3] fix: sort imports to pass CI lint check Co-Authored-By: Claude Opus 4.6 (1M context) --- .../settings/NotificationTriggerSettings/utils/trigger.ts | 3 ++- src/renderer/components/settings/sections/WorkspaceSection.tsx | 2 +- src/renderer/types/tabs.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts index 4fe36c09..8db17b0d 100644 --- a/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts +++ b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts @@ -2,9 +2,10 @@ * Utility functions for notification triggers. */ -import type { NotificationTrigger, TriggerContentType, TriggerMode } from '@renderer/types/data'; import { generateUUID } from '@renderer/utils/uuid'; +import type { NotificationTrigger, TriggerContentType, TriggerMode } from '@renderer/types/data'; + /** * Generates a UUID v4 for new triggers. */ diff --git a/src/renderer/components/settings/sections/WorkspaceSection.tsx b/src/renderer/components/settings/sections/WorkspaceSection.tsx index b95c48a1..6f35d65b 100644 --- a/src/renderer/components/settings/sections/WorkspaceSection.tsx +++ b/src/renderer/components/settings/sections/WorkspaceSection.tsx @@ -13,9 +13,9 @@ import { useCallback, useEffect, useState } from 'react'; import { api } from '@renderer/api'; -import { generateUUID } from '@renderer/utils/uuid'; import { confirm } from '@renderer/components/common/ConfirmDialog'; import { useStore } from '@renderer/store'; +import { generateUUID } from '@renderer/utils/uuid'; import { Edit2, Loader2, Plus, Save, Server, Trash2, X } from 'lucide-react'; import { SettingsSectionHeader } from '../components/SettingsSectionHeader'; diff --git a/src/renderer/types/tabs.ts b/src/renderer/types/tabs.ts index 3227e536..5e08dc71 100644 --- a/src/renderer/types/tabs.ts +++ b/src/renderer/types/tabs.ts @@ -3,9 +3,10 @@ * Based on specs/001-tabbed-layout-dashboard/contracts/tab-state.ts */ -import type { TriggerColor } from '@shared/constants/triggerColors'; import { generateUUID } from '@renderer/utils/uuid'; + import type { Session } from './data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; // ============================================================================= // Navigation Request Types