Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

househunt

Interactive model of a Takoma Park purchase: closing costs, SBLOC bridge, double-carry during renovation, MD reassessment, and 60 months of cash / equity.

Behind a password, and your figures live in your browser, not in this repo.

Run locally

deno task dev

Reads .env for HOUSEHUNT_PASSWORD and serves http://localhost:8000. The server watches itself; client edits are picked up on reload — files are read and transpiled per request, no build step.

deno task test runs the model's unit tests. deno task check runs deno check + fmt --check + lint + the tests.

Access

One shared password in HOUSEHUNT_PASSWORD. Signing in sets a signed, HttpOnly cookie good for 30 days; the signing secret is derived from the password, so changing the password signs every device out. Set SESSION_SECRET to pin it instead. Every route except /login is gated, including the client modules.

Your numbers

Nothing personal is in the source. frontend/schema.js splits inputs in two:

  • MODEL_PARAMS — rates, county fees, insurance, reassessment and policy assumptions. Generic, stay in the repo.
  • PERSONAL_FIELDS — price, renovation, and DC rent budgets, plus cash, gifts, and SBLOC for funding the purchase. Blank in the repo, entered on first visit, saved to localStorage under househunt:inputs:v1.

First visit shows a setup screen: type the figures in, or paste a JSON blob. Your original values are in private/defaults.local.json (gitignored, and excluded from the Val Town push) — paste that on first load, then delete the file if you'd rather it not sit on disk.

After that every edit persists automatically. The left column has Copy values as JSON (for moving to another browser), Reset rates and fees, Erase and start over (back to the setup screen), and Sign out. Clearing site data loses the figures — copy them out first.

Layout

PathWhat it is
main.tsxHono app — default export for Val Town, Deno.serve locally
server/auth.tsPassword check, signed session cookie, gate middleware
server/files.tsFile serving: std/utils on Val Town, disk + sucrase locally
login.htmlSign-in page
index.htmlApp shell: Twind CDN, Val Town error catcher, module script
frontend/index.jsxMounts setup or model depending on what's stored
frontend/setup.jsxFirst-run screen
frontend/schema.jsModel params vs. personal fields
frontend/storage.jslocalStorage read/write
frontend/model.jsThe model: closing costs, MD assessment, cash sim, frontier
frontend/model.jsxThe UI around it
frontend/model_test.jsUnit tests for model.js

React and recharts load from esm.sh in the browser, pinned to react 18.2.0. Client imports keep explicit extensions (./model.jsx) because the browser resolves them as URLs.

Moving to Val Town

Push these files to a val with main.tsx as the HTTP entrypoint, then set HOUSEHUNT_PASSWORD in the val's environment variables — it is not in the code and the app returns a 500 explaining itself until it's set. .vtignore keeps .env and private/ out of the push.

There:

  • serveFile from https://esm.town/v/std/utils/index.ts reads project files and transpiles .jsx on the way out; sucrase and the disk reads are never loaded (IS_VAL_TOWN is decided by whether modules load over https:).
  • /source redirects to the val via parseVal().
  • Client errors surface in val logs through https://esm.town/v/std/catch.