trevato/beam

Public: anyone can read this; the crew can change it.

A structural beam calculator for engineers: a simply-supported or cantilever beam with a span, point and distributed loads, and a section (rectangular or an I-beam from a small table); it draws shear, moment and deflection diagrams as SVG, reports maxima with units (SI and imperial, switchable), keeps every case in a shareable URL, and prints cleanly. Results must match hand calculations in the tests.

Running eda5fc0 https://beam--forge-trevato-apps.open-platform.sh in production 21d 16h

README.md

beam

A structural beam calculator. A beam on up to four supports — simply supported, cantilever, propped or continuous — with point, distributed and applied-moment loads, a settlement per support, and a section (a rectangle you type, or an I-beam from a small table): it draws shear, moment and deflection as SVG, reports the maxima with units in SI or imperial, keeps the whole case in a shareable URL, imports and exports CSV and JSON, and prints cleanly.

PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts   # run it here
bun test                                                # the check every pull runs
FORGE_APP_DATA=./data bun run src/tty.ts                # the terminal interface

No dependencies: Bun, bun:sqlite, and hand-written SVG. No client-side JavaScript at all — the form's fields are the query parameters, so every button is a plain submit and the address bar is always the case.

How to use it

Open /, set the span, left end, section and modulus, place the supports, add loads, press Calculate. + Support drops one into the middle of the widest gap, so one press turns a single span into a two-span continuous beam; each support takes a settlement in mm (in), positive downwards. + Applied moment adds a couple, positive clockwise. The URL then holds the whole case; copy it from the address bar or the "shareable link" box and it reproduces exactly these numbers. SI and Imperial rewrite the case into the other system.

A link states its own unit system in u=si or u=imp, and every number in it is read in that system — never guessed, never converted. Editing u by hand therefore re-reads the numbers rather than converting them: L=6 with u=imp is a six-foot beam, not a six-metre one. The app cannot tell which was meant, so it says which it used: the unit system is printed under the title, on the page and in print, and it is the first entry under Known limitations on the page and in case.json. Use the SI / Imperial buttons to change units. Print with your browser's print command: the form, links and badges drop out and the diagrams, results and assumptions stay, with the link printed under the title.

  • /case.json?<case> — the case, the results and every diagram point.
  • /case.csv?<case> — the diagram points, under a comment block recording the case.
  • /loads.csv?<case> — just the loads, in the shape the importer reads.
  • The import box takes either a case.json or a CSV loads table

(type,magnitude,start,end; point,25,2,, udl,12,0,6 and moment,20,3,). Supports come from the case, or from a case.json.

Saved cases (name + link) are the only stored state; they live in SQLite under FORGE_APP_DATA, so production and each preview keep their own.

What it computes

Macaulay's method (singularity functions), integrated in closed form, so the answers are exact rather than sampled:

V(x) = dM/dx EI·θ = ∫M dx EI·y = ∫∫M dx

A continuous beam is statically indeterminate, so the reactions are not known before the deflection is. They are carried as unknowns instead: V, M, θ and y are all linear in [R₁ … Rₙ, M₀, C₁, C₂], and the conditions that close the problem — V(L⁺) = 0, M(L⁺) = 0, y(xᵢ) = −δᵢ at every support, and θ(0) = 0 at a built-in end — are one small linear system, solved by Gaussian elimination with partial pivoting in src/linear.ts. That is the force method, the same compatibility the three-moment equation is a hand form of; the tests check the answers against it. It collapses to y(0) = y(L) = 0 for a simply supported beam and y(0) = θ(0) = 0 for a cantilever. Maxima are located exactly: shear at the breakpoints, moment at the breakpoints and at the zeros of V, deflection at the zero of θ found by bisection. src/beam.test.ts checks every result against a closed-form beam table — wL²/8, 5wL⁴/384EI, PL³/3EI, Pab/L and the rest — in both SI and imperial, and the continuous cases against hand calculations written out in the test: two equal spans (M = −wL²/8, R = 3wL/8, 10wL/8), two unequal spans by the three-moment equation, three equal spans (−wL²/10, 0.4wL, 1.1wL), a propped cantilever, and a middle support that settles by Δ on an unloaded beam (R_B = −6EIΔ/L³, M_B = 3EIΔ/L²). src/linear.test.ts exercises the solver on its own: systems whose answer comes from Cramer's rule by hand, and the singular ones — a dependent row, an unconstrained unknown, a ragged matrix, a non-finite entry — where it has to return null so the page can say the beam is not held.

Sign convention: loads positive downwards, applied moments positive clockwise, settlement positive downwards, x from the left end, shear positive upwards on the left of a cut, moment positive sagging, deflection reported as a magnitude with its direction. A reaction can come out negative, which means that support holds the beam down.

Support positions are part of the case and do not move when you change the span: a support past the new end is brought back onto it, and one short of the end leaves an overhang. The page says where the supports stand, what spans they make and what overhangs, under the title.

Assumptions are listed on the page, and in the JSON export. In short: Euler–Bernoulli, prismatic elastic section, strong-axis bending, no self-weight, no buckling or torsion check, supports that hold the beam at their own level (settlement included) with no rotational spring and no lift-off. Shape table values are published section properties (IPE from the European tables, W from the AISC Shapes Database) and σ = M/S uses the tabulated elastic modulus, so it is the manual's number.

Real, not simulated: every number on the page comes from the solver in src/beam.ts. Nothing is stubbed.

Shape

  • src/units.ts — unit systems and the exact conversion factors.
  • src/sections.ts — the section table and rectangle properties.
  • src/linear.ts — a small dense linear system, Gauss with partial pivoting.
  • src/beam.ts — the solver: SI in, plain data out.
  • src/case.ts — a case, and the URL it is read from and written to.
  • src/results.ts — one case in, the numbers a person reads out.
  • src/diagram.ts — the SVG.
  • src/exchange.ts — JSON and CSV, in and out.
  • src/db.ts — saved cases.
  • src/pages/beam.ts — the page.
  • src/server.ts — routes and the security floor.
  • src/tty.ts — the same calculator in a terminal.

Every response carries the security floor in src/server.ts: Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and Strict-Transport-Security when FORGE_APP_URL is an https address (a local http run is not pinned to https, which would lock it out of itself).

Untrusted text never becomes a case directly: an imported file is turned into query parameters and handed to the same parseCase a hand-edited URL goes through, which clamps, drops and reports anything out of range.

It also has a terminal: the exact command is in FORGE_APP_SSH when the app runs (the app is the login name, the forge is the host), and the page prints that command verbatim. It opens src/tty.ts on a real pty over the same data — edit the beam, switch units, read the maxima, ASCII diagrams, save and open cases, and the shareable link for what is on screen.

The forge sets PORT, FORGE_APP_DATA, FORGE_APP_ENV (production or preview), FORGE_APP_URL, FORGE_APP_SSH and FORGE_APP_COMMIT when it starts the app. forge.json says how.

↑↓ navigate ↵ open esc close

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs