Fox Events Fox Events

Use cases

Ideas and patterns for where Fox Events fits: micro-frontends, wizards, dashboards, games, e-commerce, design systems, and more.

Micro-frontends and shells

Multiple apps or fragments need to talk without tight coupling. Use a shared event map and, if you need isolation, Fox.forScope.

ts
// Shell emits: "shell:navigate", "shell:theme-changed"
// Fragment A emits: "cart:updated", "checkout:started"
// Fragment B listens to cart:updated and updates the badge

type ShellEvents = {
  "shell:navigate": { path: string };
  "shell:theme-changed": { theme: "light" | "dark" };
  "cart:updated": { itemCount: number };
  "checkout:started": void;
};

Fox.on<ShellEvents>("cart:updated", (p) => updateCartBadge(p.itemCount));
Fox.emit<ShellEvents>("shell:navigate", { path: "/checkout" });

Each micro-frontend can use its own scope so events don’t leak; the shell can still emit global events for navigation or theme.

---

Wizards and multi-step flows

Steps emit completion; the wizard (or a central coordinator) listens and advances. Typed payloads keep “step 2 result” clear.

ts
type WizardEvents = {
  "wizard:step-complete": { stepId: string; data: Record<string, unknown> };
  "wizard:back": { fromStep: string };
  "wizard:abort": void;
};

// Step 2 form
Fox.emit<WizardEvents>("wizard:step-complete", {
  stepId: "payment",
  data: { method: "card", last4: "4242" },
});

// Wizard container
Fox.on<WizardEvents>("wizard:step-complete", (p) => {
  saveStepData(p.stepId, p.data);
  goToNextStep(p.stepId);
});

Use once for “wait until user completes step” in async flows.

---

Real-time dashboards and live data

Feeds (WebSocket, polling, SSE) emit normalized events; widgets subscribe only to what they need. Trail helps debug “who received what.”

ts
type DashboardEvents = {
  "feed:price": { symbol: string; price: number; ts: number };
  "feed:alert": { severity: "info" | "warn" | "error"; message: string };
  "dashboard:filter-changed": { filter: string };
};

// Price widget
Fox.on<DashboardEvents>("feed:price", (p) => {
  if (p.symbol === "BTC") updateBtcPrice(p.price);
});

// When replaying or debugging
const history = Fox.channel<DashboardEvents["feed:price"]>("feed:price").trail();
console.log(history.toText());

---

Game UI: scores, achievements, levels

Game logic emits; UI layer listens and animates. Typed events avoid magic strings and keep payloads (e.g. score delta, achievement id) clear.

ts
type GameEvents = {
  "game:score": { delta: number; total: number };
  "game:achievement": { id: string; name: string };
  "game:level-complete": { level: number; stars: number };
  "game:over": { score: number; reason: string };
};

Fox.emit<GameEvents>("game:achievement", { id: "first_blood", name: "First Blood" });
Fox.on<GameEvents>("game:score", (p) => animateScore(p.total));
Fox.once<GameEvents>("game:level-complete").then((p) => showLevelCompleteModal(p.stars));

---

E-commerce: cart, checkout, inventory

Cart service emits cart:item-added, cart:item-removed; header, mini-cart, and checkout listen. Optional storage can persist cart events for refresh/hydrate.

ts
type CartEvents = {
  "cart:item-added": { sku: string; qty: number; name: string };
  "cart:item-removed": { sku: string };
  "cart:updated": { itemCount: number; total: number };
  "checkout:started": void;
  "checkout:completed": { orderId: string };
};

Fox.emit<CartEvents>("cart:item-added", { sku: "SHIRT-1", qty: 2, name: "T-Shirt" });
Fox.on<CartEvents>("cart:updated", (p) => updateHeaderBadge(p.itemCount));

---

Design systems and theme/layout toggles

Theme or layout changes broadcast once; every component that cares subscribes. No prop drilling.

ts
type DesignSystemEvents = {
  "ds:theme": { theme: "light" | "dark" | "system" };
  "ds:sidebar": { open: boolean };
  "ds:density": { density: "compact" | "comfortable" };
};

Fox.emit<DesignSystemEvents>("ds:theme", { theme: "dark" });
Fox.on<DesignSystemEvents>("ds:theme", (p) => document.documentElement.setAttribute("data-theme", p.theme));

---

React Native + WebView hybrid

RN sends commands into the WebView; the web app emits “ready”, “login”, etc. back. Same event names and types on both sides. Set up the bridge on the web with createReactNativeBridge and on RN with createFoxRNAdapter + WebView onMessage / inject.

1. Web (inside WebView) — setup bridge, listen for RN commands, emit to RN

ts
import { Fox } from "fox-events";
import { createReactNativeBridge } from "fox-events/bridge-react-native";

createReactNativeBridge({ direction: "both", filter: (n) => n.startsWith("app:") });

Fox.on("app:command", (payload) => {
  if (payload.action === "reload") window.location.reload();
  if (payload.action === "back") history.back();
});

Fox.emit("app:ready", { version: "1.0" });
Fox.emit("app:login", { userId: "u-1", email: "user@example.com" });

2. React Native — adapter + WebView, send commands, listen for web events

ts
import { useRef, useMemo } from "react";
import { WebView } from "react-native-webview";
import { createFoxRNAdapter } from "fox-events/bridge-react-native";

function App() {
  const webViewRef = useRef<WebView>(null);
  const fox = useMemo(
    () =>
      createFoxRNAdapter({
        sendToWebView: (msg) => {
          const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
          webViewRef.current?.injectJavaScript(code);
        },
        filter: (name) => name.startsWith("app:"),
      }),
    []
  );

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: "https://example.com" }}
      onMessage={(e) => fox.handleMessage(e.nativeEvent.data)}
    />
  );
}

// Usage: send command to web, listen for events from web
fox.emit("app:command", { action: "reload" });
fox.on("app:ready", (p) => setWebReady(true));
fox.on("app:login", (p) => setUser(p.userId, p.email));
const loginPayload = await fox.once("app:login");

Define one event map and use it in both environments for a consistent contract.

---

Analytics and audit trail

Emit every meaningful action as an event; one listener sends to your analytics or audit log. Trail gives you a local trace for debugging.

ts
type AuditEvents = {
  "audit:action": { action: string; payload: unknown; userId: string; ts: number };
};

Fox.on<AuditEvents>("audit:action", (p) => sendToAnalytics(p));
// Any feature
Fox.emit<AuditEvents>("audit:action", {
  action: "checkout.completed",
  payload: { orderId: "123" },
  userId: "u-1",
  ts: Date.now(),
});

---

Plugin and extension lifecycle

Host app emits “app:ready”, “route:changed”, “user:logged-in”; plugins subscribe and run their logic. Typed events make the contract clear for third-party code.

ts
type HostEvents = {
  "app:ready": void;
  "route:changed": { path: string; params: Record<string, string> };
  "user:logged-in": { userId: string; role: string };
};

// Plugin
Fox.on<HostEvents>("route:changed", (p) => {
  if (p.path.startsWith("/admin")) loadAdminPlugin();
});

---

A/B tests and feature flags

Emit “experiment:exposed” or “feature:checked” with variant id; analytics or logging listen. No need to pass flags through every component.

ts
type ExperimentEvents = {
  "experiment:exposed": { experimentId: string; variant: string };
  "feature:checked": { name: string; enabled: boolean };
};

Fox.emit<ExperimentEvents>("experiment:exposed", { experimentId: "checkout-v2", variant: "B" });
Fox.on<ExperimentEvents>("experiment:exposed", (p) => trackExposure(p.experimentId, p.variant));

---

Notifications and toasts

A single channel for “show toast”; any part of the app emits, the notification component listens and queues.

ts
const toasts = Fox.channel<{ id: string; message: string; type: "info" | "success" | "error"; duration?: number }>("ui:toast");

toasts.emit({ id: crypto.randomUUID(), message: "Saved!", type: "success", duration: 3000 });
toasts.on((t) => toastQueue.add(t));

---

Undo / redo and command history

Emit commands as events; a central handler pushes to a history stack and applies inverse actions. Trail can complement for debugging.

ts
type CommandEvents = {
  "command:execute": { type: string; payload: unknown; undo: () => void };
};

Fox.on<CommandEvents>("command:execute", (p) => {
  commandHistory.push(p);
  p.undo(); // or apply, then store undo
});
Fox.emit<CommandEvents>("command:execute", {
  type: "rename",
  payload: { id: "doc-1", name: "New Name" },
  undo: () => revertRename("doc-1"),
});

---

Pick the events and payloads that match your domain; use foxes.ts to keep them in one place and typed end-to-end.