Support workspace
The product is the console a support or success agent lives in all day. They are never on one ticket: a billing question is open next to the customer’s account, a bug report sits beside the repro steps, and a new assignment lands while both are still on screen. This is the shape where the multi-window model stops being a nicety and becomes the point.
Why the metaphor fits
Section titled “Why the metaphor fits”- Real support is parallel. A single-pane inbox forces one ticket at a time and loses the second you were holding in your head. Windows keep every open thread visible.
- Comparing is constant: ticket next to customer record, two related reports, the conversation next to the knowledge-base article. Snapping puts any two side by side in one keystroke.
- Context switches are violent (a P1 interrupts a slow billing thread). Workspaces let an agent park a half-built layout and jump to the fire, then come back to it intact.
One window per ticket
Section titled “One window per ticket”A system window opened with args becomes one distinct window per argument set. Register the ticket window once; open it per ticket id and each gets its own draggable, snappable frame with the ticket number in the title bar.
import { registerSystemWindow } from "@react-ui-os/desktop";import { TicketThread } from "./TicketThread";
registerSystemWindow("ticket", { // A function name resolves per instance, so the title bar reads the id. name: ({ ticketId }) => `Ticket #${String(ticketId)}`, tagline: "Conversation", defaultBounds: { w: 520, h: 640 }, content: ({ args }) => <TicketThread id={String(args?.ticketId)} />,});
// Two tickets open at once, one window each:openWindow({ kind: "system", systemId: "ticket", args: { ticketId: "4821" } });openWindow({ kind: "system", systemId: "ticket", args: { ticketId: "4822" } });windowIdOf serializes the args into the window id, so the same ticketId focuses the existing window instead of spawning a duplicate, and two different ids coexist. See the per-instance arguments recipe.
Compare side by side
Section titled “Compare side by side”Once two ticket windows are open, the agent puts them edge to edge with the same snap vocabulary every OS uses: Cmd/Ctrl + ← sends the focused window to the left half, Cmd/Ctrl + → to the right. Dragging a title bar to a screen edge does the same, with a translucent preview of the target before release.
Cmd/Ctrl + ← focused ticket to the left halfCmd/Ctrl + → customer record to the right halfNothing to wire: snapping is part of every window <Desktop> renders. The full zone map (halves, quarters, maximize) is on the Snapping page.
A workspace per queue
Section titled “A workspace per queue”Give each queue or context its own workspace so layouts do not collide. Billing tickets live on one, technical on another, and an agent flips between them with Ctrl + Alt + ←/→ without tearing down either arrangement. The menu-bar pips show which one is active.
import { useWindowManager } from "@react-ui-os/core";
function QueueSwitcher() { const { state, switchWorkspace } = useWindowManager(); return state.workspaces.map((id) => ( <button key={id} type="button" onClick={() => switchWorkspace(id)}> {labelFor(id)} </button> ));}Opening a ticket that already lives on another workspace pulls the agent to that workspace and focuses it, the same way clicking a dock tile jumps you to its space. That means “open ticket #4821” always lands on the one window, wherever it is.
Incoming work: notify and a tray badge
Section titled “Incoming work: notify and a tray badge”New assignments should reach the agent without stealing focus from the ticket they are typing in. A headless companion subscribes to the assignment stream and fires a notification with an Open action that closes over openWindow:
import { useEffect } from "react";import { notify, useWindowManager } from "@react-ui-os/core";import { subscribeAssignments } from "./queue";
export function TicketInbox() { const { openWindow } = useWindowManager(); useEffect(() => { return subscribeAssignments((ticket) => { notify({ title: `Assigned: #${ticket.id}`, body: ticket.subject, level: "info", actions: [ { label: "Open", primary: true, onClick: () => openWindow({ kind: "system", systemId: "ticket", args: { ticketId: ticket.id }, }), }, ], }); }); }, [openWindow]); return null;}Pair it with a queue-depth widget in the menu-bar tray so the agent always sees how many are waiting:
registerStatusItem({ id: "queue-depth", icon: <InboxIcon size={14} />, tooltip: `${waiting} waiting`, badge: waiting || undefined, // hidden at zero order: 30,});Mount <TicketInbox /> inside <Desktop> and re-register the status item when waiting changes. Unlike the SaaS shell, this shape keeps the menu bar, so the tray has a home. See Notifications and Status items.
See also
Section titled “See also”- Window snapping for the full zone map and keyboard chords.
- Workspaces for per-queue contexts.
- Per-instance system window arguments for the one-window-per-ticket mechanism.
- Internal ops console for the registry and tray patterns this shape reuses.