Window Manager
The WindowManager is the core system responsible for managing all window operations in Time Capsule. It handles window lifecycle, positioning, dragging, focus management, z-index ordering, and workspace assignment.Overview
WindowManager is implemented as a singleton module pattern with private state and exposed public methods:Copy
Ask AI
const WindowManager = (() => {
let zIndex = CONFIG.WINDOW.BASE_Z_INDEX;
let highestWindowZIndex = CONFIG.WINDOW.BASE_Z_INDEX;
let highestModalZIndex = 90000;
let currentWorkspace = '1';
let lastFocusedWindowId: string | null = null;
const dragState: DragState = { /* ... */ };
// Public API
return {
init,
drag,
focusWindow,
registerWindow,
centerWindow,
switchWorkspace,
showWindow,
getNextZIndex,
getTopZIndex,
};
})();
Key Concepts
Window Registration
All windows must be registered with WindowManager to enable management features:Copy
Ask AI
function registerWindow(win: HTMLElement): void {
if (win.hasAttribute('data-cde-registered')) return;
const id = win.id;
const titlebar = document.getElementById(`${id}Titlebar`) ||
win.querySelector('.titlebar');
if (titlebar) {
// Restore session position if available
const session = settingsManager
.getSection('session')
.windows[id];
if (session && session.left && session.top) {
win.style.left = session.left;
win.style.top = session.top;
if (session.maximized) {
win.classList.add('maximized');
}
} else {
// First time: normalize position
setTimeout(() => {
normalizeWindowPosition(win);
}, CONFIG.TIMINGS.NORMALIZATION_DELAY);
}
// Enable dragging
titlebar.style.touchAction = 'none';
titlebar.addEventListener(
'pointerdown',
titlebarDragHandler
);
win.setAttribute('data-cde-registered', 'true');
}
}
Dynamic Scanning
WindowManager uses a MutationObserver to automatically detect and register new windows:Copy
Ask AI
function initDynamicScanning(): void {
// Scan existing windows
const windows = document.querySelectorAll(
'.window, .cde-retro-modal'
);
windows.forEach((el) => registerWindow(el as HTMLElement));
// Observe for new windows
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLElement) {
if (node.classList.contains('window') ||
node.classList.contains('cde-retro-modal')) {
registerWindow(node);
}
// Scan children
node.querySelectorAll('.window, .cde-retro-modal')
.forEach((el) => registerWindow(el as HTMLElement));
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
Z-Index Management
Layered Z-Index System
Z-indexes are managed in separate layers to prevent conflicts:Copy
Ask AI
// Layer allocation
const BASE_WINDOW_Z = 10000; // Regular windows
const BASE_MODAL_Z = 90000; // Modal dialogs
const DROPDOWN_Z = 20000; // Dropdown menus
let highestWindowZIndex = CONFIG.WINDOW.BASE_Z_INDEX;
let highestModalZIndex = 90000;
function getNextZIndex(isModal: boolean = false): number {
if (isModal) {
return ++highestModalZIndex;
}
return ++highestWindowZIndex;
}
function getTopZIndex(): number {
return Math.max(highestWindowZIndex, highestModalZIndex);
}
Focus and Z-Index
Focusing a window assigns it the highest z-index in its layer:Copy
Ask AI
function focusWindow(id: string): void {
if (id === lastFocusedWindowId) return;
const win = document.getElementById(id);
if (!win) return;
if (!dragState.isDragging) {
// Remove active class from previous window
if (lastFocusedWindowId) {
const prevWin = document.getElementById(
lastFocusedWindowId
);
if (prevWin) prevWin.classList.remove('active');
}
// Periodic cleanup of stale classes
if (Math.random() < 0.05) {
document.querySelectorAll('.active').forEach((el) => {
if (el.id !== id) el.classList.remove('active');
});
}
win.classList.add('active');
lastFocusedWindowId = id;
zIndex = getNextZIndex();
win.style.zIndex = String(zIndex);
if (window.AudioManager) window.AudioManager.click();
}
}
Drag and Drop
Drag State Management
Copy
Ask AI
interface DragState {
element: HTMLElement | null;
offsetX: number;
offsetY: number;
startX: number;
startY: number;
startLeft: number;
startTop: number;
lastX: number;
lastY: number;
isDragging: boolean;
}
Pointer-Based Dragging
Dragging uses PointerEvents for unified mouse/touch support:Copy
Ask AI
function drag(e: PointerEvent, id: string): void {
// Disable drag on mobile
if (isMobile()) {
logger.log(
`[WindowManager] Drag disabled on mobile for: ${id}`
);
return;
}
if (!e.isPrimary) return;
const el = document.getElementById(id);
if (!el) return;
e.preventDefault();
e.stopPropagation();
// Normalize position if transform is applied
if (window.getComputedStyle(el).transform !== 'none') {
normalizeWindowPosition(el);
}
focusWindow(id);
const rect = el.getBoundingClientRect();
dragState.element = el;
dragState.offsetX = e.clientX - rect.left;
dragState.offsetY = e.clientY - rect.top;
dragState.lastX = e.clientX;
dragState.lastY = e.clientY;
dragState.isDragging = true;
// Capture pointer for reliable tracking
el.setPointerCapture(e.pointerId);
// X11-style move cursor
document.documentElement.style.setProperty(
'--cde-cursor-override',
"url('/icons/cursors/cursor-move.svg') 12 12, move"
);
document.body.style.cursor =
"url('/icons/cursors/cursor-move.svg') 12 12, move";
// Performance optimization
el.style.willChange = 'transform, left, top';
el.addEventListener('pointermove', move, { passive: false });
el.addEventListener('pointerup', stopDrag, { passive: false });
el.addEventListener('pointercancel', stopDrag, { passive: false });
}
Mouse Acceleration
Mouse movement supports configurable acceleration:Copy
Ask AI
function move(e: PointerEvent): void {
if (!dragState.element || !dragState.isDragging) return;
e.preventDefault();
e.stopPropagation();
// Get acceleration from CSS variable
const accelStr = getComputedStyle(document.documentElement)
.getPropertyValue('--mouse-acceleration');
const acceleration = parseFloat(accelStr) || 1;
// Calculate delta
const deltaX = e.clientX - dragState.lastX;
const deltaY = e.clientY - dragState.lastY;
// Apply acceleration
let currentLeft = parseFloat(
dragState.element.style.left || '0'
);
let currentTop = parseFloat(
dragState.element.style.top || '0'
);
let left = currentLeft + deltaX * acceleration;
let top = currentTop + deltaY * acceleration;
// Update tracking
dragState.lastX = e.clientX;
dragState.lastY = e.clientY;
// Apply constraints (shown in next section)
// ...
dragState.element.style.left = left + 'px';
dragState.element.style.top = top + 'px';
}
Viewport Constraints
Windows are constrained to stay within viewport bounds:Copy
Ask AI
function move(e: PointerEvent): void {
// ... delta calculation ...
const winWidth = dragState.element.offsetWidth;
const winHeight = dragState.element.offsetHeight;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const TOP_BAR_HEIGHT = CONFIG.WINDOW.TOP_BAR_HEIGHT;
const PANEL_HEIGHT = isMobile() ? 65 : 85;
// Define bounds
const minX = 0;
const maxX = Math.max(0, viewportWidth - winWidth);
const minY = TOP_BAR_HEIGHT;
const maxY = Math.max(
minY,
viewportHeight - winHeight - PANEL_HEIGHT
);
// Clamp position
left = Math.max(minX, Math.min(left, maxX));
top = Math.max(minY, Math.min(top, maxY));
dragState.element.style.left = left + 'px';
dragState.element.style.top = top + 'px';
}
Opaque vs Wireframe Dragging
Copy
Ask AI
function move(e: PointerEvent): void {
// ...
// Check if opaque dragging is enabled
const opaque = document.documentElement
.getAttribute('data-opaque-drag') !== 'false';
if (!opaque) {
// Wireframe mode: lighter rendering
dragState.element.classList.add('dragging-wireframe');
}
// ...
}
Drag Completion
Copy
Ask AI
function stopDrag(e: PointerEvent): void {
if (!dragState.element || !dragState.isDragging) return;
e.preventDefault();
e.stopPropagation();
const el = dragState.element;
el.releasePointerCapture(e.pointerId);
el.removeEventListener('pointermove', move);
el.removeEventListener('pointerup', stopDrag);
el.removeEventListener('pointercancel', stopDrag);
// Clear performance hints
el.style.willChange = 'auto';
// Restore cursor
document.body.style.cursor = '';
el.classList.remove('dragging-wireframe');
dragState.isDragging = false;
// Persist position
settingsManager.updateWindowSession(el.id, {
left: el.style.left,
top: el.style.top,
maximized: el.classList.contains('maximized'),
});
dragState.element = null;
}
Window Operations
Minimize
Copy
Ask AI
function minimizeWindow(id: string): void {
const win = document.getElementById(id);
if (!win) return;
if (win.style.display !== 'none') {
// Save state before hiding
windowStates[id] = {
display: win.style.display,
left: win.style.left,
top: win.style.top,
width: win.style.width,
height: win.style.height,
maximized: win.classList.contains('maximized'),
};
// Animate closing
win.classList.add('window-closing');
if (window.AudioManager) {
window.AudioManager.windowMinimize();
}
// Hide after animation
win.addEventListener(
'animationend',
() => {
win.style.display = 'none';
win.classList.remove('window-closing');
},
{ once: true }
);
}
}
Maximize
Copy
Ask AI
function maximizeWindow(id: string): void {
const win = document.getElementById(id);
if (!win || win.hasAttribute('data-no-maximize')) return;
if (win.classList.contains('maximized')) {
// Restore
win.classList.remove('maximized');
if (window.AudioManager) {
window.AudioManager.windowMaximize();
}
// Update icon
const maxBtnImg = win.querySelector('.max-btn img') as
HTMLImageElement;
if (maxBtnImg) {
maxBtnImg.src = '/icons/ui/maximize-inactive.png';
}
// Restore saved size/position
if (windowStates[id]) {
win.style.left = windowStates[id].left || '';
win.style.top = windowStates[id].top || '';
win.style.width = windowStates[id].width || '';
win.style.height = windowStates[id].height || '';
}
settingsManager.updateWindowSession(id, {
maximized: false
});
} else {
// Maximize
windowStates[id] = {
left: win.style.left,
top: win.style.top,
width: win.style.width,
height: win.style.height,
maximized: false,
};
win.classList.add('maximized');
if (window.AudioManager) {
window.AudioManager.windowMaximize();
}
// Update icon
const maxBtnImg = win.querySelector('.max-btn img') as
HTMLImageElement;
if (maxBtnImg) {
maxBtnImg.src = '/icons/ui/maximize-toggled-inactive.png';
}
settingsManager.updateWindowSession(id, {
maximized: true
});
}
WindowManager.focusWindow(id);
}
Window Shading
Double-click titlebar to “shade” (roll up) a window:Copy
Ask AI
function shadeWindow(id: string): void {
const win = document.getElementById(id);
if (!win) return;
const titlebar = win.querySelector('.titlebar') as HTMLElement;
if (!titlebar) return;
const isMaximized = win.classList.contains('maximized');
if (win.classList.contains('shaded')) {
// Unshade: restore height
win.classList.remove('shaded');
if (isMaximized) {
win.style.height = '';
} else if (windowStates[id]?.height) {
win.style.height = windowStates[id].height!;
}
if (window.AudioManager) {
window.AudioManager.windowShade();
}
} else {
// Shade: save height and collapse
if (!isMaximized) {
windowStates[id] = {
...windowStates[id],
height: win.style.height ||
getComputedStyle(win).height,
};
}
win.classList.add('shaded');
win.style.height = titlebar.offsetHeight + 'px';
if (window.AudioManager) {
window.AudioManager.windowShade();
}
}
}
Focus Management
Focus Modes
Two focus modes are supported:- Click-to-Focus
- Point-to-Focus
Default mode. Windows receive focus when clicked.
Copy
Ask AI
document.addEventListener('pointerdown', (e) => {
if (dragState.isDragging) return;
const target = e.target as HTMLElement;
const win = target.closest('.window, .cde-retro-modal');
if (win) {
focusWindow(win.id);
}
});
X11-style: Windows receive focus when mouse enters.
Copy
Ask AI
document.addEventListener('pointerenter', (e) => {
const mode = document.documentElement
.getAttribute('data-focus-mode');
if (mode !== 'point') return;
const target = e.target as HTMLElement;
const win = target.closest('.window, .cde-retro-modal');
if (win) {
focusWindow(win.id);
}
}, true);
Workspace Management
Virtual Desktops
WindowManager supports 4 virtual workspaces:Copy
Ask AI
function switchWorkspace(id: string): void {
if (id === currentWorkspace) return;
AudioManager.click();
const windows = document.querySelectorAll(
'.window, .cde-retro-modal'
);
// Hide windows from current workspace
windows.forEach((win) => {
const el = win as HTMLElement;
const winWorkspace = el.getAttribute('data-workspace');
if (winWorkspace === currentWorkspace) {
const isVisible =
window.getComputedStyle(el).display !== 'none';
if (isVisible) {
el.setAttribute('data-was-opened', 'true');
el.style.display = 'none';
}
}
});
currentWorkspace = id;
// Show windows from new workspace
windows.forEach((win) => {
const el = win as HTMLElement;
const winWorkspace = el.getAttribute('data-workspace');
if (winWorkspace === currentWorkspace) {
if (el.getAttribute('data-was-opened') === 'true') {
el.style.display = 'flex';
}
}
});
// Update pager UI
const pagerItems = document.querySelectorAll(
'.pager-workspace'
);
pagerItems.forEach((item) => {
if ((item as HTMLElement).dataset.workspace === id) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
Workspace Assignment
Windows are automatically assigned to the current workspace when opened:Copy
Ask AI
function showWindow(id: string): void {
const win = document.getElementById(id);
if (!win) return;
// Assign workspace on first show
if (!win.getAttribute('data-workspace')) {
win.setAttribute('data-workspace', currentWorkspace);
}
// Mark as opened
win.setAttribute('data-was-opened', 'true');
win.style.display = 'flex';
win.classList.add('window-opening');
// Center on mobile
if (isMobile()) {
centerWindow(win);
}
focusWindow(id);
AudioManager.windowOpen();
win.addEventListener(
'animationend',
() => {
win.classList.remove('window-opening');
},
{ once: true }
);
}
Mobile Considerations
Disabled Features
Certain features are disabled on mobile for better UX:Copy
Ask AI
function isMobile(): boolean {
return window.innerWidth < 768;
}
// Drag disabled on mobile
function drag(e: PointerEvent, id: string): void {
if (isMobile()) {
logger.log('[WindowManager] Drag disabled on mobile');
return;
}
// ...
}
Automatic Centering
Windows are automatically centered on mobile devices:Copy
Ask AI
function centerWindow(win: HTMLElement): void {
const winWidth = win.offsetWidth;
const winHeight = win.offsetHeight;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const TOP_BAR_HEIGHT = CONFIG.WINDOW.TOP_BAR_HEIGHT;
const PANEL_HEIGHT = isMobile() ? 65 : 85;
let left = (viewportWidth - winWidth) / 2;
let top = (viewportHeight - winHeight) / 2;
// Clamp to viewport
const minX = 0;
const maxX = Math.max(0, viewportWidth - winWidth);
const minY = TOP_BAR_HEIGHT;
const maxY = Math.max(
minY,
viewportHeight - winHeight - PANEL_HEIGHT
);
left = Math.max(minX, Math.min(left, maxX));
top = Math.max(minY, Math.min(top, maxY));
win.style.position = 'absolute';
win.style.left = `${left}px`;
win.style.top = `${top}px`;
win.style.transform = 'none';
}
Resize Handling
Windows are normalized when viewport resizes:Copy
Ask AI
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(() => {
logger.log(
'[WindowManager] Viewport resized, ' +
'normalizing positions...'
);
document.querySelectorAll('.window, .cde-retro-modal')
.forEach((win) => {
if (win instanceof HTMLElement) {
if (isMobile()) {
centerWindow(win);
} else {
normalizeWindowPosition(win);
}
}
});
}, CONFIG.TIMINGS.NORMALIZATION_DELAY);
});
Global Exposure
WindowManager functions are exposed globally for legacy compatibility:Copy
Ask AI
declare global {
interface Window {
drag: (e: PointerEvent, id: string) => void;
focusWindow: (id: string) => void;
centerWindow: (win: HTMLElement) => void;
minimizeWindow: typeof minimizeWindow;
maximizeWindow: typeof maximizeWindow;
shadeWindow: typeof shadeWindow;
WindowManager: typeof WindowManager;
}
}
window.drag = WindowManager.drag;
window.focusWindow = WindowManager.focusWindow;
window.centerWindow = WindowManager.centerWindow;
window.minimizeWindow = minimizeWindow;
window.maximizeWindow = maximizeWindow;
window.shadeWindow = shadeWindow;
window.WindowManager = WindowManager;
Best Practices
Always Register Windows
Always Register Windows
Ensure windows have proper structure and are registered:
Copy
Ask AI
<div id="myWindow" class="window">
<div id="myWindowTitlebar" class="titlebar">
<span class="titlebar-text">My Window</span>
<div class="titlebar-buttons">
<button class="min-btn"
onclick="minimizeWindow('myWindow')">
<img src="/icons/ui/shade-inactive.png" />
</button>
<button class="max-btn"
onclick="maximizeWindow('myWindow')">
<img src="/icons/ui/maximize-inactive.png" />
</button>
<button class="close-btn"
onclick="document.getElementById('myWindow').style.display='none'">
<img src="/icons/ui/close-inactive.png" />
</button>
</div>
</div>
<div class="window-content">
<!-- Content -->
</div>
</div>
Persist Window State
Persist Window State
Use
updateWindowSession after position/state changes:Copy
Ask AI
settingsManager.updateWindowSession(id, {
left: win.style.left,
top: win.style.top,
maximized: win.classList.contains('maximized'),
});
Handle Mobile Gracefully
Handle Mobile Gracefully
Check
isMobile() before drag/resize operations:Copy
Ask AI
if (isMobile()) {
centerWindow(win);
return; // Skip desktop-specific operations
}

