+
')
+ expect(markup).not.toContain('disabled="" type="button">Send feedback')
+ })
+
+ it("recovery + preview 200: send disabled when journal empty and note empty", () => {
+ useQueryMock.mockReturnValue({
+ data: createPreviewResponse(0),
+ error: null,
+ isLoading: false,
+ isError: false,
+ isSuccess: true,
+ })
+
+ const markup = renderDialog({
+ source: "recovery",
+ failureKind: "boot_failed",
+ })
+
+ expect(markup).toContain("Activity history")
+ expect(markup).not.toContain("Sign in to send feedback")
+ expect(markup).toContain('type="button">Send feedback')
+ expect(markup).not.toContain('disabled="" type="button">Send feedback')
+ })
+
+ it("recovery + preview 200: send enabled when journal has events", () => {
+ useQueryMock.mockReturnValue({
+ data: createPreviewResponse(2),
+ error: null,
+ isLoading: false,
+ isError: false,
+ isSuccess: true,
+ })
+
+ const markup = renderDialog({
+ source: "recovery",
+ failureKind: "boot_failed",
+ })
+
+ expect(markup).toContain('type="button">Send feedback')
+ expect(markup).not.toContain('disabled="" type="button">Send feedback')
+ })
+})
diff --git a/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/TailscaleRemoteAccessDialog.test.tsx b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/TailscaleRemoteAccessDialog.test.tsx
new file mode 100644
index 0000000000..6cdca7857c
--- /dev/null
+++ b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/TailscaleRemoteAccessDialog.test.tsx
@@ -0,0 +1,106 @@
+import { describe, expect, it, vi } from "vitest"
+import { renderToStaticMarkup } from "react-dom/server"
+
+import { MagicDnsRemoteUrlField } from "@/components/Common/MagicDnsRemoteUrlField"
+import { TailscaleRemoteAccessDialog } from "@/components/Common/TailscaleRemoteAccessDialog"
+import { OCTOBOT_TAILSCALE_CONNECT_GUIDE_URL } from "@/lib/external-links"
+import {
+ MAGIC_DNS_INVALID_SHAPE,
+ MAGIC_DNS_OPEN_IN_BROWSER_LABEL,
+ TAILSCALE_ADMIN_CONSOLE_MACHINES_URL,
+ TAILSCALE_MAGICDNS_REACHABILITY_LEAD,
+ TAILSCALE_SERVE_NOT_ENABLED_ON_TAILNET_MESSAGE,
+ TAILSCALE_SERVE_STARTED_RUNNING_MESSAGE,
+} from "@/lib/ui-recovery-constants"
+
+vi.mock("@/components/ui/dialog", () => ({
+ Dialog: ({ children }: { children: React.ReactNode }) =>
{children}
,
+ DialogContent: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogHeader: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogFooter: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogTitle: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogDescription: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogClose: ({ children }: { children: React.ReactNode }) => <>{children}>,
+}))
+
+const pageHref = "http://192.168.0.10:5001/app/setup?x=1#tab"
+
+describe("TailscaleRemoteAccessDialog", () => {
+ it("shows tailscale serve command with port from location", () => {
+ vi.stubGlobal("window", {
+ location: { href: pageHref, port: "5001" },
+ })
+ const markup = renderToStaticMarkup(
+
{}} />,
+ )
+ expect(markup).toContain("tailscale serve --bg http://127.0.0.1:5001")
+ expect(markup).toContain(TAILSCALE_SERVE_NOT_ENABLED_ON_TAILNET_MESSAGE)
+ expect(markup).toContain(TAILSCALE_SERVE_STARTED_RUNNING_MESSAGE)
+ expect(markup).toContain("printed Tailscale link")
+ expect(markup).toContain("tailscale up")
+ expect(markup).not.toContain('value="tailscale status"')
+ expect(markup).toContain("Full domain")
+ expect(markup).toContain(TAILSCALE_ADMIN_CONSOLE_MACHINES_URL)
+ expect(markup).not.toContain("ERR_SSL_PROTOCOL_ERROR")
+ expect(markup).toContain("this computer")
+ expect(markup).toContain("run the command below")
+ expect(markup).toContain(TAILSCALE_MAGICDNS_REACHABILITY_LEAD)
+ expect(markup).toContain('readOnly=""')
+ expect(markup).not.toContain('aria-label="Copy command"')
+ expect(markup).not.toContain('aria-label="Copy URL"')
+ expect(markup).toContain(OCTOBOT_TAILSCALE_CONNECT_GUIDE_URL)
+ vi.unstubAllGlobals()
+ })
+})
+
+describe("MagicDnsRemoteUrlField", () => {
+ it("shows selectable remote URL and open link for valid hostname", () => {
+ const markup = renderToStaticMarkup(
+ {}}
+ />,
+ )
+ expect(markup).toContain("https://my-node.tailnet.ts.net/app/setup?x=1#tab")
+ expect(markup).toContain('readOnly=""')
+ expect(markup).toContain(MAGIC_DNS_OPEN_IN_BROWSER_LABEL)
+ expect(markup).toContain("ERR_SSL_PROTOCOL_ERROR")
+ expect(markup).toContain("step 2 command")
+ expect(markup).not.toContain('aria-label="Copy URL"')
+ })
+
+ it("shows error for invalid hostname", () => {
+ const markup = renderToStaticMarkup(
+ {}}
+ />,
+ )
+ expect(markup).toContain(MAGIC_DNS_INVALID_SHAPE)
+ expect(markup).not.toContain("https://not-valid")
+ })
+
+ it("normalizes pasted https URL into remote link", () => {
+ const markup = renderToStaticMarkup(
+ {}}
+ />,
+ )
+ expect(markup).toContain("https://my-node.tailnet.ts.net/app/setup?x=1#tab")
+ expect(markup).toContain(MAGIC_DNS_OPEN_IN_BROWSER_LABEL)
+ })
+})
diff --git a/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/auth-login-redirect-suppression-provider.test.tsx b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/auth-login-redirect-suppression-provider.test.tsx
new file mode 100644
index 0000000000..2bfa3ec79f
--- /dev/null
+++ b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/auth-login-redirect-suppression-provider.test.tsx
@@ -0,0 +1,28 @@
+import { renderToStaticMarkup } from "react-dom/server"
+import { beforeEach, describe, expect, it } from "vitest"
+
+import { AuthLoginRedirectSuppressionProvider } from "@/components/Common/AuthLoginRedirectSuppressionProvider"
+import {
+ acquireAuthLoginRedirectSuppression,
+ isAuthLoginRedirectSuppressed,
+ releaseAuthLoginRedirectSuppression,
+ resetAuthLoginRedirectSuppressionForTests,
+} from "@/lib/auth-login-redirect-suppression"
+
+describe("AuthLoginRedirectSuppressionProvider", () => {
+ beforeEach(() => {
+ resetAuthLoginRedirectSuppressionForTests()
+ })
+
+ it("wraps children (useEffect acquire/release covered by module ref-count tests)", () => {
+ const markup = renderToStaticMarkup(
+
+ ok
+ ,
+ )
+ expect(markup).toContain("ok")
+ acquireAuthLoginRedirectSuppression()
+ expect(isAuthLoginRedirectSuppressed()).toBe(true)
+ releaseAuthLoginRedirectSuppression()
+ })
+})
diff --git a/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/recovery-screen.test.tsx b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/recovery-screen.test.tsx
new file mode 100644
index 0000000000..ea710178fa
--- /dev/null
+++ b/packages/tentacles/Services/Interfaces/node_web_interface/src/components/Common/__tests__/recovery-screen.test.tsx
@@ -0,0 +1,104 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
+import { renderToStaticMarkup } from "react-dom/server"
+
+import {
+ confirmAndResetLocalBrowserData,
+ RecoveryScreen,
+} from "@/components/Common/RecoveryScreen"
+
+const mocks = vi.hoisted(() => ({
+ resetClientStorage: vi.fn().mockResolvedValue(undefined),
+}))
+
+vi.mock("@/lib/client-storage-reset", () => ({
+ resetClientStorage: mocks.resetClientStorage,
+}))
+
+vi.mock("@/components/Common/ShareFeedbackButton", () => ({
+ ShareFeedbackButton: () => ,
+}))
+
+vi.mock("@/components/Common/TailscaleRemoteAccessDialog", () => ({
+ TailscaleRemoteAccessDialog: () => null,
+}))
+
+describe("RecoveryScreen", () => {
+ beforeEach(() => {
+ mocks.resetClientStorage.mockClear()
+ })
+
+ it("does not reset on mount", () => {
+ renderToStaticMarkup()
+ expect(mocks.resetClientStorage).not.toHaveBeenCalled()
+ })
+
+ it("does not reset when confirmation is declined", () => {
+ confirmAndResetLocalBrowserData(() => false)
+ expect(mocks.resetClientStorage).not.toHaveBeenCalled()
+ })
+
+ it("resets once when confirmation is accepted", () => {
+ confirmAndResetLocalBrowserData(() => true)
+ expect(mocks.resetClientStorage).toHaveBeenCalledOnce()
+ expect(mocks.resetClientStorage).toHaveBeenCalledWith("manual_recovery")
+ })
+
+ it("does not render tier selection controls", () => {
+ const markup = renderToStaticMarkup(
+ ,
+ )
+ expect(markup).not.toMatch(/