diff --git a/frontend/app/asset/claude-color.svg b/frontend/app/asset/claude-color.svg new file mode 100644 index 0000000000..b70e167740 --- /dev/null +++ b/frontend/app/asset/claude-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app/view/term/osc-handlers.test.ts b/frontend/app/view/term/osc-handlers.test.ts new file mode 100644 index 0000000000..11e6ed387c --- /dev/null +++ b/frontend/app/view/term/osc-handlers.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; + +import { isClaudeCodeCommand } from "./osc-handlers"; + +describe("isClaudeCodeCommand", () => { + it("matches direct Claude Code invocations", () => { + expect(isClaudeCodeCommand("claude")).toBe(true); + expect(isClaudeCodeCommand("claude --dangerously-skip-permissions")).toBe(true); + expect(isClaudeCodeCommand("/usr/local/bin/claude chat")).toBe(true); + }); + + it("matches Claude Code invocations wrapped with env assignments", () => { + expect(isClaudeCodeCommand('ANTHROPIC_API_KEY="test" claude')).toBe(true); + expect(isClaudeCodeCommand("FOO=bar env claude --print")).toBe(true); + }); + + it("ignores other commands", () => { + expect(isClaudeCodeCommand("claudes")).toBe(false); + expect(isClaudeCodeCommand("echo claude")).toBe(false); + expect(isClaudeCodeCommand("")).toBe(false); + }); +}); diff --git a/frontend/app/view/term/osc-handlers.ts b/frontend/app/view/term/osc-handlers.ts index f44659d2c6..e4c067728d 100644 --- a/frontend/app/view/term/osc-handlers.ts +++ b/frontend/app/view/term/osc-handlers.ts @@ -25,6 +25,8 @@ const Osc52MaxRawLength = 128 * 1024; // includes selector + base64 + whitespace // See aiprompts/wave-osc-16162.md for full documentation export type ShellIntegrationStatus = "ready" | "running-command"; +const ClaudeCodeRegex = /(?:^|\/)claude\b/; + type Osc16162Command = | { command: "A"; data: Record } | { command: "C"; data: { cmd64?: string } } @@ -43,41 +45,56 @@ type Osc16162Command = | { command: "I"; data: { inputempty?: boolean } } | { command: "R"; data: Record }; +function normalizeCmd(decodedCmd: string): string { + let normalizedCmd = decodedCmd.trim(); + normalizedCmd = normalizedCmd.replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, ""); + normalizedCmd = normalizedCmd.replace(/^env\s+/, ""); + return normalizedCmd; +} + function checkCommandForTelemetry(decodedCmd: string) { if (!decodedCmd) { return; } - if (decodedCmd.startsWith("ssh ")) { + const normalizedCmd = normalizeCmd(decodedCmd); + + if (normalizedCmd.startsWith("ssh ")) { recordTEvent("conn:connect", { "conn:conntype": "ssh-manual" }); return; } const editorsRegex = /^(vim|vi|nano|nvim)\b/; - if (editorsRegex.test(decodedCmd)) { + if (editorsRegex.test(normalizedCmd)) { recordTEvent("action:term", { "action:type": "cli-edit" }); return; } const tailFollowRegex = /(^|\|\s*)tail\s+-[fF]\b/; - if (tailFollowRegex.test(decodedCmd)) { + if (tailFollowRegex.test(normalizedCmd)) { recordTEvent("action:term", { "action:type": "cli-tailf" }); return; } - const claudeRegex = /^claude\b/; - if (claudeRegex.test(decodedCmd)) { + if (ClaudeCodeRegex.test(normalizedCmd)) { recordTEvent("action:term", { "action:type": "claude" }); return; } const opencodeRegex = /^opencode\b/; - if (opencodeRegex.test(decodedCmd)) { + if (opencodeRegex.test(normalizedCmd)) { recordTEvent("action:term", { "action:type": "opencode" }); return; } } +export function isClaudeCodeCommand(decodedCmd: string): boolean { + if (!decodedCmd) { + return false; + } + return ClaudeCodeRegex.test(normalizeCmd(decodedCmd)); +} + function handleShellIntegrationCommandStart( termWrap: TermWrap, blockId: string, @@ -101,16 +118,20 @@ function handleShellIntegrationCommandStart( const decodedCmd = base64ToString(cmd.data.cmd64); rtInfo["shell:lastcmd"] = decodedCmd; globalStore.set(termWrap.lastCommandAtom, decodedCmd); + const isCC = isClaudeCodeCommand(decodedCmd); + globalStore.set(termWrap.claudeCodeActiveAtom, isCC); checkCommandForTelemetry(decodedCmd); } catch (e) { console.error("Error decoding cmd64:", e); rtInfo["shell:lastcmd"] = null; globalStore.set(termWrap.lastCommandAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); } } } else { rtInfo["shell:lastcmd"] = null; globalStore.set(termWrap.lastCommandAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); } rtInfo["shell:lastcmdexitcode"] = null; } @@ -287,6 +308,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo case "A": { rtInfo["shell:state"] = "ready"; globalStore.set(termWrap.shellIntegrationStatusAtom, "ready"); + globalStore.set(termWrap.claudeCodeActiveAtom, false); const marker = terminal.registerMarker(0); if (marker) { termWrap.promptMarkers.push(marker); @@ -324,6 +346,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo } break; case "D": + globalStore.set(termWrap.claudeCodeActiveAtom, false); if (cmd.data.exitcode != null) { rtInfo["shell:lastcmdexitcode"] = cmd.data.exitcode; } else { @@ -337,6 +360,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo break; case "R": globalStore.set(termWrap.shellIntegrationStatusAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); if (terminal.buffer.active.type === "alternate") { terminal.write("\x1b[?1049l"); } diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts index 9cb1c58720..8507394535 100644 --- a/frontend/app/view/term/term-model.ts +++ b/frontend/app/view/term/term-model.ts @@ -10,7 +10,7 @@ import { waveEventSubscribeSingle } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; -import { TerminalView } from "@/app/view/term/term"; +import { TermClaudeIcon, TerminalView } from "@/app/view/term/term"; import { TermWshClient } from "@/app/view/term/term-wsh"; import { VDomModel } from "@/app/view/vdom/vdom-model"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; @@ -155,7 +155,7 @@ export class TermViewModel implements ViewModel { if (isCmd) { const blockMeta = get(this.blockAtom)?.meta; let cmdText = blockMeta?.["cmd"]; - let cmdArgs = blockMeta?.["cmd:args"]; + const cmdArgs = blockMeta?.["cmd:args"]; if (cmdArgs != null && Array.isArray(cmdArgs) && cmdArgs.length > 0) { cmdText += " " + cmdArgs.join(" "); } @@ -242,7 +242,7 @@ export class TermViewModel implements ViewModel { }); this.termTransparencyAtom = useBlockAtom(blockId, "termtransparencyatom", () => { return jotai.atom((get) => { - let value = get(getOverrideConfigAtom(this.blockId, "term:transparency")) ?? 0.5; + const value = get(getOverrideConfigAtom(this.blockId, "term:transparency")) ?? 0.5; return boundNumber(value, 0, 1); }); }); @@ -397,10 +397,12 @@ export class TermViewModel implements ViewModel { return null; } const shellIntegrationStatus = get(this.termRef.current.shellIntegrationStatusAtom); + const claudeCodeActive = get(this.termRef.current.claudeCodeActiveAtom); + const icon = claudeCodeActive ? React.createElement(TermClaudeIcon) : "sparkles"; if (shellIntegrationStatus == null) { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-muted", title: "No shell integration — Wave AI unable to run commands.", noAction: true, @@ -409,14 +411,16 @@ export class TermViewModel implements ViewModel { if (shellIntegrationStatus === "ready") { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-accent", title: "Shell ready — Wave AI can run commands in this terminal.", noAction: true, }; } if (shellIntegrationStatus === "running-command") { - let title = "Shell busy — Wave AI unable to run commands while another command is running."; + let title = claudeCodeActive + ? "Claude Code Detected" + : "Shell busy — Wave AI unable to run commands while another command is running."; if (this.termRef.current) { const inAltBuffer = this.termRef.current.terminal?.buffer?.active?.type === "alternate"; @@ -429,7 +433,7 @@ export class TermViewModel implements ViewModel { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-warning", title: title, noAction: true, @@ -544,7 +548,7 @@ export class TermViewModel implements ViewModel { console.log("search is open, not giving focus"); return true; } - let termMode = globalStore.get(this.termMode); + const termMode = globalStore.get(this.termMode); if (termMode == "term") { if (this.termRef?.current?.terminal) { this.termRef.current.terminal.focus(); diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index b167688907..beabbe5174 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -1,6 +1,7 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import ClaudeColorSvg from "@/app/asset/claude-color.svg"; import { SubBlock } from "@/app/block/block"; import type { BlockNodeModel } from "@/app/block/blocktypes"; import { NullErrorBoundary } from "@/app/element/errorboundary"; @@ -33,6 +34,16 @@ interface TerminalViewProps { model: TermViewModel; } +const TermClaudeIcon = React.memo(() => { + return ( + + ); +}); + +TermClaudeIcon.displayName = "TermClaudeIcon"; + const TermResyncHandler = React.memo(({ blockId, model }: TerminalViewProps) => { const connStatus = jotai.useAtomValue(model.connStatus); const [lastConnStatus, setLastConnStatus] = React.useState(connStatus); @@ -60,7 +71,7 @@ const TermVDomToolbarNode = ({ vdomBlockId, blockId, model }: TerminalViewProps const unsub = waveEventSubscribeSingle({ eventType: "blockclose", scope: WOS.makeORef("block", vdomBlockId), - handler: (event) => { + handler: (_event) => { RpcApi.SetMetaCommand(TabRpcClient, { oref: WOS.makeORef("block", blockId), meta: { @@ -103,7 +114,7 @@ const TermVDomNodeSingleId = ({ vdomBlockId, blockId, model }: TerminalViewProps const unsub = waveEventSubscribeSingle({ eventType: "blockclose", scope: WOS.makeORef("block", vdomBlockId), - handler: (event) => { + handler: (_event) => { RpcApi.SetMetaCommand(TabRpcClient, { oref: WOS.makeORef("block", blockId), meta: { @@ -391,4 +402,4 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => ); }; -export { TerminalView }; +export { TermClaudeIcon, TerminalView }; diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 1cd167c800..1e9399ea4f 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -1,4 +1,4 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import type { BlockNodeModel } from "@/app/block/blocktypes"; @@ -32,6 +32,7 @@ import { handleOsc16162Command, handleOsc52Command, handleOsc7Command, + isClaudeCodeCommand, type ShellIntegrationStatus, } from "./osc-handlers"; import { bufferLinesToText, createTempFileFromBlob, extractAllClipboardData, normalizeCursorStyle } from "./termutil"; @@ -90,6 +91,7 @@ export class TermWrap { promptMarkers: TermTypes.IMarker[] = []; shellIntegrationStatusAtom: jotai.PrimitiveAtom; lastCommandAtom: jotai.PrimitiveAtom; + claudeCodeActiveAtom: jotai.PrimitiveAtom; nodeModel: BlockNodeModel; // this can be null hoveredLinkUri: string | null = null; onLinkHover?: (uri: string | null, mouseX: number, mouseY: number) => void; @@ -142,6 +144,7 @@ export class TermWrap { this.promptMarkers = []; this.shellIntegrationStatusAtom = jotai.atom(null) as jotai.PrimitiveAtom; this.lastCommandAtom = jotai.atom(null) as jotai.PrimitiveAtom; + this.claudeCodeActiveAtom = jotai.atom(false); this.terminal = new Terminal(options); this.fitAddon = new FitAddon(); this.fitAddon.scrollbarWidth = 6; // this needs to match scrollbar width in term.scss @@ -393,16 +396,19 @@ export class TermWrap { const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, { oref: WOS.makeORef("block", this.blockId), }); + let shellState: ShellIntegrationStatus = null; if (rtInfo && rtInfo["shell:integration"]) { - const shellState = rtInfo["shell:state"] as ShellIntegrationStatus; + shellState = rtInfo["shell:state"] as ShellIntegrationStatus; globalStore.set(this.shellIntegrationStatusAtom, shellState || null); } else { globalStore.set(this.shellIntegrationStatusAtom, null); } const lastCmd = rtInfo ? rtInfo["shell:lastcmd"] : null; + const isCC = shellState === "running-command" && isClaudeCodeCommand(lastCmd); globalStore.set(this.lastCommandAtom, lastCmd || null); + globalStore.set(this.claudeCodeActiveAtom, isCC); } catch (e) { console.log("Error loading runtime info:", e); } @@ -419,14 +425,18 @@ export class TermWrap { this.promptMarkers.forEach((marker) => { try { marker.dispose(); - } catch (_) {} + } catch (_) { + /* nothing */ + } }); this.promptMarkers = []; this.terminal.dispose(); this.toDispose.forEach((d) => { try { d.dispose(); - } catch (_) {} + } catch (_) { + /* nothing */ + } }); this.mainFileSubject.release(); } @@ -481,7 +491,7 @@ export class TermWrap { } } let resolve: () => void = null; - let prtn = new Promise((presolve, _) => { + const prtn = new Promise((presolve, _) => { resolve = presolve; }); this.terminal.write(data, () => { diff --git a/package-lock.json b/package-lock.json index 99c2a025b4..3dbb6471ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.14.2-beta.1", + "version": "0.14.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.14.2-beta.1", + "version": "0.14.2", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ @@ -5434,32 +5434,32 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react": { - "version": "0.27.16", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", - "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "version": "0.27.19", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz", + "integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==", "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.1.6", - "@floating-ui/utils": "^0.2.10", + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", "tabbable": "^6.0.0" }, "peerDependencies": { @@ -5468,12 +5468,12 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -5481,9 +5481,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@hapi/hoek": { @@ -15194,9 +15194,9 @@ "license": "MIT" }, "node_modules/emoji-regex": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", - "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/emojilib": { @@ -15365,9 +15365,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", - "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", "license": "MIT", "workspaces": [ "docs", @@ -18960,9 +18960,9 @@ } }, "node_modules/katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "version": "0.16.38", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.38.tgz", + "integrity": "sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -29930,13 +29930,13 @@ } }, "node_modules/swr": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", - "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz", + "integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==", "license": "MIT", "dependencies": { "dequal": "^2.0.3", - "use-sync-external-store": "^1.4.0" + "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -29959,9 +29959,9 @@ } }, "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, "node_modules/tailwind-merge": { @@ -31879,9 +31879,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"