Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/dev-os/llms.txt

Use this file to discover all available pages before exploring further.

DevOS achieves its desktop-OS illusion through a deliberately shallow component tree centred on a single React Context provider. There is no Redux store, no external state library, and no server. Every window you open, drag, minimise, or close is described by a plain JavaScript object in a useState array, and every transition you see is a Framer Motion spring. Understanding those two pillars — the WindowManagerProvider context and framer-motion — explains almost everything about how DevOS behaves.

Component Hierarchy

main.js is the entry point. It mounts WindowManagerProvider at the root, then renders BootScreen, Desktop, and Taskbar as siblings inside it. All three children can read and mutate window state through the shared context without any prop-drilling.
App (main.js)
└── WindowManagerProvider (context)
    ├── BootScreen (shown first, onComplete toggles to desktop)
    ├── Desktop (icon grid, renders open Window components)
    └── Taskbar (app switcher, start menu, clock)
BootScreen is rendered exclusively until the user clicks Login to Desktop, at which point a boolean in App flips and Desktop + Taskbar take over. The WindowManagerProvider persists across that transition so any state accumulated during the boot phase (none by design today, but extensible) is not lost.

State Management

WindowManagerProvider creates its context with React.createContext(undefined) and exposes it through the useWindowManager() hook, which throws a descriptive error if called outside the provider — a safety net that makes misuse obvious at development time. Inside the provider, three useState calls track the entire window system:
State variableTypePurpose
windowsArray<WindowObject>All currently open windows and their full state
focusedWindowIdstring | nullThe id of the window that currently has focus
zIndexCounternumber (starts at 10)Monotonically increasing counter used to layer windows
Every mutating function is wrapped in useCallback to prevent unnecessary re-renders in child components that destructure only the actions they need. The useWindowManager() hook exposes the following interface to any component in the tree:
const {
  windows,           // WindowObject[]
  focusedWindowId,   // string | null
  openWindow,        // (id, config) => void
  closeWindow,       // (id) => void
  minimizeWindow,    // (id) => void
  maximizeWindow,    // (id) => void
  focusWindow,       // (id) => void
} = useWindowManager();

Window Lifecycle

Each entry in the windows array is a WindowObject with the shape:
{
  id: string,
  title: string,
  icon: ReactComponent,
  component: ReactComponent,   // the app rendered inside the window
  isOpen: true,
  isMinimized: boolean,
  isMaximized: boolean,
  zIndex: number,
  defaultSize: { width: number, height: number },
}
The five lifecycle actions map to clean, immutable state updates:
  • openWindow(id, config) — checks whether a window with that id already exists in the array. If it does and it is minimised, it un-minimises it and gives it focus. If it is already open and visible, nothing happens. If it does not exist, a new WindowObject is pushed onto the array and focus is granted immediately.
  • focusWindow(id) — sets focusedWindowId to the given id, assigns the current zIndexCounter value to that window’s zIndex, then increments zIndexCounter by 1.
  • minimizeWindow(id) — sets isMinimized: true on the target window and clears focusedWindowId if it matched.
  • maximizeWindow(id) — toggles isMaximized on the target window (i.e. !e.isMaximized), then calls focusWindow so the maximised window rises to the top.
  • closeWindow(id) — filters the target window out of the windows array entirely, and clears focusedWindowId if it matched.

Animations

Every window component wraps its root element in a framer-motion motion.div. The AnimatePresence wrapper in Desktop.js detects when a window is removed from the React tree and plays its exit animation before unmounting. A typical window uses spring physics for a natural feel:
<motion.div
  initial={{ scale: 0.8, opacity: 0 }}
  animate={{ scale: 1, opacity: 1 }}
  exit={{ scale: 0.8, opacity: 0, y: '100vh' }}
  transition={{ type: 'spring', bounce: 0.1, duration: 0.4 }}
>
  {/* window chrome + app content */}
</motion.div>
Drag is handled by Framer Motion’s drag prop with dragControls bound to the title bar, so only grabbing the title bar initiates a drag — clicking inside the app content does not accidentally move the window. The BootScreen uses the same primitives for its login panel reveal: initial={{ opacity: 0, scale: 0.9 }}animate={{ opacity: 1, scale: 1 }} after the boot log finishes streaming.

Desktop Icon Registry

The I array defined in Desktop.js is the single source of truth for everything that appears on the desktop. Each entry follows this shape:
{
  id: string,                         // unique key, used as window id
  label: string,                      // text shown below the icon
  icon: ReactComponent,               // Lucide icon component
  component: ReactComponent,          // app rendered inside the window
  defaultSize: { width, height },     // initial window dimensions in px
}
The nine built-in entries are:
idlabelApp
homeREADME.mdHomeApp
aboutabout_me.exeAboutApp
projectsprojects/ProjectsApp
skillstask_manager.exeSkillsApp
workcareer.logTerminalApp
case_studiescase_studies/CaseStudiesApp
articlesnotes.txtArticlesApp
contactmail.exeContactApp
testimonialsreviews.htmlTestimonialsApp
To add a new app to the desktop, create your component in components/apps/, import it in Desktop.js, and append a new entry to the I array. The icon grid, double-click handler, and window manager integration all work automatically — no further wiring is needed.
The zIndexCounter starts at 10 and increments by 1 every time a window is opened or focused. This guarantees that whichever window was clicked last always sits on top of every other window, with no manual z-index management required. The taskbar sits at z-50 and the boot screen at z-[100], both of which are permanently above the window stack — so no matter how many windows are open, the taskbar and boot screen are never obscured.

Build docs developers (and LLMs) love