From be2a38818ec383a8d1857c8e9289be0a86de9e06 Mon Sep 17 00:00:00 2001
From: Diego Rodrigues de Sa e Souza
<8016841+diegosouzapw@users.noreply.github.com>
Date: Mon, 22 Jun 2026 17:10:59 -0300
Subject: [PATCH] fix(dashboard): surface manual config CTA when Open Claw CLI
auto-detect fails (#4562)
Integrated into release/v3.8.34 (rebuilt onto tip)
---
.../cli-code/components/OpenClawToolCard.tsx | 14 ++
...awToolCard-manual-config-fallback.test.tsx | 160 ++++++++++++++++++
2 files changed, 174 insertions(+)
create mode 100644 tests/unit/ui/OpenClawToolCard-manual-config-fallback.test.tsx
diff --git a/src/app/(dashboard)/dashboard/cli-code/components/OpenClawToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/OpenClawToolCard.tsx
index ff023a2f31..c817d72c4c 100644
--- a/src/app/(dashboard)/dashboard/cli-code/components/OpenClawToolCard.tsx
+++ b/src/app/(dashboard)/dashboard/cli-code/components/OpenClawToolCard.tsx
@@ -339,6 +339,20 @@ export default function OpenClawToolCard({
: t("installCliPrompt", { tool: "Open Claw" })}
+ {/*
+ Always surface Manual Config even when the CLI is not
+ detected locally — typical of remote OmniRoute
+ deployments where the CLI lives on the user's machine,
+ not on the server. Upstream report: #579.
+ */}
+
)}
diff --git a/tests/unit/ui/OpenClawToolCard-manual-config-fallback.test.tsx b/tests/unit/ui/OpenClawToolCard-manual-config-fallback.test.tsx
new file mode 100644
index 0000000000..4c5ea89ef3
--- /dev/null
+++ b/tests/unit/ui/OpenClawToolCard-manual-config-fallback.test.tsx
@@ -0,0 +1,160 @@
+// @vitest-environment jsdom
+//
+// Regression test: when the Open Claw CLI is not detected locally (typical of
+// remote OmniRoute deployments where the CLI lives on the user's laptop, not
+// on the server), the card must still surface a "Manual Config" button so the
+// user can copy the settings.json snippet and paste it into the CLI on their
+// local machine. Before this fix the Manual Config button only rendered when
+// `cliReady === true`, which made the card useless for remote deployments
+// (upstream report: decolua/9router#579, port of decolua/9router#615).
+import React from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+// ── Mocks ─────────────────────────────────────────────────────────────────────
+
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string, values?: Record) => {
+ const messages: Record = {
+ cliNotInstalled: "{tool} CLI not detected locally",
+ cliNotRunnable: "{tool} CLI installed but not runnable",
+ installCliPrompt:
+ "Manual configuration is still available if OmniRoute is deployed on a remote server.",
+ cliFoundFailedHealthcheck: "{tool} CLI was found but failed runtime healthcheck{reason}.",
+ manualConfig: "Manual Config",
+ checkingCli: "Checking {tool}...",
+ openClawManualConfiguration: "Open Claw Manual Configuration",
+ "toolDescriptions.openclaw": "Open Claw CLI",
+ };
+ const raw = messages[key] ?? key;
+ if (!values) return raw;
+ return Object.entries(values).reduce(
+ (acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v ?? "")),
+ raw
+ );
+ },
+ useLocale: () => "en",
+}));
+
+vi.mock("next/image", () => ({
+ default: () => ,
+}));
+
+vi.mock("@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge", () => ({
+ default: () => ,
+}));
+
+// Surface ManualConfigModal as a marker so we can assert it gets rendered with
+// isOpen=true after clicking the new Manual Config button.
+vi.mock("@/shared/components", async () => {
+ const React = await import("react");
+ return {
+ Card: ({ children }: { children: React.ReactNode }) =>