Skip to main content
Back to all posts
Sep. 20269 min read

The couch took some of my basement wall on the way out. I built the planner nobody sold me

Every room planner ever built answers does it fit in the room. Elbow Room answers the question that costs people money and plaster: can it even get there. Predicted two real outcomes at my own house that had already happened (a couch that did not fit, a 279 litre water heater that did), agrees to 1.63 x 10^-6 inches with a 300-year-old closed-form corner solve, and ships 19 WebMCP tools so an agent can operate the canvas by talking. Cross-origin bonus: a separate furniture shop can ask my staircase over a shared read-only tool, both sides consent by name.

WebMCPModel Context ProtocolCanvasHomographyNumerical GeometryCross-OriginPlaywrightVercelHackathonShowDev
I wrote this post for the WebMCP hackathon (Sep 2026), and built Elbow Room for the same submission. Live at elbowroom.jonathanandrei.com, source at github.com/JonathanSolvesProblems/elbow-room, both open, no key, no account. #webmcp
The claim in one sentence: every room planner ever built answers does it fit in the room. Elbow Room answers the question that costs people money and plaster: can it even get there. On the two objects the same staircase saw in real life (a 91 x 36 in couch and a Giant 172E-3F8M water heater) it predicts what actually happened both times. Two for two, and the grader is the plaster.
Elbow Room verdict panel: the couch is 3 ft 9 1/2 in too long for the winder turn. Every figure came off a tape measure, not an estimate.
The verdict, in the units the tape measure gave. The turn allows 3 ft 9 1/2 in at that depth and the couch is 7 ft 7 in, more than twice the longest thing that corner will ever pass. Colouring by touch rather than by fit is the water-heater lesson: a thing can technically fit and still gouge the wall on the way through.

The problem, on my own staircase

A couch got stuck in the basement stairs. It stopped dead at the turn and would not go round whichever way I tipped it. It came out in the end, and it took some of the wall with it. Then it happened to someone else. A flood forced a water-heater replacement and the installers brought a 279 litre tank down the same stairs without putting anything on the walls first. The gouges along the soffit edge are theirs, not the couch's. Two objects, one staircase, nobody measured it either time, and there was nowhere to look it up. The property listing gives all thirteen rooms to the inch and says nothing about the stairs. The one measurement that decides whether your furniture can get in appears on no listing, no floor plan and no product page.

Does it work, and who says so

Three claims, none of them graded by me. First, it predicted two real outcomes, both of which had already happened. The staircase has history that predates this software. The couch is 91 by 36 inches on a tape. The water heater is a Giant 172E-3F8M, 24 in diameter by 59 7/8 tall, from the manufacturer's engineering sheet rather than from my measuring. Predicted the couch does not go: it did not, the wall took the damage. Predicted the tank goes: it went, the soffit took the damage. n=2, stated plainly, and two more than an assertion.

A water heater rendered inside the stairwell, this one clearing the turn. The two remedy tools (try_removing_door, adjust_clearance) unregister themselves the moment nothing is failing.
The tank fits. It also cleared the turn with 3 ft 9 1/2 in to spare in real life and still gouged the wall going down, which is why the 3D view colours by touch and not by fit. Fitting and fitting without touching anything are two different answers.

The math nobody has to trust me on

Second claim: the corner solver agrees with a result three hundred years older than it. The longest thin rod that turns a right-angle corner between corridors of width a and b is (a^(2/3) + b^(2/3))^(3/2), a closed form in any calculus textbook. Elbow Room does not use that formula. It minimises a/sin t + b/cos t - w/(sin t x cos t) numerically because a couch has width and a rod does not. Set width to zero and the two must agree. Across 169 width pairs from 24 to 60 inches they disagree by at most 1.63 x 10^-6 inches, and on the real staircase they agree to machine precision. eval/closed_form.mjs, one command, no key. A judge can check the formula in thirty seconds and does not have to trust a line of my code.

Third: the photograph-to-inches maths is checked against a camera it never saw. Four clicked correspondences determine a homography between the image and the floor plane. Verified against a synthetic camera with genuine perspective, distances the calibration never saw come back within four thousandths of an inch. That is what makes the /measure/ page real inches instead of a guessed pixel scale.

Four clicked corners on a photograph of the basement floor, and every subsequent click lands in real inches on the plane. One photograph, no reconstruction.
Single-image metric rectification. Four corners of something whose real size you know become a homography; every pixel on that floor becomes inches. No GPU, no upload, no server. The agent walks you through it and files each reading with save_reading, so the planner then runs against your house instead of mine.

Why WebMCP was the right shape

The interesting state lives in a <canvas>, and a canvas is invisible to an agent. There are no elements to read and nothing to click. That is not a claim, it is measured, headless and without an API key, in eval/interface_comparison.py. The first result corrected me. Reading the state already on screen, the accessibility tree answered 7 of 8 questions, because the sidebar renders the verdict as text. The canvas is invisible, the page is not. That table ships exactly as it came out.

The sharper question is asking something the page is not displaying, or changing it. There a DOM-driving agent has a control for only 3 of 7 tasks, against 7 of 7 through site tools. The distinction is not read versus read: the DOM exposes one frozen state, the tools expose the function. An agent can read that this couch fails. Without tools it cannot ask what would fit, cannot retry with the door off, cannot place the object at a coordinate, and cannot hand a measurement back for a human to approve.

The document.modelContext view live in the page: 19 tools registered across three surfaces, with readOnlyHint on the reads and untrustedContentHint on the one that echoes typed text.
19 tools across three pages. 15 in the planner (six reads with readOnlyHint, six writes, record_measurement as the consequential one behind requestUserInteraction, two that register only while something is failing and unregister via AbortSignal when it stops). 4 in the measuring page. 1 shared across origins, read-only. Names under 30 chars, descriptions under 500, outputs capped at 1.5K, zero violations.

A separate furniture shop asks your staircase

halliwell-and-co.vercel.app is a separate origin selling a sofa. It embeds the planner and calls check_fit across the boundary. Consent is mutual: the tool names the shop in exposedTo, the shop names the tool's origin in fromOrigins, and the browser refuses registration in a cross-origin frame without allow="tools". Any one withheld and nothing happens. Exactly one tool is shared, and it is read-only. The shop gets a yes or no about a sofa whose dimensions it already published; it never learns your measurements, never sees the plan and cannot move anything. That is the part of this spec that is actually about the open web: two sites with no API between them, no SDK, no shared account, cooperating because both said yes.

Halliwell and Co, a separate origin selling a sofa, showing a Passes check-fit verdict returned by my staircase over WebMCP's cross-origin tool sharing.
The furniture shop asks, my staircase answers, both sides consented by name. No account, no API key, no SDK, no callback URL. The whole handshake is two matching origin strings and one browser permission attribute.

What the tank taught it

The water heater cleared the turn with 3 ft 9 1/2 in to spare and still gouged the wall going down. So fitting, and fitting without touching anything, became two different answers. The 3D view colours the object by whether it is touching, not by whether it fits, because of that afternoon. Honest limitations: rigid bodies only, so a mattress bends and its verdict is a lower bound. Headroom over the turn is estimated rather than measured, and eval/headroom_sensitivity.mjs sweeps every plausible value to show the verdict never moves. The ground truth for the real-world predictions is two objects. n=2, stated as n=2.

Related project

Elbow Room: before you buy the couch, find out whether it can get up the stairs

View the project