From 61a4dd6e91b20db61d2ebe40f64553fb65bd07fc Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:15:04 +0200 Subject: [PATCH] refactor: extract frontend uuid helper (#9521) --- AGENTS.md | 1 + frontend/src/lib/components/ArgInput.svelte | 2 +- .../components/apps/components/display/AppChat.svelte | 2 +- .../copilot/chat/global/rawAppBundlerBridge.ts | 3 ++- .../flows/conversations/FlowChatManager.svelte.ts | 10 +--------- frontend/src/lib/components/flows/utils.svelte.ts | 2 +- frontend/src/lib/utils/uuid.ts | 7 +++++++ 7 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 frontend/src/lib/utils/uuid.ts diff --git a/AGENTS.md b/AGENTS.md index 346e996e19..a294d47667 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ Open-source platform for internal tools, workflows, API integrations, background - **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow - **Backend patterns**: use the `rust-backend` skill when writing Rust code - **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill. +- **Frontend UUIDs**: do not call `crypto.randomUUID()` in frontend code. Import `randomUUID` from `$lib/utils/uuid` instead. - **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi. - **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc` - **Brand/UI guidelines**: `frontend/brand-guidelines.md` diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index ed9d11eaa7..368cfa6d8a 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -46,7 +46,7 @@ import AIProviderPicker from './AIProviderPicker.svelte' import TextInput from './text_input/TextInput.svelte' import FileInput from './common/fileInput/FileInput.svelte' - import { randomUUID } from './flows/conversations/FlowChatManager.svelte' + import { randomUUID } from '$lib/utils/uuid' interface Props { label?: string diff --git a/frontend/src/lib/components/apps/components/display/AppChat.svelte b/frontend/src/lib/components/apps/components/display/AppChat.svelte index 15bdc7bc57..d1b5e09f9c 100644 --- a/frontend/src/lib/components/apps/components/display/AppChat.svelte +++ b/frontend/src/lib/components/apps/components/display/AppChat.svelte @@ -15,7 +15,7 @@ import ChatMessage from '$lib/components/chat/ChatMessage.svelte' import ChatInput from '$lib/components/chat/ChatInput.svelte' import { parseStreamDeltas } from '$lib/components/chat/utils' - import { randomUUID } from '$lib/components/flows/conversations/FlowChatManager.svelte' + import { randomUUID } from '$lib/utils/uuid' interface Message { id: string diff --git a/frontend/src/lib/components/copilot/chat/global/rawAppBundlerBridge.ts b/frontend/src/lib/components/copilot/chat/global/rawAppBundlerBridge.ts index 27f9f5aaee..2e2a9c68b1 100644 --- a/frontend/src/lib/components/copilot/chat/global/rawAppBundlerBridge.ts +++ b/frontend/src/lib/components/copilot/chat/global/rawAppBundlerBridge.ts @@ -1,4 +1,5 @@ import { WorkspaceService } from '$lib/gen' +import { randomUUID } from '$lib/utils/uuid' export type RawAppBundle = { js: string @@ -20,7 +21,7 @@ type BundleRawAppDraftParams = BundleRawAppFilesParams & { const DEFAULT_TIMEOUT_MS = 120_000 function makeRequestId(): string { - return globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2) + return randomUUID() } async function loadSharedUiFiles(workspace: string): Promise> { diff --git a/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts b/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts index 5cfe7ab3e3..2a57ad937e 100644 --- a/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts +++ b/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts @@ -7,6 +7,7 @@ import InfiniteList from '$lib/components/InfiniteList.svelte' import { workspaceStore, userStore } from '$lib/stores' import { get } from 'svelte/store' import { parseStreamDeltas } from '$lib/components/chat/utils' +import { randomUUID } from '$lib/utils/uuid' export interface ChatMessage extends FlowConversationMessage { loading?: boolean @@ -17,15 +18,6 @@ export interface ConversationWithDraft extends FlowConversation { isDraft?: boolean } -export function randomUUID() { - // Pure JS (RFC4122 v4) UUID implementation (no external dependencies) - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = (Math.random() * 16) | 0 - const v = c === 'x' ? r : (r & 0x3) | 0x8 - return v.toString(16) - }) -} - export class FlowChatManager { // State messages = $state([]) diff --git a/frontend/src/lib/components/flows/utils.svelte.ts b/frontend/src/lib/components/flows/utils.svelte.ts index ade76dc9ee..f260813a1b 100644 --- a/frontend/src/lib/components/flows/utils.svelte.ts +++ b/frontend/src/lib/components/flows/utils.svelte.ts @@ -104,7 +104,7 @@ export function filteredContentForExport(flow: ExtendedOpenFlow) { } import { dfs as dfsApply } from './dfs' -import { randomUUID } from './conversations/FlowChatManager.svelte' +import { randomUUID } from '$lib/utils/uuid' export function cleanFlow(flow: OpenFlow | any): OpenFlow & { tag?: string diff --git a/frontend/src/lib/utils/uuid.ts b/frontend/src/lib/utils/uuid.ts new file mode 100644 index 0000000000..4a9f092dac --- /dev/null +++ b/frontend/src/lib/utils/uuid.ts @@ -0,0 +1,7 @@ +export function randomUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +}