37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
MODERN MAP MAKING HAS ARRIVED
Maps
Engineering
Tools for fast (and less furious) frontend development
You can think of a Felt map as a space which has certain Felt elements (drawings, pins, notes, data) that live inside it.
You can think of a Felt map as a space which has certain Felt elements (drawings, pins, notes, data) that live inside it.

And there are a bunch of operations that you can do to a Felt element: move it around, rotate it, change some properties on it, and so on.

Maybe that sounds obvious, but I actually think that it makes Felt very unlike a lot of software that is primarily forms and buttons and menus and lists of things. Felt has these objects that live inside it. (Plus, a map is at least a little bit more wiggly than all those other kinds of software.)

Felt is more like an environment or a space, with its own basic building blocks that are governed by certain ’physical rules’. You might compare it to Figma [which has vector elements], or Notion [blocks], or Excel [cells], or a text editor [characters], or to your filesystem or desktop [files and folders], or even to a Web page [the HTML elements that comprise the page].

I wanted to talk about some techniques for quickly developing and iterating on this sort of (object-y, visual, direct-manipulation) software. The following is a grab bag of general techniques which have paid dividends here at Felt.

1. Hot reload!

As I think many people in software now realize, iteration speed is all-important. You want to have immediate feedback into what your changes are doing to the software – it’s great for motivation, it lets you play and try new things, and come up with solutions on the fly that you wouldn’t otherwise think of.

Hot reload is interesting in that it’s as much an aspiration as it is a particular technology–you need different technology to make different parts of your application ‘hot-reloadable’ – so you can spend an almost unbounded amount of time improving reload coverage (edits to React component source? changes to CSS? changes to map rendering code?) and improving reload speed (a minute? 10 seconds? 500 ms? 50 ms?).

At the moment, we’re at a middle point on that spectrum–we have hot reload for React components and for our Elixir backend, but some CSS top-level theme changes require a refresh, for example. We’ll probably improve on that as our styling gets more complicated and we want to tweak it more continuously.

There’s a human element to how you should set up hot reload. Is someone in the organization making a lot of a certain kind of change? Are they blocked on waiting for the thing to reload, or / could they design better with live feedback? Maybe you should fix hot reload for that area; unblocking them will probably produce a lot of value.

It’s worth noting that hot reload shouldn’t be magic or ‘set and forget’; as a developer, you generally want to know how the hot reload system works, because it will fail or go wrong at some point. You need to know what classes of things you can do ‘hot’ and what classes of things will glitch out or require a restart. A lot of that is specific to the context of your project and what kind of changes you’re typically making. In our experience, the fast feedback loop is worth this price.

2. Fast & Typeless Compilation

We write all our frontend code in TypeScript, but the hot compilation doesn’t actually use the TypeScript compiler – it runs the code through swc and doesn’t do typechecking. That means that, first of all, it’s much, much faster than using <p-inline>tsc<p-inline>. We can hot reload in a few hundred milliseconds at most (and I think we could go faster if we pushed). We also have git pre-push checks and continuous integration and editor support. This keeps us aware of type errors, allows us to check the error log as needed, but the errors don't interrupt the critical path of compilation and development work. They appear asynchronously, on the side, from the TypeScript language server, and at push time.

Equally as important, it doesn’t feel like typechecking is a blocker for development; you don’t need to care about the type errors if you’re working on something; so you can do classic dynamically-typed programming/exploration/probing /playing around early on:

  • I want to see what’s in this object (or if it changes over time!); I’ll assign it to <p-inline>window.foo<p-inline>, and poke at it in the browser console at runtime.
  • I want a bit of context/provenance when something happens later in the program, so early on, I’ll tag some data on as an extra property <p-inline>._extraStuff<p-inline> onto this object that’s getting passed through my program already.
  • I just want to see if this thing will work at all, so I’ll ignore the null case or error case for now (or I won’t bother to give the right type, or whatever)

You can fix up the types when you’re ready to commit/push–or when you run into a roadblock and you’re like, “okay, maybe I’ve missed something,” and you consult the type error log. The type errors are useful; don’t get me wrong! They just shouldn’t necessarily hold you up all the time or distort the way that you program.

This technique comes out of a property of TypeScript that I’ve always found interesting. Historically, the point of a type system was to help the compiler to generate code (e.g. is this variable 8 bits or 32 bits in memory?). TypeScript, in contrast, has nothing to do with how a JavaScript VM executes your code; it erases all the type information and just emits plain JavaScript in the end. TypeScript is basically a sort of amped-up linter that is there to help you, not to help the runtime.

3. Development Server Over the Internet

We’ve started using ngrok, which is a service that lets me take the https://localhost:4000 development server on my laptop and put it right up on the Internet at a URL that looks like https://21e7-19-35-39-115.ngrok.io. If you want to pair program on something with someone, you can just send them that ngrok URL, and they’ll be connected, live, to the server on your computer.

Hot reload works here in the exact same way that it does on your local computer. This is really powerful–you edit the source file on your computer, and it immediately hot reloads in your browser and in the browser of whoever you’re pairing with–now you tweak a shade of blue or an animation, and they can play with it on their end, in their own way (maybe they are screen-sharing their screen so you can see what they see). The traditional approach of sharing your screen is much inferior to this, I think; it doesn’t have that ability of independent interaction; it drains the other person of agency. They can’t look around and notice other things that are broken.

I also think that the over-the-Internet hot reload creates this feeling of connection, that you’re both touching this live wire, and there’s something really compelling about that. Not only does screen sharing dilute or prevent interaction, it doesn’t seem to create this same feeling.

There are also the advantages of code consistency and data consistency: if we’re pairing about some problem, and we’re both using my development server, and we refresh the page, we can be pretty sure that 1. we’re looking at the exact same map data and 2. we’re using the exact same version of the application. We’ll talk later about import and export as another route to data consistency.

4. 'Observability'

I talked about the desire for immediate feedback earlier. Immediate feedback doesn’t just mean that the app reloads really fast. Immediate feedback means you should be able to see the thing that you care about immediately. The thing that you care about is not necessarily just ‘the app interface’ or ‘whether it works’; in fact, while you’re programming, you often want to see some particular data on the inside of your program, or you want to see how some performance metric is changing, etc.

Frontend programmers probably don’t do enough of this yet. React developer tools provide some of this observability, but it’s too clumsy, in my opinion (you’re clicking around this giant React component tree and squinting at blobs of JSON with latitudes and longitudes in them, instead of seeing the graphical Felt elements and the places on the map).

Ideally, we’d have an inspector that’s as powerful as the Web inspector, but it operates on the domain concepts of our particular application (instead of DOM elements, Felt elements; instead of a component tree, an actual geographical map?).

One concrete thing that we did toward this Felt-inspector dream: we exposed some variables, felt.$ and felt.$0, in the browser console.

You might know about $ and $0 in the browser console: these are variables that give you programmatic access to the DOM element that you've selected in the Web inspector.


It's really nice to be able to point at something in the DOM, then program with it (ask questions about its properties, mutate it, etc). I wanted something similar for our Felt elements – you should be able to point at a Felt element and then have interactive access to it from the JavaScript console.


It's been really useful to debug changes we make to the drawing system in Felt. These are the kinds of tools that we need–tools that provide us the ability to inspect an element, see its raw data (and if that matches up with what our frontend is showing), perturb it in various ways.

It also hints at the power of the browser console as a tool for developing applications, which I want to talk a little more about–

5. Browser Console

I think that a tragedy of modern, highly-integrated, rich Web applications is that they destroy a lot of the utility of the browser JavaScript console and the Web inspector. These are really powerful tools!

  • Your CSS styles all live in classes with obfuscated names (generated by some CSS-in-JS system), the CSS inspector isn’t designed to deal with that / even if you can tweak them in the inspector, you’ve lost most of the structure that you wrote in the original code
  • Your applications’ data is sealed in variables inside modules or closures, not available from the ordinary global JS console. you have to set a breakpoint to even get to a point where you can see what your application is doing
  • Transpiled variable names are mangled and you can’t even read them unless you also turn off source maps
  • Stack traces don’t work properly over async-await, or across a chain of React calls, or whatever, so you’re using console.log and breakpoints to reverse-engineer control flow 

Anyway, the browser console is an excellent way to get more visibility into what the application is doing, and to do lightweight programming and scripting and analysis (without needing to edit the app itself).


It’s a nice JS environment which already has a lot of the context (all the normal libraries that we use in Felt can be imported, there’s a map open in the browser that you can take context from and draw onto, etc). Much easier than trying something in node at the command line, and much easier than actually developing inside the actual app and having to wire it up with the UI and with React and everything.

Also – as you can see above, the browser console supports CSS styles! It isn’t only the plain text that you might expect.

Something you might not know is that you can actually draw arbitrary images in the browser console. You can render into an HTML5 canvas, convert that canvas to a data URI, and then set that data URI as a CSS <p-inline>background-image<p-inline> in <p-inline>console.log<p-inline>. This is really useful for us (and, really, for any application where there is a visual-spatial element and you want to actually see what you’re developing).

Sometimes I’ve developed things purely as imperative browser commands first, and I get the logic right in that self-contained context (where I can scroll up and compare to past results!), and then I add it to where it belongs in the Felt app.

For example, I pan and zoom the map to the place where I want to test something, maybe draw some stuff in the app, then open up the console and iterate on some command that uses that location and/or the elements on the map.

The browser console also provides a nice interface to expose debug functionality without you having to make a proper HTML UI. You can make global functions to turn on debugging modes, to reveal different pieces of data, and so on, which leads us to–

6. Import and Export Data

Felt has a pretty complicated document model! Maps can be in all kinds of different states with all kinds of different objects with different properties/scale/location/zoom.

If I run into a problem, or I have some half-developed feature, and I want you to check it out, I want to be able to send you a copy of the map that I’m talking about, so you can look at it on your computer and try different things.

One solution is what I discussed earlier: be on the same server with the same backend database (which means either the production server, a development server deployed off a GitHub branch, or someone’s local development server over ngrok).

When that’s impractical, though, it’s also very useful to be able to quickly import and export objects; you can paste them into Slack or a GitHub issue comment or whatever you use (it’s also useful for debugging to be able to see what the ground-truth data looks like).

So we added browser console commands that do this (it only took a few lines of code, purely on the frontend): we can now dump Felt objects as a chunk of JavaScript, and we can paste that chunk into any other Felt map’s browser console to import those objects.

You want to have immediate feedback into what your changes are doing to the software – it’s great for motivation, it lets you play, try new things, and come up with solutions on the fly that you wouldn’t otherwise think of.

7. Build the Frontend the Backend in Parallel

Build all your views and interactions and transitions for real, but have them backed by state that lives purely on the client at first (in React local state, or in whatever state manager that you use – we use jotai).Then, later, when you have your backend working, switch to using your backend as the canonical state instead.

Sometimes it really is that simple! It can be, like, a five-line change. (Sometimes it takes a bit more work to migrate to using server-side state: we’ve had cases where the server wants to asynchronously change the data over time for security/resource use/postprocessing reasons, for example. We ran into this with image uploading: the client should render the uploaded image immediately, so it feels fast, but the server may replace it later as it postprocesses the image.)

There is some risk to this – that your frontend won’t quite be aligned with your backend – but it has worked well for us. I think you need the right framework and frontend data model to allow you to cleanly swap between frontend and backend state. I think that there is a huge psychological advantage to working this way, where you never feel blocked on anything on the frontend, and it doesn’t feel like a chore as you’re working on the interface because you have to change a bunch of points in the backend at the same time.

8. Start with the Prototype

This applies mostly if you have a design prototype from something like Figma or Sketch (or even a hand-drawn prototype). When you start developing that view for real, take that prototype, turn it into an image (a PNG file, etc.), and put that image right in the app (probably as a CSS <p-inline>background-image<p-inline> for the view that you’re working on). (You might set the CSS <p-inline>opacity<p-inline> for your real view to <p-inline>0.5 or something<p-inline>, so you can see through it to the prototype underneath and check how well they align.)

Then your development process is a process of continuously ‘fixing’ your real view, tuning it to match the prototype underneath, rater than trying to make it from scratch. You always have something working the whole time. You’re taking more and more of that dead static mock and tracing bits out and bringing them to life. A problem with this is that you need to have a good handle on your abstractions and primitives to do it well. It’s easy to just spew out hard-coded pixel values that make it match, but then you’re obscuring the structure of the design and making it brittle and hard to understand and change. You also don’t know how it’ll respond to resizes and theme changes and animation and so on.

Constant and Fast Iteration at Felt

At Felt, we believe the best products are built with constant and fast iteration. We go the extra mile at every point, whether it is cartography, frontend, infrastructure, to lower the distance between having an idea and seeing it live on screen. As a company that builds mapping products on the web, the frontend iteration cycles are especially important. In this post, I tried to list some of the less obvious ways to squeeze out some feedback loops. This work is exciting, but we are nowhere near done! We are always looking for amazing people to join.

I think that there is a huge psychological advantage to working this way, where you never feel blocked on anything on the frontend, and it doesn’t feel like a chore as you’re working on the interface because you have to change a bunch of points in the backend at the same time.
Bio
LinkedIn
More articles

Cartography Tips for Designing Web Maps

Search, filter, select — data discovery done right

Getting the most out of Felt’s new Workspaces

Teaching Spatial Analysis with Felt

Powerful new admin tools and a streamlined billing experience

Three key UI upgrades in Felt 2.0