Desktop shell for the web

Every website is a window.
Every window sings in the choir.

Choir opens the sites you live in as real OS windows and gives them window-manager superpowers: magnetic snapping, locked groups that move and resize as one, and edge docking that arranges the whole group.

oasis.farm
OA
app.homeschool.fit
AP
rune.design
RU

Three sites, three real OS windows.

Platforms
macOS and Windows
Runtime
Electron 43
License
MIT
Price
Free

Features

Window management, for the web apps you already live in

Not tabs pretending to be windows. Real windows that know about each other.

Every site is a real window

A website opens as its own OS window, with a Choir titlebar instead of a browser. No tab strip, no address bar, no chrome you did not ask for.

The titlebar mounts into a closed shadow root, so the page underneath cannot restyle it, read it, or fight it for the top 32 pixels.

Windows that snap and stay snapped

Drag a window near another and it pulls flush, sharing a border. Drag it near a screen edge and it pins there.

Adjacency is one signed separation per axis, and the test includes zero, so a pair that is already flush still reads as adjacent. That single case is what makes a snap hold instead of releasing on contact.

Groups derived from geometry

Push two windows together and they associate. Pull one away and they do not. Membership matches what is actually on screen, however the windows got there.

A group is a connected component of the "is adjacent to" relation, recomputed from current bounds. It is a pure function of the layout, so it cannot drift the way drag-history bookkeeping did.

Lock a group, move it as one

One titlebar drag carries every member of the group, at any size, in any arrangement.

The drag path is a pure function from world plus intent to a set of mutations: exactly one move per window per frame, at absolute positions taken from a single drag-start snapshot. Resolving the same frame twice is a no-op rather than a double-apply.

Resize the group like a window

Grab the outside edge of a locked group and every member follows. Grab a seam between two members and only the seam moves.

Outline edges map every member through the affine transform carrying the old outline onto the new one, which is equal growth on the undivided axis and proportional on the divided one. A row of five and a 2x2 grid fall out of the same rule.

Dock a group, it arranges itself

Drag a group to a screen edge and the whole group fills that region. Two windows on the left half become two stacked quarters. Three become thirds.

The split runs along the longer axis of the region, so the left half of a display divides into rows rather than slivers. Undock and the previous layout comes back.

A floating bar for the whole set

Type a URL, get a window. Grouped windows draw as one cluster. Focus, minimize and close anything from one strip.

The bar is its own Choir-owned window rather than an overlay, so it survives whatever the sites underneath it do.

Guides before you commit

Resize snapping previews with amber guide lines and commits on release, so an edge lands where you saw it land.

Resize is measured against the display work area, so a window cannot be dragged under the macOS menu bar or the Windows taskbar.

How it works

Snapping that holds, because the model is stated

Most window snapping is a pile of event handlers that mostly agree. Choir writes the rule down first, then makes the code the only thing that could satisfy it.

  1. 1

    The window model is a library

    Geometry, adjacency and group resolution live in a package with no Electron imports at all. That is what makes them testable, and they are tested.

  2. 2

    Membership is derived, never bookkept

    A group is a connected component of the "is adjacent to" relation over current bounds. There is no drag history to fall out of sync with the screen.

  3. 3

    A frame resolves to a set

    One pure function turns the world plus your drag into a set of absolute moves. A window physically cannot be moved twice in a frame, so the classic double-apply jitter has nowhere to live.

window-core/src/snap.ts
// One quantity per axis. Positive is a gap;
// negative is overlap, by exactly that much.

sepX(A,B) = max(A.left - B.right, B.left - A.right)
sepY(A,B) = max(A.top - B.bottom, B.top - A.bottom)

// Four sides, not twelve relations.

vertical seam    |sepX| <= tol && sepY < -minOverlap
horizontal seam  |sepY| <= tol && sepX < -minOverlap

The tolerance test includes zero, which is the state snapping produces. Requiring a positive gap is what makes most implementations unable to see a pair that is already flush, and it is the root of the flakiness you have felt everywhere else.

Prior art

The closest thing to Choir is HadoukenIO, the container OpenFin open-sourced, along with Finsemble and Cosaic. Those were built for trading desks: six monitors, a dozen vendor apps, layouts that have to come back exactly. Choir takes the same marionette docking model and aims it at everyone who is not a trading desk, but who still has eight browser tabs doing the job of eight applications.

Roadmap

What is coming next

Choir is in active development and honest about where the edges are. These are the next pieces, not a wish list.

Saveable workspaces

Name a layout, close everything, and get it back on launch with every window in position and every group intact.

Launchable widgets

Small first-party panels that live in the bar and dock like any other window, with preferred aspect ratios feeding the arrangement.

Favicons in the bar

Windows are identified by two letters of the hostname today. Real icons are next.

A CLI and an API

Open a window, restore a workspace, or drive a layout from a script.

Download

Not released yet

Choir is at 0.0.8 and building toward a first public release. The download links below go live the moment there is something honest to put behind them.

  • macOSApple siliconBuilding
  • macOSIntelBuilding
  • WindowsWindows 10 and 11Building

Currently at 0.0.8. Progress is in the repo, commit by commit.