volta-wm
v0.7.0
Published
Vanilla ESM browser window manager — float / snap / popout / persist / cross-window sync card shell. Domain-agnostic; composes with OpaDeck.
Downloads
1,694
Maintainers
Readme
volta-wm
A vanilla ESM browser window manager — arrange any live content as cards you can float, snap, maximize, minimize, pop out to a real OS window, and keep in sync across windows, with persisted layouts. No framework dependency.
Domain-agnostic by design. The WM keys on opaque item ids and renders whatever you draw into a provided mount element — so a "card" can be an iframe, a terminal, a chart, or an OpaDeck operation panel. volta-wm = how cards are arranged; OpaDeck = what runs inside them.
Status
v0.1.0 — extracted from the volta-platform console and now consumed by it in
production. Full WM: grid drag-reorder, float, 6-zone snap (NW/NE/SW/SE/L/R) plus
U/D halves, maximize, minimize→taskbar, seamless mode, popout to a real OS window,
cross-window sync, persisted layouts, keyboard move/rescue, Ctrl+ switcher.
Headless unit tests (node --test`). See #1 EPIC.
Why
Homelab launchers (Homer/Dashy/Homarr) stop at static tile grids that open apps in a new tab. IDE-docking libs (golden-layout/dockview) are heavy and tab/pane oriented. volta-wm fills the gap: a lightweight, embeddable window-manager app shell with live-embed + popout + cross-window sync + named layouts.
Install
npm i volta-wm # published on npm (MIT, public)No build step — raw ESM, zero runtime deps. The package exports both the entry
and the stylesheet:
import { createDeck, createLayoutStore } from 'volta-wm';
import 'volta-wm/styles.css';Public API
const deck = createDeck(el, {
items, // WindowItem[]: { id, title, capabilities?:{seamless}, statusColor?, taskbarBadge?, meta? }
renderItem, // (item) => Node | { el, destroy } grid card body
renderContent, // (item, { mountEl, getState, subscribe }) => cleanup? window/popout body (drawn once)
gridFilter, // (item) => boolean view-only grid filter (search box); cardOrder & windows unaffected
storageKey, // localStorage key for persisted layout (default 'wm_layout')
navbarHeight, // px reserved at top so maximize/snap fit below a fixed navbar (default 0)
popout, // { url(item), windowName?(item), features? }
channelName, // BroadcastChannel name for cross-window sync (default 'wm')
onMessage, // (msg) => void consumer hook for broadcast messages
store, // optional pre-made createLayoutStore (share across components)
});
// → { store, broadcast, update(items), refresh(), destroy() }renderContent draws into mountEl once; layout changes only mutate styles
imperatively, so iframes / terminals / live sessions are never torn down. Both
renderItem and renderContent may return a cleanup fn (e.g. unmount a React or
OpaDeck root) that runs when the card/window goes away.
Also exported: createLayoutStore, SNAP_RECTS, computeSnapRect,
createWindow, createFloatingLayer, createWindowSwitcher, makeSortable,
createBroadcastSync, mountPopout, h, clear.
Develop
npm test # node --test (headless unit tests, no deps)
npm run serve # static server → open http://127.0.0.1:8078/Try the example
npm run serve, then open http://127.0.0.1:8078/examples/basic/. Three dummy
cards (Notes / Clock / Web-iframe) — no framework, just the public API.
Verification checklist (browser, issue #7)
- [ ] Grid renders; drag a card onto another to reorder
- [ ] Float a card; drag its titlebar to move
- [ ] Resize from the bottom-right handle
- [ ] Hover □ → snap popover; try all 6 zones (NW/NE/SW/SE/L/R)
- [ ] Maximize (double-click titlebar) / restore
- [ ] Minimize → appears in taskbar; click taskbar to restore
- [ ] Clock card seamless toggle (🔲) → borderless; taskbar shows
[live] - [ ] **Ctrl+
** cycles windows (hold Ctrl, tap, release to focus) — Alt-Tab is grabbed by the OS - [ ] Ctrl+Alt+Arrows move the focused window (Shift = larger steps)
- [ ] Ctrl+Alt+R (or Home) rescues the focused window: unmaximize/unsnap + recenter on-screen
- [ ] Pop out (⧉) → opens a real OS window with the same content; close it → card docks back to grid (BroadcastChannel sync)
- [ ] Type in Notes, then float/snap/maximize → text survives (content is never rebuilt)
- [ ] Reload → layout (order/positions/modes) persists
Tiling layout (preview)
A separate, non-overlapping layout engine (tmux / i3 style) alongside the floating deck — recursive split panes with draggable dividers, keyboard split/focus/close, and a persistable split tree.
import { createTilingLayout } from 'volta-wm';
let n = 1;
const tiles = createTilingLayout(el, {
firstId: 'pane-1',
renderLeaf: (id, { mountEl }) => { mountEl.innerHTML = `<iframe src="/p/${id}">`; },
createPane: () => `pane-${++n}`, // mint an id when a pane is split
onChange: (tree) => save(tree), // persist the split tree
});Keyboard (when the container is focused): Ctrl+Alt+\ split side-by-side,
Ctrl+Alt+- split stacked, Ctrl+Alt+Arrows move focus, Ctrl+Alt+x close.
The split-tree model + geometry are pure and exported (splitLeaf, removeLeaf,
setRatio, computeRects, neighbor, leaves, …) — fully unit-tested.
Try it: npm run serve → http://127.0.0.1:8078/examples/tiling/.
Preview API: not yet wired into the volta-platform console (the console uses the floating deck). Drop-into-snap-zone entry and console integration are tracked in #13.
Theming (styling & accent contract)
styles.css exposes the WM chrome and is themed entirely through CSS custom
properties on :root (override them anywhere up the cascade — e.g. a .dark
scope — to retheme without touching the package):
| Variable | Default | What it colors |
|----------|---------|----------------|
| --wm-accent | #2563eb | Window border + titlebar background (the primary accent) |
| --wm-accent-fg | #ffffff | Titlebar text/icons on the accent |
| --wm-window-bg | #ffffff | Window body background |
| --wm-window-fg | #111827 | Window body text |
| --wm-border | #d1d5db | Resize handle + subtle borders |
| --wm-taskbar-bg | #111827 | Taskbar background |
| --wm-z-base | 1000 | Base z-index for floating windows (focus adds on top) |
/* example: dark theme + brand accent */
.dark {
--wm-accent: #7c3aed;
--wm-window-bg: #0d1117;
--wm-window-fg: #e6edf3;
--wm-taskbar-bg: #161b22;
}Contract notes:
- The accent drives both the window border and the titlebar; pick one brand color and the chrome stays coherent.
- Seamless windows intentionally drop all chrome (no border/shadow) so embedded apps render edge-to-edge — accent variables don't apply in that mode.
- Class names (
.wm-window,.wm-titlebar,.wm-taskbar,.wm-btn, …) are part of the public surface; you may add rules targeting them, but the variables above are the supported retheming path.
MIT © opaopa6969
