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.
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.
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.
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 tolocalStorageunderhousehunt: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.
| Path | What it is |
|---|---|
main.tsx | Hono app — default export for Val Town, Deno.serve locally |
server/auth.ts | Password check, signed session cookie, gate middleware |
server/files.ts | File serving: std/utils on Val Town, disk + sucrase locally |
login.html | Sign-in page |
index.html | App shell: Twind CDN, Val Town error catcher, module script |
frontend/index.jsx | Mounts setup or model depending on what's stored |
frontend/setup.jsx | First-run screen |
frontend/schema.js | Model params vs. personal fields |
frontend/storage.js | localStorage read/write |
frontend/model.js | The model: closing costs, MD assessment, cash sim, frontier |
frontend/model.jsx | The UI around it |
frontend/model_test.js | Unit 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.
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:
serveFilefromhttps://esm.town/v/std/utils/index.tsreads project files and transpiles.jsxon the way out; sucrase and the disk reads are never loaded (IS_VAL_TOWNis decided by whether modules load overhttps:)./sourceredirects to the val viaparseVal().- Client errors surface in val logs through
https://esm.town/v/std/catch.