Data & event handling in the app

As we continue working on the app, one of the decisions in building Thymer is how the data will flow through the app. Let’s look at some more technical details about all the event and data handling considerations.

Server communication

Because Thymer will work like an editor of sorts, it needs to feel like one you might have on your laptop, even though it can run in the browser. That means interactions with the app should be pretty much instant from the perspective of the user. When a user adds or removes text, we don’t have time to ask the server to do the rendering for us. Even if the server would be fast, when every millisecond to render something on the screen counts, that pesky speed of light limit alone would make a round-trip to the server too slow.

That means we need to do all the rendering in the app itself, and send data between the client and server in the background. All the rendering we do is optimistic, meaning it assumes that at some point we’ll be able to communicate it to the server, but it doesn’t matter when.

Data types

The main data type in Thymer is called a “line” for now. It’s like a line in a text document, except lines in Thymer can have all kinds of additional metadata. For example, we will support different “types” of lines, like a heading, a todo, a comment (and perhaps later on things like files or drawings or whatever else we want). Lines can also contain other information, such as references to other lines, tags, and they can be “scheduled”, have due dates and so on.

Lines can also have a parent line, so we can allow features to work on a tree structure, such as zooming in at a section of the document, or collapsing/expanding sections. A document is a list of lines in a certain order.

Next to the data itself, we store the changes on the “document”, called events. An event can be something like creating a line, editing a line, and so on. Although we store the latest version of the “document”, if you would replay all events from the beginning to the end, you would get to the same document.

A copy of all the data

In order to make the app feel instant, it’s important that we store a local copy of the “document” on the client. If something like scrolling down in your document or opening a schedule for next week would result in a delay while waiting from the server, that would not be a good editing experience. In addition, unlike text, it’s possible to link to other parts of the document, so those parts need to be loaded too.

In order to store a local copy of the document, we use the browser’s built-in database, called IndexedDB. Unfortunately, IndexedDB is rather slow, so we also keep an in-memory cache and try to do as few updates to the IndexedDB as possible.

An extra advantage of storing the document like this is that we will be able to easily make the app work offline as well, so you can keep working with Thymer while on the road (in the air).

Because almost all functionality will be client-side anyway, we could even look into something like end-to-end encryption, but we might not have time for that for the first version.

Collaboration

Another factor to consider is that we need the app to be collaborative. That means that not only should we send our own events to the server, but also listen to incoming changes the server sends us. For this part we use websockets. Whenever the user makes any changes, we’ll tell the server about it, which will then tell other clients which are online.

To sync with the server, we send our changes and request the server to send us changes from other clients. We’ll go into the exact algorithm and data types to do this in some other post, but in the end we end up with a list of “events” which we should apply to the document we have stored locally.

UI components

Another reason we have to think about “collaboration” is that even when someone uses Thymer by themselves, things still need to work if you have multiple browser tabs open. And even if that wouldn’t be necessary, then the point of the app is to have features popular in IDEs, such as split-panel views.

That means that when you make a change somewhere in the document, it’s possible that that same section of the document is also visible in another panel, and would need to be updated there as well. From the other panel’s point of view, the change might have come from anywhere, but we need to re-render the line so it’s updated. That means the events need to be handled by all components in the UI.

Combining it all

Because changes can come from multiple sources, multiple parts of the app might have to be updated because of a change, and simply rendering everything to the browser’s DOM would never be fast enough, we use a simple event system in which data always flows in the same direction.

That way, when we make a change somewhere, we don’t have to think about which of a dozen things we need to update one by one. Instead, we create an event, and send it to the root of the app, which sends it to all visible components which can then update themselves. For performance reasons, we take one shortcut in the editor itself: it will immediately respond by drawing the change, after which it will create an event which will inform other components.

As an example, when we paste “hello world” on an empty line:

  • The editor will immediately draw ‘hello world’ to the line’s DOM node
  • The editor panel will create an “update line” event, which is dispatched when the browser has time. We’ll experiment a bit with the best heuristic for performance. This could either be a timeout, or browser callbacks like requestIdleCallback. Using a timeout, we can replace multiple events happening in a short time with one single event (so we can combine individual keystrokes in quick succession to one update event)
  • When the “update line” is dispatched, the app will update the local database (in-memory, and make an asynchronous request to add the change to IndexedDB), and queue the event to be sent to the server
  • Each component in the app for which the “update line” event is relevant receives the event, updates its local state and redraws a part of the screen.
  • After a while, the event is sent to the server. Any new event which is received as a reply follows the same flow and all components will self-update.

Creating thymer.com

On Day -1, before we actually started, we wrote a bit about creating the website you’re reading now. Now that the landing page for the app we’re building is live, I thought I’d do the same for thymer.com.

With each new design, I usually start with sketching out some ideas on paper. What elements should go on the page, what message do we want to convey.

Because the product itself isn’t ready yet, we can’t really have a video or demo of the product on the page. That’s why I made a few mockup screenshots before.

I also worked on a few different headings and subtitles to try to make it clear what the product is about and what the pitch is. Hopefully the main heading, H1, will tell our initial target audience why this might be interesting in 5 seconds.

For the look and feel of the site I was thinking about dark mode. It seems to be popular with many apps our likely initial users are familiar with, and it fits well with other related software such as IDEs, editors and terminals, and in general many modern apps too.

This was actually the first time I really worked with just dark mode, so I started reading a few concepts first, such as this page. The book RefactoringUI is a great resource too (for anything UI design really).

With that, I picked some basic colors for both the screenshots and the site. Rather than going for 100% black or 100% white, one trick I often use is to pick a few colors, and then overlay them with another div, position: absolute with a dark background color (and light foreground color) and alpha-blend them.

As for the font, just like in the product and the screenshots, using some accents with a monospaced font felt like a good match, so I picked those for the H2 sub-headers.

This looked like a good start but other than the screenshot it looks rather boring, so time to think about adding some colors and other elements. When thinking of the monospaced font a bit more, I thought maybe it would be a fun effect to add a bit of the glow from those retro terminals:

Another technique I always like to use to add some pizzazz while being able to keep the rest of the page simple is to use some sort of gradient or colorful image as a background for the H1 heading.

h1 {
   letter-spacing: .1px;
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
   background-image: url(some_cool_picture.jpeg);
   background-position: 0% 20%; 
   color: transparent;
   ...
 }

I usually just browse on Unsplash, and search for something like abstract or certain color names. I then use the image as the background-image with the background-clip: text effect, and see what it looks like.

In the end I didn’t use any of the images directly, but it gave me some inspiration for a gradient, which I then generated with some online gradient tool (I think it was https://cssgradient.io/, but there are many of them).

Next up was adding a call-to-action (we want to build a launch list for the private beta) and some more details/benefits about the product we’re building. The CTA is nothing special, just a form and hook it up with an online form provider.

Even though we have to keep the first version of the product small, just the flexibility of the core editor component gave us quite some ideas of cool use cases users might use the product for. I first I thought of adding a detailed “tour” highlighting all of those, but that would take way too much time. Especially because we can’t easily take screenshots from the product itself, as it’s not ready yet. Plus our main goal is to just generate some initial interest. We’ll be able to let the product speak for itself and let users discover all the extra’s once it’s ready.

I picked 4 core parts of Thymer to highlight: how it being an editor of sorts is a different approach from classic lists/kanbans, how we think tools like IDEs are a great match for managing big plans, how we think this is better for planning too, and highlight the fact that Thymer also works in teams.

To make the page less text-heavy, it would be nice to have something accompany the text explaining the benefits. Instead of screenshots or animated gifs (which again, we don’t have yet) or drawing something (which would cost time), I thought using some UI elements within the text might be interesting. They should be instantly recognizable for our users, help explain what it is and make it look a bit more colorful. Plus, it’s easy to make, and there’s a lot on our todo list right now.

I probably rewrote the entire thing at least five times, but that’s just how that process works ;), see the messy work in progress screenshot:

Eventually I settled on four “tiles” with a few animations/UI elements in each. The autocomplete menu will show the actual date of tomorrow at the time someone visits the page. The “someone’s typing” animation will probably be instantly recognizable to show the editor supports multiplayer. Very little work to describe things with less text and more colors.

I also added a quick summary of why we’re making Thymer at the end. I’m not sure if it adds a lot, but if we’re going to explain to anyone what we’re doing, a valid first question would be “seriously, another to-do list?”, so this would hopefully explain that part 😅. Added the blinking cursor effect to emphasize the editor part again.

I thought it looked OK enough for a V1. Maybe this was the point I should have just shipped it ;), but I still wanted to add some cool effect above the fold. In one of the messy paper drafts, I had added a few lines around the product screenshot, which were meant to be some bright colors popping out against a dark background.

I looked around in all the abstract photos I had found on Unsplash, and one in particular (https://unsplash.com/photos/gbY7XxB8Pzs) was a great fit with the colors of the app and the landing page. Using the same rgba(0,0,0,0.X) trick as for the color palette, I got it to blend in a bit more. To make it more dynamic, I added a very subtle animation to it by very slowly making it scale. Also gave the screenshot a subtle shadow to work better with the new background.

@keyframes pulse {
  from { transform: scale(1.0); }
  to { transform: scale(1.2); }
}
.pulse {
 animation: pulse 6s linear infinite;
 animation-direction: alternate;
}

The last part was making the website responsive. I just quickly hacked together some media queries using Inspect Element and checking which elements didn’t look good on smaller screens. It was mostly adding a bit of extra padding when not showing the benefit blocks next to each other, and making sure the desktop-size screenshot would stay large and scrollable so phone users can see the details too.

All in all the landing page is just a single .html file with a few lines of inline CSS/JS, and a few images.

And that’s the landing page. Now time to continue the work on building the actual thing, which is a tiny bit more complicated 😉

First (live!) coding: virtual scrolling

Started with a first bit of coding on the app today! We have to discuss exactly what features we want to build over the coming days and weeks, but I thought I’d quickly start with prototyping a small core component to see if that works as expected.

As an experiment I gave coding in a livestream a try, you can watch it below if you’re interested in a glimpse behind the scenes.

Many apps on the web get very slow when you add a lot of items, and as our app should be as fast as an editor, we want to make sure it works great when you’re adding thousands of tasks or ideas. A technique to speed things up when having to show many items is called virtual scrolling, which I tried to start with today. I’ll also write up a more detailed post about how it works later.