diff --git a/src/lib/tours/dismissals.ts b/src/lib/tours/dismissals.ts
new file mode 100644
index 0000000..78f17b2
--- /dev/null
+++ b/src/lib/tours/dismissals.ts
@@ -0,0 +1,89 @@
+/**
+ * Per-browser tour dismissals in localStorage, keyed by tour id. Mirrors the
+ * announcement dismissal store (browser storage is read during mount and
+ * written during mount or the explicit dismissal handler; it is never
+ * accessed during render). Dismissals are permanent per browser: a tour that
+ * was dismissed never auto-starts again, but the header replay control can
+ * always run it on demand.
+ */
+
+const DISMISSALS_STORAGE_KEY = "ptf:tour:dismissals";
+const OPTED_OUT_STORAGE_KEY = "ptf:tours:opted-out";
+
+export type TourDismissals = Record;
+
+function isRecord(value: unknown): value is Record {
+ return typeof value === "object" && value !== null;
+}
+
+export function readTourDismissals(now: number): TourDismissals {
+ if (typeof window === "undefined") return {};
+
+ try {
+ const raw = window.localStorage.getItem(DISMISSALS_STORAGE_KEY);
+ if (!raw) return {};
+ const parsed: unknown = JSON.parse(raw);
+ if (!isRecord(parsed)) return {};
+
+ const dismissals: TourDismissals = {};
+ for (const [key, value] of Object.entries(parsed)) {
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
+ dismissals[key] = value;
+ }
+ }
+ return dismissals;
+ } catch (error) {
+ if (error instanceof Error) return {};
+ throw error;
+ }
+}
+
+export function writeTourDismissals(dismissals: TourDismissals, now: number): void {
+ if (typeof window === "undefined") return;
+
+ try {
+ window.localStorage.setItem(DISMISSALS_STORAGE_KEY, JSON.stringify(dismissals));
+ } catch (error) {
+ if (error instanceof Error) return;
+ throw error;
+ }
+}
+
+/** Merges a single dismissal (timestamp = dismissal time) into the stored map. */
+export function storeTourDismissal(tourId: string, now: number): void {
+ const existing = readTourDismissals(now);
+ writeTourDismissals({ ...existing, [tourId]: now }, now);
+}
+
+/** True when the tour has been dismissed by this browser before `now`. */
+export function isTourDismissed(tourId: string, now: number): boolean {
+ return readTourDismissals(now)[tourId] !== undefined;
+}
+
+/**
+ * Site-wide tours opt-out (per browser). When set, tours never auto-start;
+ * the header replay control remains as the explicit opt-in.
+ */
+export function isToursOptedOut(): boolean {
+ if (typeof window === "undefined") return false;
+ try {
+ return window.localStorage.getItem(OPTED_OUT_STORAGE_KEY) === "1";
+ } catch (error) {
+ if (error instanceof Error) return false;
+ throw error;
+ }
+}
+
+export function setToursOptedOut(optedOut: boolean): void {
+ if (typeof window === "undefined") return;
+ try {
+ if (optedOut) {
+ window.localStorage.setItem(OPTED_OUT_STORAGE_KEY, "1");
+ } else {
+ window.localStorage.removeItem(OPTED_OUT_STORAGE_KEY);
+ }
+ } catch (error) {
+ if (error instanceof Error) return;
+ throw error;
+ }
+}
diff --git a/src/lib/tours/registry.ts b/src/lib/tours/registry.ts
new file mode 100644
index 0000000..9ef0895
--- /dev/null
+++ b/src/lib/tours/registry.ts
@@ -0,0 +1,239 @@
+/**
+ * Tour registry: one TourDefinition per page path, defined in code. Brief
+ * steps keyed to stable `data-tour` anchors placed in the page markup. New
+ * pages join by adding a definition here and placing the anchors; the header
+ * replay control and auto-start pick the tour up automatically.
+ *
+ * Pure module (client-safe): no Payload or server imports.
+ */
+
+import type { TourDefinition } from "./types";
+
+export const TOURS: TourDefinition[] = [
+ {
+ id: "dashboard",
+ path: "/",
+ title: "Dashboard",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Your operations dashboard",
+ body: "Everything that matters at a glance: your service summary, the mission briefing, live events, and quick stats.",
+ },
+ {
+ id: "quick-stats",
+ selector: '[data-tour="dashboard-quick-stats"]',
+ title: "Quick stats",
+ body: "Unit-wide counters: active campaigns, missions, structures, factions, and members. Updated as the campaign progresses.",
+ },
+ {
+ id: "wallet",
+ selector: '[data-tour="dashboard-wallet"]',
+ title: "Wallet",
+ body: "Your personal balance in the unit's main currency. Move money in the Banking section under Logistics.",
+ },
+ ],
+ },
+ {
+ id: "roster",
+ path: "/roster",
+ title: "Unit Roster",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Unit Roster",
+ body: "Every operator in the task force, grouped by assignment. Open a member to see their rank, awards, and service record.",
+ },
+ ],
+ },
+ {
+ id: "calendar",
+ path: "/intelligence/missions/calendar",
+ title: "Friday Ops",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Friday operations calendar",
+ body: "The upcoming Friday op nights at a glance. Each Friday shows the missions scheduled for it; open one to read the full OPORD.",
+ },
+ ],
+ },
+ {
+ id: "events",
+ path: "/events",
+ title: "Event Log",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "The event log",
+ body: "A live ledger of everything the simulation does: shipments, salaries, market moves, and GM-written narrative entries (marked with a book icon).",
+ },
+ ],
+ },
+ {
+ id: "map",
+ path: "/map",
+ title: "World Map",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="map-picker-header"]',
+ title: "World map",
+ body: "Every active theater. Pick a grid to open the full tactical map with structures, resource nodes, roads, and live shipments.",
+ },
+ {
+ id: "grid",
+ selector: '[data-tour="map-picker-grid"]',
+ title: "Pick your theater",
+ body: "Cards show weather, ease of access, and who controls the area. Logistics-qualified members can place new structures from inside a map.",
+ },
+ ],
+ },
+ {
+ id: "operations",
+ path: "/operations",
+ title: "Ops Overview",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Operations overview",
+ body: "Live and historical operation activity fed from the Arma bridge: extraction returns, settlements, and readiness aggregates.",
+ },
+ ],
+ },
+ {
+ id: "operations-ledger",
+ path: "/operations/ledger",
+ title: "Ops Ledger",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Operations ledger",
+ body: "Every operation event with its derived effects. Rejected and dead-lettered entries are kept as evidence and never applied.",
+ },
+ ],
+ },
+ {
+ id: "structures",
+ path: "/logistics",
+ title: "Structures",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Structures",
+ body: "Every building the unit holds and what is stored inside. Open a structure to manage its storage grid, staff, and upgrades.",
+ showWhen: (gates) => gates.hasLogistics,
+ },
+ ],
+ },
+ {
+ id: "shipments",
+ path: "/logistics/shipments",
+ title: "Shipments",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Shipments",
+ body: "Cargo moving between structures by road, air, or sea. Progress updates live; the map shows vehicles moving along their routes.",
+ },
+ {
+ id: "filters",
+ selector: '[data-tour="shipments-filters"]',
+ title: "Filter by status",
+ body: "Slice the list by where a shipment is in its journey: pending, in transit, arrived, or failed.",
+ },
+ ],
+ },
+ {
+ id: "banking",
+ path: "/logistics/banking",
+ title: "Banking",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Banking",
+ body: "Personal wallets plus unit and faction treasuries. Deposits, withdrawals, and transfers all land here.",
+ },
+ {
+ id: "overview",
+ selector: '[data-tour="banking-overview"]',
+ title: "Your wallet",
+ body: "Your personal balance sits at the top. The tabs below show your activity and, for managers, every unit account.",
+ },
+ ],
+ },
+ {
+ id: "market",
+ path: "/logistics/market",
+ title: "Market",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "The market",
+ body: "Buy gear from other members and NPC vendors, or list your own locker items for sale. Haggling is welcome: vendors counter-offer.",
+ },
+ ],
+ },
+ {
+ id: "wiki",
+ path: "/wiki",
+ title: "Wiki",
+ steps: [
+ {
+ id: "controls",
+ selector: '[data-tour="wiki-controls"]',
+ title: "The unit wiki",
+ body: "A shared field guide for campaigns, lore, and how the unit's systems work. Search, filter by tag, or browse by category tab.",
+ },
+ ],
+ },
+ {
+ id: "awards",
+ path: "/awards",
+ title: "Awards",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Awards",
+ body: "Every medal and ribbon a member can earn, with requirements and artwork. Design new ribbons or, as staff, review submissions.",
+ },
+ {
+ id: "actions",
+ selector: '[data-tour="awards-actions"]',
+ title: "Design and review",
+ body: "Anyone can design a ribbon. Staff reviewers approve or send back submissions from this same toolbar.",
+ showWhen: (gates) => gates.hasAdminPanel,
+ },
+ ],
+ },
+ {
+ id: "locker",
+ path: "/locker",
+ title: "Locker",
+ steps: [
+ {
+ id: "welcome",
+ selector: '[data-tour="page-header"]',
+ title: "Personal locker",
+ body: "Your gear storage: items on the grid, loadouts you build from them, and what you can list on the market.",
+ },
+ ],
+ },
+];
+
+/** Exact-path lookup. Query strings are ignored (pathname only). */
+export function getTourForPath(pathname: string | null | undefined): TourDefinition | null {
+ if (!pathname) return null;
+ return TOURS.find((tour) => tour.path === pathname) ?? null;
+}
diff --git a/src/lib/tours/types.ts b/src/lib/tours/types.ts
new file mode 100644
index 0000000..e309933
--- /dev/null
+++ b/src/lib/tours/types.ts
@@ -0,0 +1,54 @@
+/**
+ * Guided tour domain types. A tour is defined in code per page: brief steps
+ * keyed to stable selectors (usually `data-tour="..."` attributes). Steps can
+ * be gated so users only see what their permissions and qualifications
+ * actually unlock, mirroring the sidebar's conditional groups.
+ */
+
+/**
+ * Per-user access signals the layout resolves server-side and hands to the
+ * tour host. Mirrors the sidebar's visibility inputs (AppSidebar props).
+ */
+export interface TourGateContext {
+ readonly hasIntelligence: boolean;
+ readonly hasLogistics: boolean;
+ readonly hasAdminPanel: boolean;
+ readonly statisticsEnabled: boolean;
+ readonly helpdeskEnabled: boolean;
+}
+
+export interface TourStep {
+ /** Stable id within the tour (used for keys and debugging). */
+ readonly id: string;
+ /** CSS selector the spotlight anchors to. Must exist on the page. */
+ readonly selector: string;
+ readonly title: string;
+ readonly body: string;
+ /**
+ * Show this step only when the gate predicate passes. Omitted means the
+ * step is visible to every user who can see the page at all.
+ */
+ readonly showWhen?: (gates: TourGateContext) => boolean;
+}
+
+export interface TourDefinition {
+ /** Stable id used in per-browser dismissal storage. */
+ readonly id: string;
+ /** Exact pathname this tour belongs to. */
+ readonly path: string;
+ readonly title: string;
+ readonly steps: readonly TourStep[];
+}
+
+export const DEFAULT_GATES: TourGateContext = {
+ hasIntelligence: true,
+ hasLogistics: true,
+ hasAdminPanel: true,
+ statisticsEnabled: true,
+ helpdeskEnabled: true,
+};
+
+/** Steps filtered down to what the visitor can actually access. */
+export function visibleTourSteps(steps: readonly TourStep[], gates: TourGateContext): TourStep[] {
+ return steps.filter((step) => step.showWhen?.(gates) ?? true);
+}
diff --git a/tests/e2e/tours.e2e.spec.ts b/tests/e2e/tours.e2e.spec.ts
new file mode 100644
index 0000000..ca0e3a2
--- /dev/null
+++ b/tests/e2e/tours.e2e.spec.ts
@@ -0,0 +1,95 @@
+import { expect, test, type Page } from "@playwright/test";
+
+/**
+ * Guided tour smoke: auto-start on first visit, per-browser dismissal via
+ * localStorage, and the header replay control. Each test gets a fresh browser
+ * context, so localStorage starts empty: exactly the "first visit" state.
+ */
+
+/** Login and wait for the redirect to land (cold dev-server compiles are slow). */
+async function login(page: Page): Promise {
+ await page.goto("/login");
+ await page.getByLabel("Username").fill("dev");
+ await page.getByLabel("Password").fill("Test123");
+ await page.getByRole("button", { name: "Log in" }).click();
+ await page.waitForURL((url) => !url.pathname.startsWith("/login"), { timeout: 90_000 });
+}
+
+test.describe("Guided tours", () => {
+ test("auto-starts on first visit, dismisses, and never auto-starts again", async ({ page }) => {
+ await login(page);
+
+ // Landing on the dashboard starts its tour (first visit for this browser).
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ await expect(page.locator("[data-tour-card]")).toContainText("1 / 3");
+
+ // Walk the tour to the end: dismissal is remembered.
+ await page.getByRole("button", { name: "Next", exact: true }).click();
+ await expect(page.locator("[data-tour-card]")).toContainText("2 / 3");
+ await page.getByRole("button", { name: "Next", exact: true }).click();
+ await expect(page.locator("[data-tour-card]")).toContainText("3 / 3");
+ await page.getByRole("button", { name: "Done" }).click();
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+
+ const dismissals = await page.evaluate(() =>
+ window.localStorage.getItem("ptf:tour:dismissals"),
+ );
+ expect(dismissals).not.toBeNull();
+ expect(JSON.parse(dismissals ?? "{}")).toHaveProperty("dashboard");
+
+ // Reload: the tour does NOT auto-start again.
+ await page.reload();
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+ });
+
+ test("replay control re-runs the tour after dismissal", async ({ page }) => {
+ await login(page);
+
+ // Dismiss the auto-started tour immediately.
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ await page.getByRole("button", { name: "Skip", exact: true }).click();
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+
+ // The header replay control brings it back on demand.
+ await page.getByRole("button", { name: "Replay page tour" }).click();
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ await expect(page.locator("[data-tour-card]")).toContainText("Dashboard");
+ });
+
+ test("skip all opts the browser out; the account toggle re-enables", async ({ page }) => {
+ await login(page);
+
+ // Opt out from the tour card itself.
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ await page.getByRole("button", { name: "Skip all tours on this device" }).click();
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+ expect(await page.evaluate(() => window.localStorage.getItem("ptf:tours:opted-out"))).toBe("1");
+
+ // Another toured page no longer auto-starts...
+ await page.goto("/wiki");
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+
+ // ...but replay stays available as the explicit opt-in.
+ await page.getByRole("button", { name: "Replay page tour" }).click();
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ await page.getByRole("button", { name: "Skip", exact: true }).click();
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+
+ // Re-enable from the account page (the switch reads off after the opt-out),
+ // then a never-visited toured page auto-starts again.
+ await page.goto("/account");
+ const toursSwitch = page.getByRole("switch", { name: "Auto-start page tours" });
+ await expect(toursSwitch).toBeVisible();
+ await toursSwitch.click();
+ await page.goto("/awards");
+ await expect(page.locator("[data-tour-card]")).toBeVisible();
+ });
+
+ test("pages without tours show no replay control and no overlay", async ({ page }) => {
+ await login(page);
+
+ await page.goto("/account");
+ await expect(page.locator("[data-tour-card]")).toHaveCount(0);
+ await expect(page.getByRole("button", { name: "Replay page tour" })).toHaveCount(0);
+ });
+});
diff --git a/tests/int/tours.int.spec.ts b/tests/int/tours.int.spec.ts
new file mode 100644
index 0000000..e89f09e
--- /dev/null
+++ b/tests/int/tours.int.spec.ts
@@ -0,0 +1,192 @@
+import { beforeEach, describe, expect, it } from "vitest";
+import {
+ DEFAULT_GATES,
+ visibleTourSteps,
+ type TourGateContext,
+ type TourStep,
+} from "@/lib/tours/types";
+import { TOURS, getTourForPath } from "@/lib/tours/registry";
+import {
+ isTourDismissed,
+ isToursOptedOut,
+ readTourDismissals,
+ setToursOptedOut,
+ storeTourDismissal,
+ writeTourDismissals,
+} from "@/lib/tours/dismissals";
+
+const gates = (overrides: Partial): TourGateContext => ({
+ ...DEFAULT_GATES,
+ ...overrides,
+});
+
+describe("tour step filtering", () => {
+ const steps: TourStep[] = [
+ { id: "open", selector: '[data-tour="page-header"]', title: "Open", body: "b" },
+ {
+ id: "logistics-only",
+ selector: '[data-tour="logistics-only"]',
+ title: "Logistics",
+ body: "b",
+ showWhen: (g) => g.hasLogistics,
+ },
+ {
+ id: "intel-only",
+ selector: '[data-tour="intel-only"]',
+ title: "Intel",
+ body: "b",
+ showWhen: (g) => g.hasIntelligence,
+ },
+ {
+ id: "stats-flag",
+ selector: '[data-tour="stats"]',
+ title: "Stats",
+ body: "b",
+ showWhen: (g) => g.statisticsEnabled,
+ },
+ ];
+
+ it("a fully privileged visitor sees every step", () => {
+ expect(visibleTourSteps(steps, DEFAULT_GATES)).toHaveLength(4);
+ });
+
+ it("a visitor without logistics qualification never sees logistics steps", () => {
+ const visible = visibleTourSteps(steps, gates({ hasLogistics: false }));
+ expect(visible.map((s) => s.id)).toEqual(["open", "intel-only", "stats-flag"]);
+ });
+
+ it("a visitor without intelligence qualification never sees intel steps", () => {
+ const visible = visibleTourSteps(steps, gates({ hasIntelligence: false }));
+ expect(visible.map((s) => s.id)).toEqual(["open", "logistics-only", "stats-flag"]);
+ });
+
+ it("feature-flag gates hide steps when the feature is disabled", () => {
+ const visible = visibleTourSteps(steps, gates({ statisticsEnabled: false }));
+ expect(visible.map((s) => s.id)).toEqual(["open", "logistics-only", "intel-only"]);
+ });
+
+ it("a visitor with nothing unlocked sees only ungated steps", () => {
+ const visible = visibleTourSteps(
+ steps,
+ gates({
+ hasLogistics: false,
+ hasIntelligence: false,
+ hasAdminPanel: false,
+ statisticsEnabled: false,
+ helpdeskEnabled: false,
+ }),
+ );
+ expect(visible.map((s) => s.id)).toEqual(["open"]);
+ });
+});
+
+describe("tour registry", () => {
+ it("covers the major sidebar routes", () => {
+ const paths = TOURS.map((tour) => tour.path);
+ for (const path of [
+ "/",
+ "/roster",
+ "/map",
+ "/operations",
+ "/logistics",
+ "/logistics/shipments",
+ "/logistics/banking",
+ "/logistics/market",
+ "/wiki",
+ "/awards",
+ "/locker",
+ ]) {
+ expect(paths, `missing tour for ${path}`).toContain(path);
+ }
+ });
+
+ it("has unique tour ids and unique paths", () => {
+ expect(new Set(TOURS.map((t) => t.id)).size).toBe(TOURS.length);
+ expect(new Set(TOURS.map((t) => t.path)).size).toBe(TOURS.length);
+ });
+
+ it("gives every step a unique id and a data-tour anchor", () => {
+ for (const tour of TOURS) {
+ expect(tour.steps.length, `${tour.id} has steps`).toBeGreaterThan(0);
+ expect(new Set(tour.steps.map((s) => s.id)).size, tour.id).toBe(tour.steps.length);
+ for (const step of tour.steps) {
+ expect(step.selector, `${tour.id}/${step.id}`).toMatch(/^(\[data-tour=|html|body|#)/);
+ expect(step.body.length, `${tour.id}/${step.id}`).toBeGreaterThan(0);
+ expect(step.body, `${tour.id}/${step.id} copy is dash-clean`).not.toMatch(/[–—]/);
+ }
+ }
+ });
+
+ it("looks up tours by exact path and ignores query strings", () => {
+ expect(getTourForPath("/")?.id).toBe("dashboard");
+ expect(getTourForPath("/logistics/shipments")?.id).toBe("shipments");
+ expect(getTourForPath("/logistics/shipments?foo=bar")).toBeNull();
+ expect(getTourForPath("/no/such/page")).toBeNull();
+ expect(getTourForPath(null)).toBeNull();
+ expect(getTourForPath(undefined)).toBeNull();
+ });
+});
+
+describe("tour dismissals (per-browser localStorage)", () => {
+ const originalSetItem = window.localStorage.setItem.bind(window.localStorage);
+ const originalGetItem = window.localStorage.getItem.bind(window.localStorage);
+
+ beforeEach(() => {
+ window.localStorage.clear();
+ });
+
+ it("stores, reads, and reports dismissals", () => {
+ const now = 1_000_000;
+ expect(isTourDismissed("dashboard", now)).toBe(false);
+ storeTourDismissal("dashboard", now);
+ expect(isTourDismissed("dashboard", now)).toBe(true);
+ expect(isTourDismissed("other-tour", now)).toBe(false);
+ expect(readTourDismissals(now)).toEqual({ dashboard: now });
+ });
+
+ it("merges dismissals without losing other tours", () => {
+ const now = 5_000;
+ window.localStorage.clear();
+ writeTourDismissals({ a: now }, now);
+ storeTourDismissal("b", now + 1);
+ expect(readTourDismissals(now)).toEqual({ a: now, b: now + 1 });
+ });
+
+ it("degrades to an empty map when storage reads fail", () => {
+ window.localStorage.setItem = () => {
+ throw new Error("disabled");
+ };
+ window.localStorage.getItem = () => {
+ throw new Error("disabled");
+ };
+ expect(readTourDismissals(1)).toEqual({});
+ expect(isTourDismissed("dashboard", 1)).toBe(false);
+ // Writers swallow the failure instead of crashing the page.
+ expect(() => storeTourDismissal("dashboard", 1)).not.toThrow();
+ });
+
+ window.localStorage.setItem = originalSetItem;
+ window.localStorage.getItem = originalGetItem;
+});
+
+describe("tours opt-out (skip all)", () => {
+ beforeEach(() => {
+ window.localStorage.clear();
+ });
+
+ it("defaults to opted-in and toggles per browser", () => {
+ expect(isToursOptedOut()).toBe(false);
+ setToursOptedOut(true);
+ expect(isToursOptedOut()).toBe(true);
+ setToursOptedOut(false);
+ expect(isToursOptedOut()).toBe(false);
+ });
+
+ it("keeps the opt-out independent of per-tour dismissals", () => {
+ setToursOptedOut(true);
+ storeTourDismissal("dashboard", 1);
+ setToursOptedOut(false);
+ expect(isTourDismissed("dashboard", 1)).toBe(true);
+ expect(isToursOptedOut()).toBe(false);
+ });
+});