diff --git a/docs-site/docs/quickstart.md b/docs-site/docs/quickstart.md
index a5443cb1..8c4d8114 100644
--- a/docs-site/docs/quickstart.md
+++ b/docs-site/docs/quickstart.md
@@ -65,3 +65,36 @@ events.onmessage = (e) => console.log(JSON.parse(e.data));
Next: [Core concepts](/core-concepts) or the [interactive
playground](/api-reference/playground).
+
+## Running in Local Sandbox Mode
+
+The local mock sandbox lets you run the frontend and backend with seeded demo
+data, without a funded Testnet wallet or live Soroban RPC. From the repository
+root, install dependencies once and start the full stack:
+
+```bash
+npm install
+npm run dev:mock
+```
+
+This starts PostgreSQL, Redis, and the mock Soroban RPC, prepares and seeds the
+local database with `scripts/seed-mock-data.ts`, then starts the backend and
+frontend. The backend is launched with `SANDBOX_MODE_ENABLED=true` by the
+`dev:mock:start` script. The backend configuration uses this variable name
+(rather than `SANDBOX_MODE`).
+
+To run the seed script directly, first ensure the local database is running and
+its schema has been prepared, then run this command from the repository root:
+
+```bash
+npx tsx scripts/seed-mock-data.ts
+```
+
+The script uses `DATABASE_URL` if set; otherwise, it defaults to the local
+sandbox database at `******localhost:5433/flowfi`.
+
+Once the services are running, open the app at [http://localhost:3000](http://localhost:3000).
+Select **Sandbox** in the frontend network selector to use the local mock RPC
+for network-dependent features. The mock RPC does not support transaction
+simulation, so use the Testnet workflow above when testing real Stellar
+transactions.
diff --git a/frontend/src/components/ModeToggle.test.tsx b/frontend/src/components/ModeToggle.test.tsx
new file mode 100644
index 00000000..2ce2e60d
--- /dev/null
+++ b/frontend/src/components/ModeToggle.test.tsx
@@ -0,0 +1,60 @@
+import { afterEach, describe, expect, it } from "vitest";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { ThemeProvider } from "next-themes";
+import { ModeToggle } from "./ModeToggle";
+
+function renderModeToggle() {
+ return render(
+
+
+ ,
+ );
+}
+
+afterEach(() => {
+ localStorage.clear();
+ document.documentElement.className = "";
+});
+
+describe("ModeToggle", () => {
+ it("toggles themes and persists the selected preference", async () => {
+ const user = userEvent.setup();
+ renderModeToggle();
+
+ const toggle = await screen.findByRole("button", { name: "Switch to dark mode" });
+ expect(toggle.querySelector("svg.text-yellow-500")).toBeInTheDocument();
+ expect(toggle.querySelector("svg.text-blue-400")).toBeInTheDocument();
+
+ await user.click(toggle);
+
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Switch to light mode" })).toBeInTheDocument();
+ expect(localStorage.getItem("theme")).toBe("dark");
+ expect(document.documentElement).toHaveClass("dark");
+ });
+
+ await user.click(screen.getByRole("button", { name: "Switch to light mode" }));
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Switch to dark mode" })).toBeInTheDocument();
+ expect(localStorage.getItem("theme")).toBe("light");
+ expect(document.documentElement).not.toHaveClass("dark");
+ });
+ });
+
+ it("can be focused and activated from the keyboard", async () => {
+ const user = userEvent.setup();
+ renderModeToggle();
+
+ const toggle = await screen.findByRole("button", { name: "Switch to dark mode" });
+ await user.tab();
+ expect(toggle).toHaveFocus();
+
+ await user.keyboard("{Enter}");
+
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Switch to light mode" })).toBeInTheDocument();
+ expect(localStorage.getItem("theme")).toBe("dark");
+ });
+ });
+});
diff --git a/frontend/src/components/NetworkSelector.test.tsx b/frontend/src/components/NetworkSelector.test.tsx
new file mode 100644
index 00000000..6f4a01d8
--- /dev/null
+++ b/frontend/src/components/NetworkSelector.test.tsx
@@ -0,0 +1,78 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+const useNetworkMock = vi.fn();
+
+vi.mock("@/context/NetworkContext", () => ({
+ useNetwork: () => useNetworkMock(),
+}));
+
+import { NetworkSelector } from "./NetworkSelector";
+
+describe("NetworkSelector", () => {
+ const setNetworkId = vi.fn();
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ useNetworkMock.mockReturnValue({ networkId: "testnet", setNetworkId });
+ });
+
+ it("shows the active Testnet badge and available Stellar networks", () => {
+ const { container } = render();
+ const selector = screen.getByRole("combobox", {
+ name: "Active Stellar network",
+ });
+ const badge = container.querySelector("label span");
+
+ expect(selector).toHaveValue("testnet");
+ expect(badge).toHaveClass("bg-orange-400");
+ expect(screen.getByRole("option", { name: "Testnet" })).toBeInTheDocument();
+ expect(screen.getByRole("option", { name: "Mainnet" })).toBeInTheDocument();
+ expect(screen.getByRole("option", { name: "Futurenet" })).toBeInTheDocument();
+ });
+
+ it("uses the active network's badge color", () => {
+ useNetworkMock.mockReturnValue({ networkId: "mainnet", setNetworkId });
+ const { container } = render();
+
+ expect(container.querySelector("label span")).toHaveClass("bg-emerald-400");
+ });
+
+ it("sets the selected network", async () => {
+ const user = userEvent.setup();
+ render();
+
+ await user.selectOptions(
+ screen.getByRole("combobox", { name: "Active Stellar network" }),
+ "mainnet",
+ );
+
+ expect(setNetworkId).toHaveBeenCalledWith("mainnet");
+ });
+
+ it("keeps the native selector keyboard accessible", async () => {
+ const user = userEvent.setup();
+ render();
+ const selector = screen.getByRole("combobox", {
+ name: "Active Stellar network",
+ });
+ const handleKeyDown = vi.fn();
+ selector.addEventListener("keydown", handleKeyDown);
+
+ await user.tab();
+ expect(selector).toHaveFocus();
+ await user.keyboard("{ArrowDown}");
+ await user.keyboard("{Enter}");
+
+ expect(selector).toHaveFocus();
+ expect(handleKeyDown).toHaveBeenCalledWith(expect.objectContaining({ key: "ArrowDown" }));
+ expect(handleKeyDown).toHaveBeenCalledWith(expect.objectContaining({ key: "Enter" }));
+ expect(screen.getAllByRole("option").map((option) => option.textContent)).toEqual([
+ "Testnet",
+ "Futurenet",
+ "Mainnet",
+ "Sandbox",
+ ]);
+ });
+});
diff --git a/frontend/src/components/wallet/DisconnectConfirmModal.test.tsx b/frontend/src/components/wallet/DisconnectConfirmModal.test.tsx
new file mode 100644
index 00000000..15c7b07c
--- /dev/null
+++ b/frontend/src/components/wallet/DisconnectConfirmModal.test.tsx
@@ -0,0 +1,50 @@
+import { describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { DisconnectConfirmModal } from "./DisconnectConfirmModal";
+
+describe("DisconnectConfirmModal", () => {
+ it("confirms wallet disconnection when Disconnect is selected", async () => {
+ const user = userEvent.setup();
+ const onConfirm = vi.fn();
+ const onClose = vi.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText("GTESTWALLETADDRESS")).toBeInTheDocument();
+ await user.click(screen.getByRole("button", { name: "Disconnect" }));
+
+ expect(onConfirm).toHaveBeenCalledOnce();
+ expect(onClose).not.toHaveBeenCalled();
+ });
+
+ it("closes without disconnecting when Cancel is selected", async () => {
+ const user = userEvent.setup();
+ const onConfirm = vi.fn();
+ const onClose = vi.fn();
+
+ render();
+ await user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(onClose).toHaveBeenCalledOnce();
+ expect(onConfirm).not.toHaveBeenCalled();
+ });
+
+ it("closes without disconnecting when Escape is pressed", async () => {
+ const user = userEvent.setup();
+ const onConfirm = vi.fn();
+ const onClose = vi.fn();
+
+ render();
+ await user.keyboard("{Escape}");
+
+ expect(onClose).toHaveBeenCalledOnce();
+ expect(onConfirm).not.toHaveBeenCalled();
+ });
+});