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.
- src
- .gitignore 37 B · ~11 tok
- AGENTS.md 2.2 KB · ~560 tok
- forge.json 111 B · ~37 tok
- package.json 141 B · ~47 tok
- README.md 7.4 KB · ~1.9k tok
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.jsonor 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.