Internal ops console
The product is the hub every engineer and operator opens at the start of a shift. Deploys, feature flags, a user-lookup tool, the on-call schedule, a metrics board: today each lives at its own URL and everyone keeps ten bookmarks. As one desktop, each tool is an App, the dock holds them, and Spotlight finds them by name.
Why the metaphor fits
Section titled “Why the metaphor fits”- Ops work is multi-window by nature. You look up a customer while a deploy runs while a dashboard ticks. Separate tabs lose the shared context; windows on one desktop keep it.
- The audience is power users. Cmd-K to jump to a tool and Cmd-1..9 to cycle is faster than a nav bar, and it is muscle memory they already have from the OS.
- The tool count only grows. A registry the dock, menu bar, and Spotlight all read from means a new internal tool is one
Appobject, not a new route plus a nav entry plus a search hookup.
Per-role registry
Section titled “Per-role registry”Not every operator sees every tool. Resolve the app list from the signed-in user’s role; anything that produces an App[] works as the apps prop. Render a splash while it loads.
import { useEffect, useState } from "react";import { Desktop } from "@react-ui-os/desktop";import { macosTheme } from "@react-ui-os/theme-macos";import type { App } from "@react-ui-os/core";import { deployApp, flagsApp, userLookupApp, onCallApp, metricsApp } from "./tools";
const TOOLS_BY_ROLE: Record<string, App[]> = { engineer: [deployApp, flagsApp, userLookupApp, onCallApp, metricsApp], support: [userLookupApp, onCallApp], viewer: [metricsApp],};
export function Console({ role }: { role: string }) { const [apps, setApps] = useState<App[] | null>(null);
useEffect(() => { setApps(TOOLS_BY_ROLE[role] ?? TOOLS_BY_ROLE.viewer); }, [role]);
if (!apps) return <Splash />; return <Desktop apps={apps} theme={macosTheme} brand="acme ops" />;}A support agent gets a two-tile dock; an engineer gets five. The window manager re-keys windows when the registry changes, so a role change mid-session cleans up the tools that went away without manual teardown. See the dynamic app registry recipe for the remote-fetch variant.
System health in the menu bar
Section titled “System health in the menu bar”Incident state, deploy-in-progress, queue depth: anything an operator needs to glance at without opening a window belongs in the menu-bar tray. Register it once and re-register when the value changes.
import { useEffect } from "react";import { registerStatusItem } from "@react-ui-os/desktop";import { IncidentDot } from "./icons";import { useIncidentLevel } from "./incidents";
export function IncidentStatus() { const level = useIncidentLevel(); // "ok" | "degraded" | "down" useEffect(() => { return registerStatusItem({ id: "incident", icon: <IncidentDot level={level} size={14} />, tooltip: level === "ok" ? "All systems operational" : `Incident: ${level}`, badge: level === "ok" ? undefined : level === "down" ? "!" : undefined, order: 40, onClick: () => window.open("/status", "_blank", "noopener"), }); }, [level]); return null;}Mount <IncidentStatus /> as a headless companion inside <Desktop>. The tray dedups by id, so re-registering on every level change updates the widget in place. See Status items.
Notify when a job finishes
Section titled “Notify when a job finishes”A deploy or a backfill runs longer than anyone watches. Fire a notification from the job’s completion handler; it surfaces as a toast and lands in the Notification Center even if the operator was in another tool. The handler can call notify from anywhere, no dispatcher to thread through.
import { notify } from "@react-ui-os/core";
export async function runDeploy(service: string) { const job = await startDeploy(service); await job.done; notify({ title: `Deploy finished: ${service}`, body: `${job.commit.slice(0, 7)} is live in production.`, level: "success", appId: "deploy", // badges the deploy dock tile actions: [ { label: "View logs", onClick: () => window.open(job.logsUrl, "_blank"), primary: true, }, ], });}appId ties the toast to the deploy tile so the dock shows an unread badge until the operator looks. A level: "error" notification stays pinned instead of auto-dismissing, which is what you want for a failed deploy. See Notifications.
Layouts that follow the operator
Section titled “Layouts that follow the operator”Engineers move between a laptop and a desk machine. Back the desktop with a StorageAdapter that writes to your service instead of localStorage, and the accent, dock position, and window arrangement they set on one machine are there on the next.
import { Desktop } from "@react-ui-os/desktop";
<Desktop apps={apps} theme={macosTheme} storage={remoteStorage} />;The interface is synchronous, so hydrate the cache before mounting and write through in the background. The full adapter is in the persist-windows recipe.
What you did not have to build
Section titled “What you did not have to build”The dock, the Cmd-K search across every tool, the focused-tool indicator in the menu bar, drag and resize and snap, the minimize animation, the keyboard map: all of it came from the one <Desktop> tag reading the same App[] the registry already produced. The work that remained was the tools themselves.
See also
Section titled “See also”- Dynamic app registry for the per-tenant fetch.
- Status items and Notifications for the system-wide widgets.
- StorageAdapter for cross-device sync.
- SaaS application shell for the customer-facing cousin of this shape.