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.

Window is the visual shell that surrounds every application in DevOS. It handles all the behaviour you would expect from a desktop window — dragging, resizing, focusing, minimising, maximising, and closing — entirely within a single self-contained React component backed by Framer Motion for fluid animations. App components are dropped inside the content area as black-box children; Window itself is agnostic about what it is hosting.

Props

PropTypeDescription
windowDataWindowDataThe full window object from useWindowManager().windows
windowData carries everything Window needs: the id used to dispatch mutations, the title and icon shown in the title bar, the component rendered in the body, and the boolean flags isMinimized, isMaximized that drive visual state.

Minimized behaviour

When windowData.isMinimized is true, Window returns null immediately — the DOM node is removed and the Framer Motion exit animation fires. Because the window object itself stays in the WindowManager state, the app component’s local state is preserved for the lifetime of the session.
Minimised windows are not unmounted from the context — only from the DOM. Any state held inside the app component (scroll position, form input, loaded data) survives a minimise/restore cycle without needing to be persisted externally.

Title bar

The title bar is a 40px-tall header flex row that displays the window icon and title on the left, and the three control buttons on the right.
  • Focused windows: bg-os-teal text-white with a text-os-cyan icon tint
  • Unfocused windows: bg-os-teal/80 text-white/80 with no icon tint
  • Pointer down on the title bar calls both focusWindow(id) and dragControls.start(event), so a single gesture both focuses and begins a drag
  • Double-click on the title bar calls maximizeWindow(id) to toggle maximised state

Control buttons

Three icon buttons sit at the right edge of the title bar:
ButtonIconAction
MinimizeMinus (lucide)minimizeWindow(id) — hides the window, keeps taskbar button
Maximize / RestoreSquare (lucide)maximizeWindow(id) — toggles full-viewport size
CloseX (lucide)closeWindow(id) — removes the window entirely
Each button calls event.stopPropagation() to prevent the click from bubbling to the title bar’s drag handler.

Drag behaviour

Dragging uses Framer Motion’s dragControls API to restrict the drag initiation point to the title bar only:
const dragControls = useDragControls();

// On the Framer Motion div:
drag={!isMaximized}
dragControls={dragControls}
dragListener={false}   // disables automatic drag detection on the element itself
dragMomentum={false}   // no momentum/inertia after release

// On the title bar header:
onPointerDown={(e) => {
  focusWindow(id);
  dragControls.start(e);
}}
The onDragEnd callback updates a local position state with the new { x, y } offset so the window stays where it was dropped when other state updates re-render it. Drag is automatically disabled (drag={false}) when isMaximized is true to prevent dragging a full-screen window.

Resize handle

A 16×16 px div sits in the absolute bottom-right corner (cursor-se-resize). It uses raw pointer events rather than a drag library for fine-grained control:
onPointerDown={(e) => {
  e.stopPropagation();
  const startX = e.clientX;
  const startY = e.clientY;
  const startWidth = size.width;
  const startHeight = size.height;

  const onMove = (moveEvent) => {
    setSize({
      width:  Math.max(300, startWidth  + (moveEvent.clientX - startX)),
      height: Math.max(200, startHeight + (moveEvent.clientY - startY)),
    });
  };

  const onUp = () => {
    window.removeEventListener('pointermove', onMove);
    window.removeEventListener('pointerup', onUp);
  };

  window.addEventListener('pointermove', onMove);
  window.addEventListener('pointerup', onUp);
}}
The Math.max guards enforce minimum dimensions: 300 px wide and 200 px tall. The resize handle is hidden when isMaximized is true.

Initial position

On first mount, a useEffect (with an empty dependency array) computes a centred-with-jitter starting position:
{
  x: Math.max(0, (window.innerWidth  - size.width)  / 2 + (Math.random() * 40 - 20)),
  y: Math.max(0, (window.innerHeight - size.height) / 2 + (Math.random() * 40 - 20)),
}
The ±20 px random offset means that when multiple windows are opened quickly they do not stack perfectly on top of each other.

Animations

The Framer Motion div carries these animation props:
initial={{ scale: 0.8, opacity: 0 }}
animate={{
  scale: 1,
  opacity: 1,
  x: isMaximized ? 0 : position.x,
  y: isMaximized ? 0 : position.y,
  width:  isMaximized ? '100vw'              : size.width,
  height: isMaximized ? 'calc(100vh - 48px)' : size.height,
}}
exit={{ scale: 0.8, opacity: 0, y: '100vh' }}
transition={{ type: 'spring', bounce: 0.1, duration: 0.4 }}
StateEffect
OpenScales from 80 % → 100 % and fades in
CloseScales back to 80 % and slides down off-screen (y: 100vh)
MaximizeWidth and height animate to viewport size; position snaps to 0, 0
RestoreWidth, height, and position animate back to saved values
The spring transition with bounce: 0.1 gives a subtle elastic feel without being distracting.

Content area

The inner content div uses flex-1 overflow-auto and renders windowData.component with no props:
<div className="flex-1 overflow-auto relative bg-os-window cursor-auto">
  <windowData.component />
</div>
App components are therefore fully self-contained — they read their own data sources and manage their own state independently of the window chrome.

Build docs developers (and LLMs) love