Somehow, I’ve managed to avoid working on anything related to the web for almost my entire career, except for a stint at a web-based startup that, although it didn’t fail (it still exists 25+ years later), it didn’t really take off either. So I thought I’d take the opportunity during retirement to get caught up again on web-related technology.
TL;DR: writing software for the web has come a long ways since I last did it. It is really quite pleasant now!
Choice of Project
Even when my goal is just to become more familiar with a technology, I have to do it via a hands-on coding project, or I retain nothing. I decided to turn a for-fun standalone desktop app that I had written years ago into a web app.
The original app was written in Java for the desktop, and was tangentially related to my last job. It crudely simulated what happens at the surface when you pump oil and gas out of an underground reservoir; the ground above the reservoir subsides because the rocks within the reservoir are depleted of fluids and so can no longer support the weight of the rocks above. Although the math for this has been pretty much worked out (see here, here and here), I wanted to come at it using a very much simpler toy approach.
I have an ongoing general interest concerning how simple dynamical simulations can ‘solve’ moderately-complicated differential equations just by having discrete nodes or masses respond dynamically to a simplified model of the forces acting on them, and eventually coming to equilibrium. The equilibrium state represents an approximate solution to the differential equations and boundary conditions describing the system. This interests me in no small part because it lends itself to animation while the solution is being calculated - it is fun to watch how it proceeds (or goes wrong) on the way to the solution.
The subsidence app simulates a 2D matrix of point masses that depicts a cross-sectional view of a reservoir, the rock around it, and the ground above it. The nodes should be considered to be little bits of rock, and they are connected to each other with simple springs that stand in for the forces (pressure) that each bit of rock feels from surrounding bits of rock. Using a simple Hooke’s Law linear spring force is a gross simplification of how rock really behaves, but it can perhaps be defended as a first order linear approximation. The masses in the simulation are subject to gravity, so the springs compress and the masses bounce around a bit until they come to an equilibrium, balancing the tension in the springs against themselves and against gravity. Compaction in the reservoir is crudely simulated just by suddenly reducing the spring constants and at-rest lengths of the springs within the reservoir, which causes subsidence at the surface.

The Java Subsidence Desktop App
The Python Version
In an earlier project designed to refresh my memory on Python, I had ported this same app to Python using tkinter for the UI. Here is a screenshot of that Python/tkinter app:
The Python Subsidence Desktop App
Note that I did not attempt 100% identical functionality in the ported version, there are differences (improvements I hope) in how the UI is arranged.
I was a bit disappointed with the performance of the app rewritten in Python/tkinter. It was many times slower than the Java version. It is certainly possible that I did something stupid in the Python version to slow it way down, but I went back over the code twice to look for bottlenecks, and I couldn’t find any. The speed difference seemed to be due to how fast the Java version could redraw the canvas compared to Python/tkinter.
The JavaScript/TypeScript Web App
Jumping to the result: after several iterations and refinements as I learned more, the port of the Subsidence app to be a web app now looks like this:
The TypeScript Subsidence Web App
Being as though this is now a web app, you can play with it too if you like, it is here. Note that this app was designed for a full screen, it will not fit well on a phone display. You can click the help icon on the upper right for a description and usage tips, and the parameters have mouse-over tooltips.
After some number of iterations, the simulation comes to equilibrium, so the app automatically stops animating when the masses quit moving significantly. The UI generally responds immediately to changes of parameters and restarts the simulation, but the Reset button will restart the simulation manually.
Implementation Details for the Web App
Even though most modern web apps split the work between a front-end, usually written in JavaScript or TypeScript (along with HTML and CSS of course), and a back-end perhaps written in Python or Java, I decided that this app was simple enough that I could just do a purely client-side implementation. That is, I could have split the app to calculate the dynamics on the server side and just send over the positions of the masses (or even a rendered image of the current state of the simulation) to the web browser client for display, but I decided not to go this route. So this project is not a full-stack project, but is purely a client-side exercise. Plus, a back-end almost always connects to a database of some kind, and this app had no need for that.
I knew that the code to handle the UI would be the biggest part of the app. I wanted a constantly-updating canvas to render and animate the simulation, plus a panel of controls to adjust parameters. I settled on using the React framework for the UI. Another possibility was Angular, but I read someplace that React has less of a learning curve so I went with that.
The code I did to handle the dynamics (that perhaps could have been put on a back-end) is simple, and is little-changed from the original Java version. At each iteration it just does a vector sum of the forces acting on each mass from the springs, from gravity, and from viscosity (a function of the mass’s velocity), and accelerates the mass accordingly. The viscosity force is not really part of the physics of the situation, but is necessary to make the system converge to a final solution. Without it the simulation would likely oscillate forever after any change. The core of the simulation utilizes the half step method when updating, for increased stability.
Early on in rewriting the app from Java into JavaScript/React, I found myself often running afoul of JavaScript’s very, shall we say, lenient view of type safety. I’m not knocking JavaScript, it is just that I personally had become accustomed over the years to getting help from the compiler/interpreter in pointing out my mistakes with types, and I was having a hard time without that help. Too many failures at run-time. So I switched to TypeScript, which is a more type-safe extension of JavaScript. TypeScript is implemented as a transpiler - it compiles TypeScript code into JavaScript code. You never have to even look at the generated JavaScript code (good thing, it is nearly impossible to read). The browser just executes the transpiled JavaScript code, never knowing that it was originally written in TypeScript.
By the way, my AI coding assistant did a really good job of porting what I had done in JavaScript to TypeScript. And it was especially helpful in getting me through some issues I had concerning React’s state management. It even provided some useful code review comments along the way, along with a few not-so-useful suggestions. While I perhaps wouldn’t have found AI to be as helpful in a coding language/environment that I already knew well (e.g. see this article), I found AI to be incredibly helpful in an area that I am just learning.
I used VS Code while building this web app, which provided me with an environment that I found to be quite nice. Since VS Code comes from Microsoft, it integrates very well with GitHub and GitHub Copilot to meet my source control and some of my AI development needs. I did find however that the AI in Warp was often better at understanding and modifying my source code. Being able to debug a TypeScript app running in a browser just as easily as I can debug a Java desktop app was an eye-opener for me - I could’ve really used that 25 years ago…
After several iterations and tweaks on the app as I learned more about how to get things done in TypeScript and in React, it developed to the point where I could amuse myself by playing with the parameters. Note that it is pretty easy to get the simulation to break down because the masses and springs ‘fold over’ themselves, or the masses fly off to infinity because of numerical instabilities. The Cross Springs option is essential to stabilizing the simulation - it fails quickly if you check that off. I probably could have added some code to prevent these failure modes (and may do so in a future project), but I wanted to explore the regimes of settings that stabilize or destabilize the simulation.
TypeScript Versus Python and Java
I found that the TypeScript version of the app also performed many times faster than the Python/tkinter version. In fact it was also faster than the original Java version, though not by as much. I could readily simulate larger numbers of nodes with the web app version. I have to admit that this surprised me - I didn’t expect the web app to be so performant.
The TypeScript implementation was fast enough that you can see the equivalent of ‘seismic waves’ propagating through the matrix of masses after pressing the Compress or Un-Compress buttons. You can exaggerate this effect by reducing the Viscosity. This of course is completely wrong for actual subsidence, which is (usually) a very gradual process.
Summary
I am hesitant to make the source code for the Subsidence web app public, just because it was first and foremost a learning exercise for me, so I’m not overly proud of the code. In fact, now that I know more about how React works, I might have architected things a bit differently. About the overall physics and realism of the app: I make no claims that this is in any way accurate. In fact the whole approach might just be flat-out wrong for several reasons, and so it may have no relationship at all to real-life subsidence. But it amused me for a while, and taught me a bit about TypeScript and React, which was the goal.
As often happens, this project spawned at least one offshoot project so far, which I plan to post about in the future. This may turn into a series of posts as I take on more web coding projects.