~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Most deals fail

One of the hardest parts of running a startup, next to doing the actual work, is knowing what to work on. There’s a seemingly endless list of things screaming “highest priority level”. One category in particular can be very distracting and time-consuming: deals and partnerships.

Especially when starting a business for the first time, all these deals can easily be mistaken for validation and important work, where in fact most of it is playing shop. The extra difficulty comes from the fact that you need just the right amount: you need to stay the course, but sometimes it helps to tack to beat against the wind.

The danger in saying yes to everything is that you just end up wantrepreneuring away your time. Most time should be spent on building your product, and talking to actual users. It’s very easy to spend all day jumping on calls with other startup folks, join in competitions, going to conferences, hang out with business networking groups, and not end up with a better product or a single customer. Trickier still are offers which actually look like they’re helpful. Especially when you just launch, you might see your inbox fill up with those very quickly, from amazing ad campaign offers to requests for collaborations.

One of those typical offers is a potential customer (or even an actual existing customer!) reaching out with an idea to get extra business. Wow, you just launched, and not only do you have a very passionate customer, but they’re willing to think along, have ideas of their own and spread the word for you!

What makes this difficult is that the common advice is to listen to your customers and use feedback to improve your product. But in my experience, anything more than getting feedback to learn more about the problem space, so you can then improve your product based on your own ideas, is a huge distraction. Proposals for deals or partnerships are cheap, and it will most likely be a lot of talk with very little result. Quite often you will end up becoming more of a freelancer building a solution which is only great for one customer. Stay the course and stick to your original mission.

We made this mistake too at the beginning. A customer reached out and thought our horizontal product would do especially well in their vertical. Perhaps we could white label it? They had a huge network in their market and were convinced they would be able to sell this easily to many of their customers and businesses in their network. Especially being technical founders, and only just having our first customers or two, only having to write a bit of whitelabel code and having someone do the sales to bring in even more, that’s amazing right? They just needed a few features which would be really critical for this market.

You can see where this is going. Only they ended up using the whitelabeling and extra features, for their own business. They never managed to refer anyone else or reach any other businesses. And of course why would they? Running your business is your job, not theirs. They aren’t as invested, it’s just a random idea for them. We all have random ideas, the execution is the hard part. Only as an owner of the business are you willing to do the actual hard work of building and selling, no customer is going to actually want to work as hard on that as you are.

Related offers to do affiliate marketing or marketing outreach for you will usually have the same result. Most of the time this just ends up being a way for a customer to ask for a discount or free plan in return for maybe telling others about your product. A better deal is to just not waste time on this and see if they want to pay so you have an actual customer.

Another very good example are emails from private equity firms or invitations to “chat” about investments or even potential acquisitions. If there’s a mother lode of distractions so large they can cost you your business, then this is it. But there’s so much to say about this (plenty of anecdotes there ;), that’s probably better for another post sometime.

The point is to remember with all these deals is that most of them will fail. They seem like a shortcut but they’re just be a distraction. What really moves the needle in the end is just improving your product and going to where your customers are to talk about it. The only caveat here is that sometimes saying yes increases your luck surface. Many good things in life can happen because of random connections, and business is no exception. As a rule of thumb that’s why I usually only say yes when the distraction is not too big and it’s either fun or to help someone and pay things forward. But time is limited so choose very wisely and say no most of the time to stay the course.

Reducing sign-up friction

When working on a product, you spend a lot of hours building and refining. Everything about your startup feels important when you’re so invested. It’s easy to project this feeling onto your customer: you’ve created such as beautiful service, of course they won’t mind having to spend their precious time to use it!

Of course that’s not how it works, and customers just want to use your app or service and get on with their day. Every hoop they need to jump through is a reason to close your website or churn.

Some examples where I think it’s really easy to improve a workflow with almost no effort:

Login == password reset
Why do I need an account, set up a password, and so on? I see this a lot with websites you hardly need to use anyway, like online stores where you order from once a year. Before you can check out, you need to set up another account.

As a user I’m going to have to track yet another password or create an entry in a password manager (and not everyone has one, and certainly not one where it takes zero time to use it across all devices). If you’re the owner of a service, you’re probably logged in all the time, but for many of your users the next time they return they won’t remember anything about their account.

So they use password reset. Which is exactly the same as if you would have only asked for their email address in the first place, rather than a “Register new account” form. Much simpler to build, much simpler check out process for the user. And next time they can still use their email address to get a link to access their previous orders (or just opt out of the process altogether, if they don’t want their email stored).

Auth links in support emails
I recently had to go through the process of updating my credit card details for a bunch of services. This is another workflow where services can easily reduce churn and friction for their users.

For example, if your app sends out a notification email to remind users to update their card details, why not include a link that takes them right to the form to do it?

It’s such a small change, but in my case there were plenty of services where I had to spend time to find out how to pay them! The number of users churning on this is going to be larger than zero for sure.

And again, if you include a link, add some sort of one-use auth code to the email for the update form, so I don’t need to go through the whole password reset again.. just to receive another email with the actual code.

Inviting team members
As we’re developing a web app which can be used in teams, one piece of functionality we need to add is a way to add other users to a workspace.

The way it works is fairly standard. You typically add one or more email addresses and click a button called something like “Invite to Workspace”. With our existing app, we’ve gone through many iterations over time, to make it easier and better based on the feedback from our customers. For example, we allowed admins to include a custom message to the invitation emails. Then we allowed admins to resend the invitation, in case people missed it. Then people wanted to change the subject. Or the template itself. If it’s possible for us to send the email from their company’s email domain? Then some asked if everyone on a specific domain name could be invited all at once.

The obvious solution here is to just make it much simpler and remove all the friction: just generate an invitation link and let admins send it any way they want. They can send it to a user, through a text message, to their entire mailing list, whatever they want. With exactly the text they want to onboard the users, with their logo and from their domain, and as often as they like.

With many of these workflows, it turns out that the best solution is both more convenient for your users, removes a lot of friction, and is much faster to build, too. Win-win-win.

Recap @ Day 45

Time for the latest recap!

Just like last time, we’ve spent a lot of days coding again to finish an MVP for Thymer as soon as possible. Unfortunately bouts of COVID are putting a bit of a spanner in the works for us, so not as productive as before, but we still managed to get quite some things done. To get a bit of an idea, these are some of the features we’ve been working on:

  • save the changes you make in Thymer (sounds like a must-have right πŸ˜‰ )
  • send and sync those changes with the server
  • resolve “conflicts” (if two different changes have been made to the same part)
  • line wrapping in the editor
  • indent and de-indent lines
  • open different parts of the doc in different panels
  • basics for auto complete in the editor
  • handling tags

We wrote a bit more about the technical related work in: Some database design strategies, Data & event handling in the app, Insecure defaults considered harmful, Look ma, no dependencies!.

With 7 weeks left, there’s still a lot do! We need to integrate the editor with the data part, we need command menus and planning features, basic account management and more.

On the business and marketing side of things, there’s plenty work left as well. One of the aspects is pricing, which we wrote a bit about in Calculating SaaS pricing in reverse. We also already want to think of other marketing activities we can do around the launch, to get some additional traffic. Perhaps some strategies from Viral Loops. The launch list on the landing page is growing slowly too, and we’re still planning to post about the site in other places, and see if we can get some more signups (once we get around to that, we’ll post the results of that as well).

And as usual we’ve written more articles about our thoughts on starting and running a business: Your app needs a USP, Reduce, Basic strategies for validating startup ideas, Assert all the things, Randomness is a perspective.

Thanks again for following our story! You can see our daily updates on Twitter (@wcoolsΒ andΒ @jdvhouten). Or drop us a line atΒ [email protected]Β for any reason. We’re always excited to hear from people πŸ™‚

Reduce

The number one priority right now is to get the first version of our product out as soon as possible.

One advantage of time or budget constraints is that it forces you to really think about what the focus should be of your product. Spending a bit of extra time at the beginning to think about how you can simplify the product and reduce the scope saves a lot of time in the long run. The “If I had more time, I would have written a shorter letter” quote is applicable to code, UI, product and marketing as well.

The most obvious way to ship faster is to build less.

Every line of code we’re adding to the product is code we might not have needed in the first place. Maybe certain ideas won’t work out and we need to scrap them later. Or maybe nobody cares about the product in the first place. If the product does take off, we need to maintain all of it. It’s much harder to remove features than it is to add them, in terms of keeping users happy.

This is not the time for premature optimization in our code or being architecture astronauts: we need to ship. That said, spending a bit of time sketching out what screens should roughly look like helps us identity places for re-use.

For example, one of the main components for Thymer will be the editor. On top of that, we also want people to be able to schedule their tasks. If we would dive right in without thinking these features through, we might end up building two completely separate controls for them. Looking at it more closely, they can actually be the same component. We simply might have to make some parts read-only, and date headings will be like headings in a doc, but dynamically generated. And instead of spending a lot of time on a preferences panel, we might just reuse the editor again to show a configuration file or sorts.

Simple products are better products.

Reducing the number of “concepts” doesn’t just reduce the number of components to build. It also makes it much easier for users to understand the product. When there are only one or two core concepts, it’s easy to get a good “mental model” of what the app is and how it works.

For example, many text editors in the past had several modes. Different options were available in different modes, like a view mode and an edit mode. Not only would you need to build both as a developer, but users had to understand the two concepts and deal with working with both. Larry Tesler worked on the idea that interfaces should be modeless (and famously drove around with a “NOMODES” license plate).

Both a good and a bad example of reducing UI concepts is Windows. The Start Menu, introduced with Windows 95, is absolutely brilliant in that sense. Even the most technophobic users only had to learn one concept: whenever you want to do something or you get lost, just click “Start”. Simple to build, simple to pitch, simple to understand.

Windows 8, by huge contrast was a complete mess. There’s a bunch of different start menus, with completely different styles. You had to learn all those concepts and they had to explain why it was better. Same for Settings vs Control panel (and there’s probably more). It takes more time to build, and just confuses the user.

The Home screen and Home button on iPhone is similar to the simple concept of the start menu. I notice how explaining how an Android phone works to a family member without too much tech experience is much more difficult than explaining how to navigate an iPhone. With iPhone you just explain they click one button at any time to get back. Onboarding is way faster with products introducing fewer concepts.

Simplifying the USP and marketing

As a bootstrapped startup, you won’t have the resources to be everything to everyone and outbuild your competitors. You also shouldn’t, because it’s easier to pitch a smaller product to a specific niche than selling large integrated solutions to everyone. You need to build more, need to explain more, need to onboard more, and need more buy-in from more decision makers.

Focusing on just a few core concepts is also important when thinking about your USP and describing why your app is better. Competing against another product which does “A, B and C” by building “A, B, C and D” is typically a bad idea, especially when bootstrapped. So rather than a mix of features and concepts, narrow things down to one simple concept you can describe in a sentence. A lot of marketing is about repeating yourself, and if there are dozens of concepts you need to explain, it waters downs the message about what makes your product worth trying.

Assert all the things

In programming, we use assertions all the time to make sure our assumptions about what the code should do are (still) valid. Whenever the resulting state is not what we expect it to be, the program stops and we receive some sort of error notification.

We’ve set up our systems to email us about it, so in the below example we would get an email if some_function no longer works as we expect it to:

offset = some_function()
assert offset % 4096 == 0, "The resulting value should be a multiple of 4096"

We use this concept of assertions everywhere in our business. Some of those are in code, but many others run as periodic scripts (cronscripts). They’re small bots if you will, constantly checking if all kinds of “business state” is still what we expect it to be. Those checks can be about all kinds of things: from accounting, to hardware, to invoices and security.

Rather than having to manually check if everything works as expected, we have our systems tell us when something is wrong. Push vs pull. It’s like the concept of a dark cockpit in modern planes. Rather than staring at dozens of lights and graphs and make sure they have the right color and right value, we keep working without distractions and get notified when a potential problem arises.

The checks don’t do anything other than informing us through an (email) notification. We don’t add “smart” logic to automatically try and correct the problem. The entire point of these checks is that the state doesn’t match our expectations, so those events will be rare and require investigation. At best it’s premature optimization to worry about a solution before, and worst case automatically correcting it based on assumptions which no longer hold cause even more problems.

Over the years we’ve added plenty of these checks in our existing systems, and we will definitely use them again this time. They’ve saved us a lot of headaches by catching issues early on, and quite often the fix was trivial. Some examples:

A loose network cable

We run several scripts on the server where we expect the output to be empty. For example, we run a command like the one below to check the status of the network.

$ ethtool eth0 | egrep 'Speed|Duplex' | egrep -v '(10000Mb/s|Full)'

When the output is empty everything is as it should be. When there’s output, we get an email about it. At one point we got the following email message:

Speed: 1000Mb/s

Because we found out right away it was easy to trace the problem down to a bad cable which was causing network errors in our data center.

Server configuration

Just like the example above, we have many more of these little bots checking all kinds of configuration. Sometimes we hash the entire output to make it easy to check if the output still matches. For example, this command checks if all services are running with the exact status we expect them to have:

$ systemctl -a | md5sum | grep -v 36adc2b4e6a5798c9a8348cfbfcd00e0

When there’s output, something’s off. We do the same for all kinds of other configuration (maximum number of open files, kernel modules, IPv6 support, firewall rules etc). This has especially saved us time when updating packages or the OS itself. Even when you read the changelog, some subtle changes in defaults or configuration file locations might break your assumptions.

We also have scripts checking configuration files against typos or security issues (such as constantly checking our web server config for common misconfigurations). If we would ever edit the config and make a mistake, we’ll get an email a minute later.

Stripe webhooks

Many APIs have a tendency to change over time. Of course you try to keep everything updated, but there’s no guarantee you’ll catch all unexpected behavior or that you have time to look into every new change.

We use Stripe to process our credit card charges. Obviously it’s important this works correctly. To make sure we’re not missing any important billing state or changes we didn’t even know existed, we automatically send ourselves an error message whenever we receive a webhook we didn’t know about at the time we wrote the code.

Manual invoices

Although we process most invoices automatically using Stripe, many larger B2B accounts often pay using wire transfers. Automating every last bit isn’t always the right trade off, but we’re also not going to have staff to waste time on making sure invoices get paid. So we have a simple bot which notifies us every week if an unpaid invoice has a due date in the past. It keeps emailing us every week so we can’t forget.

Accounting

In 2020, some EU countries temporarily lowered their VAT rates for a few months because of the pandemic. Who knew? We certainly didn’t. Luckily, we didn’t have to, because our VAT-rates-bot told us on the 1st of the month that something was off.

All our other accounting is code, too. We can calculate down to the cent that the balance of our accounts should be equal to the sum of the opening balance and all transactions and invoices we’ve recorded since. The strangest error we’ve discovered this way is that someone on the other side of the world accidentally paid their speeding ticket to our account instead of the traffic authorities. You can’t make this stuff up, but luckily our bots told us about it.

Feature limits

Sometimes you introduce a feature where you have to make assumtions on how it’s going to be used. For example, we allow users to create workspaces when they work in multiple teams. For the first version, the UI won’t be very advanced. Will it work well with 5 teams? Sure. Will it work with 100 teams? Maybe not. But why worry about something that might never happen. We put in a “soft assert” (which warns us but lets the user continue), and investigate what to do about it when we start seeing people go over limits.

~~~

And of course the list goes on. Sometimes new problems come up, or we read about issues which might be relevant for us too, and we add a little check. Set and forget. Assert all the things.

Viral Loops

Getting new users for any new product is no easy task. There are endless channels to try: paid ads, search engine optimization and content marketing, integrations with other products, cold outreach, social media posts… the list goes on.

Of all those options, the holy grail is word-of-mouth marketing. When running a business, it’s hard to imagine something better than your customers recommending your product to others. You don’t need any pushy sales tactics (not everyone likes those!), users will trust recommendations from other people over your marketing material, and it scales really well!

Simply deciding “word of mouth” is going to be your strategy is a variation of build it and they will come, and is unlikely to work. Unfortunately there is no way of guaranteeing in advance whether people want to tell others about your service. But that doesn’t mean we shouldn’t try to increase the chances of that, especially if it’s relatively easy to do so we don’t need to give up other (marketing) work for it.

The low-hanging fruit when it comes to improving the chances someone will find your product by existing users sharing it, is to think about viral loops in the product. They’re called viral loops because using the product will cause users to share the product with others, which will then drive new users, and so on.

One example you’ll often see is a “Powered by X” link when you’re interacting with a widget on some website. Another classic example is the “Sent from my iPhone” signature under many of the emails you receive. Or even more basic, the email address itself. Back when nobody had heard of GMail, and you received an email from that domain, existing users were spreading the word for them.

And speaking of GMail, another simple tactic is to try to make the service invite-only at the beginning. The “artificial scarcity” of the invites makes it more interesting for people to share them and ask around who wants any, and makes it more interesting for others to ask around for them. As most of this happens online, it will bring in more users who see this, and get codes to share in turn. At the same time it allows you to slowly ramp up the launch, so you can process all the feedback and fix any initial glitches.

As Diederik wrote yesterday, it’s hard to get significant revenue from very small consumer accounts, and it might make more sense to keep it cheap or free for these users. One advantage of being able to have a free offering is that it might be possible to ask some social media exposure in return. For example, we could experiment with something like mentioning us in a tweet to get free access. Large B2B accounts aren’t going to do this, but word-of-mouth to find those in the first place is helpful too.

A specific features might also lend itself to create some sort of viral loop. To stay with the example of a tweet: for a previous product we tried a feature where you could interact with the app over Twitter. People saw those tweets scroll by in their timelines which drove more traffic to the app. Another feature we might consider is being able to make a part of your Thymer document public. That way people can share things like roadmaps or ideas with others, who will then also get to know Thymer that way.

Finally, it can help to add easter eggs or other fun components to your app which people might want to share. As we wrote about before, we think it’s important an MVP sparks joy in that respect.

Like many attempts to market a product, some of these can be very hit-or-miss. There’s no guarantee it will work, but if they’re easy to do and don’t cost much time, these viral loops are a good way to increase our luck surface in getting more traction with word-of-mouth.

Look ma, no dependencies!

When building a new app or feature, it can be tempting to immediately start with “import framework” and then build from there. First search for the latest and greatest toolchain, and then get building. It’s best practice, right?

Before we do that, we always first have a look at how much time it would take to write the feature we need. Could we get away without adding another framework with all its dependencies? If the feature seems simple enough, we choose to write a version of it ourselves first.

Using a framework (and libraries to a smaller extent) is always a trade off. There are plenty of examples of both extremes of course. We’ve all seen those websites which load jQuery, Vue, React, Bootstrap and Tailwind, because the entire thing is just dependencies duct taped together. Or introducing a dependency for 11 lines of code, like left pad. On the other hand there are examples of “Not invented here” going so far that products become insecure because they ignore all the hard work done before them (e.g. don’t roll your own crypto, as the advice goes).

The benefits of frameworks are obvious. Everything it already does right you don’t need to work on. And when building an MVP (or feature in any stage of your startup for that matter) you need every minute you can save. So what are the costs we look at? The most obvious is that all those lines of code will eat RAM and CPU (and GPU these days), so if there are large parts you don’t need, that would be wasteful. But there are many other costs too:

  • Learning new concepts. Many frameworks usually lead to a rabbit hole of new concepts you need to explore, very specific to just that framework. Read the docs, explore some tutorials, find out what the “framework way of doing things” is. That includes dependencies on which the framework in turn relies on.
  • Maintenance. You need to keep up with at least security updates, so you need a system in place to learn about those and apply them in time. Ideally you could keep your code running for years and just apply some security updates. But typically the older a dependency gets, the bigger the chance is there won’t be updates for the “old” version, and you need to upgrade to a new version with lots of API changes. So even though your code itself still works perfectly, there’s some bitrot going on, and you need to keep updating your code. You might even have to start over learning new concepts for the new version. Another problem is you need to make sure those (security) updates and in turn all its dependencies can be trusted, which isn’t always easy to do.
  • Unexpected behavior & debugging. With large frameworks there’s always a risk some internal implementation “detail” isn’t really documented well, but is actually important to understand in order for your app to work right. Often you only find this out the hard way after running into a bug. Debugging large frameworks, just like any other codebases you’ve never worked on, can be really time-consuming with deep call stacks and dozens of files.
  • Unknown constraints. Another big risk is finding out half-way that the framework doesn’t really support your use case. Or it simply doesn’t scale.

For example, one of the features we’re working on for Thymer is real-time collaboration features using Websockets. Because our backend is Django (a framework which definitely saves us time!), I googled around and found out there’s a project called Django Channels, which extends Django beyond HTTP, including Websockets.

Again, it would sound tempting to simply start there, just import it and done. In this case we would already have to start out with quite a number of new concepts to explore, or new layers we have to add to the stack: ASGI and asgiref, Daphne server, Redis, interfacing with synchronous Django code. Some of those we had never heard of or worked with before. And that’s next to the API of the framework itself, and finding out whether it works for the purpose we need. I’m not at all implying Django Channels isn’t a great framework! The point is that using a framework has a cost, just like writing it yourself does, so we need to compare the two.

So let’s look at what we really need in terms of Websocket functionality. We need to accept connections, keep track of open connections, be able to query the database using Django’s ORM and finally inform open connections when a new event becomes available for them. That’s it. Looking at all this, it’s not completely obvious the integration Django Channels provides with Django is worth the costs (new concepts, add layers to the stack, maintenance, debugging, knowing we don’t run into unknown behavior).

In this case we made the decision to write a simple Websocket script ourselves. It turned out to be around 200 lines of Python3 code and it took 1 day to build.

That would certainly not have been faster when having to look into all the ins and outs of the framework, and even without a deadline of 80 days we’d want to get an MVP out as soon as possible. If we have many customers at some point and it turns out we need something more advanced, then we can still look into rebuilding it (because it’s just 200 lines after all!). And best case 10 years from now this simple script will still be running, happily serving customers, without any modifications. Which is exactly what happened to our websocket script for our first product.

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.

Recap @ Day 33

A bit later than usual this time, but time for a new recap!

Last time we wrote about how we launched the landing page for Thymer.com. The last 10 days since then we’ve mostly spent on coding for the app.

Code

The two largest chunks of code so far are the editor and data layer, which we’ve started with because they are at the core of everything else in the app. We’ve been writing all those parts from scratch, so we can truly design the app from the group up to work exactly as we want. That’s a bit of a risk in terms of scope and complexity, but it’s also what we think will be at the core of the USP for Thymer in this crowded space.

For example, for the editor that means building everything from unicode support to selections to keyboard shortcuts and line wrapping. The alternative would be using an existing solution (or the browser’s contenteditable), but then Thymer could not be the app we want it to be. We’d have to adjust our vision instead of building what’s necessary to get our vision to work. The same applies to the data layer. We’re not exactly editing just flat text, and it all needs to be synced and allow for real-time collaboration. To do that, we’re building our own API based on CRDTs, or Conflict-free replicated data types. It’s a type of data structure that we can use so multiple people can type at the same time in the same document. Another problem is that all these parts need to work together such that the app is fast, which is another challenge in the browser.

We also wrote some more about our coding in An update on the coding work, Typescript without Typescript and Thinking different about technical debt. All in all, our week pretty much looked like the screen below πŸ™‚

All the rest

It’s been quite a challenge to balance all of the coding work with writing, tweeting and marketing work. Switching between the two when you’re really focused on one or the other becomes difficult at times, but we’re trying to keep up the pace here as well.

We wrote a number of blog articles again like You need a moat, Competing with (very) large companies, Single miracle startup, Work on what you need to and Text-based user interfaces in 2022.

We also got the first signups for the private beta waiting list. It doesn’t prove anything yet, but it’s fun to see some first people interested in giving it a try! We haven’t done much marketing to promote the landing page yet though, other than sharing what we’re working on. We’re planning to also look into mentioning Thymer in other relevant communities and conversations online where people might be looking for something like this.

A quick update on the stats so far:
40 signups for the private beta waiting list
104 email newsletter subscribers
18.6K blog unique visitors
58 blog posts
~10K lines of code
159 and 129 twitter followers
Nothing spent but 33 days of time… and obviously 0 customers and $0 revenue yet πŸ˜‰

Thanks again for following our story! You can see our daily updates on Twitter (@wcoolsΒ andΒ @jdvhouten). Or drop us a line atΒ [email protected]Β for any reason. We’re always excited to hear from people πŸ™‚

Work on what you need to

Starting a business is always a gamble. I still think the odds are better than many assume (not starting one is a gamble too, in a way), but in the end you just won’t know for certain if it will work until you try.

To reduce the “risk”, a lot of advice focuses on making smaller bets and getting quick wins.

It seems a lot of this advice is taken to mean you should always try a “safe idea” to maximize the odds. Try to interview people and see what their problems are. Maybe do some consulting or freelancing instead (vs building a product). Perhaps start an info-product, something easy to make, make sure it doesn’t take too much effort to build. Instead of thinking about a problem, think the other way around: what could get you “rich quick”?

I think this narrow focus on these “safe ideas” is a mistake.

It’s totally okay to have a small scope for your initial idea. It’s very important to be able to launch relatively quickly, because you have no idea whether your product will really work. But that’s always the case, “safe idea” or not.

The biggest problem with reducing the space of ideas people think they can work on is that you stop working on what you feel you have to. It’s great if you really love one of those “safe ideas” of course, but if it feels uninspired or it becomes so much about the quick win, then we we stop dreaming about what we actually want to build. And I believe it’s working on what you feel you have to that really increases your chances of success, even if that’s a harder problem than the “safe bet” easy-win get-rick-quick idea.

You’ll automatically know when you’re working on something and your heart isn’t really in it. In fact, your customers will know it too. Your product won’t stand out, it will feel bland, look the same as the rest, uninspired.

You know that problem that you just have to work on, because it’s where your mind drifts when thinking about solving problems during a walk/shower. It’s the thing you can still work on even if you’re tired (not that you should, but you could!). That means you’ll be able to keep working on it, which is important if you want to succeed. Especially because in every startup there will be many dull and boring tasks which need doing, and it’s hard to stay motivated enough to do all the parts which are boring to you if you don’t really want to work on this problem.

Another advantage of working on your dream idea is that will want it for yourself, so you know exactly what to build. Or maybe it describes yourself. Or it’s a trend you’re part of which is starting to emerge and you think more will follow. It’s hard to time the market anyway, and just coloring within the lines and building similar things to what’s out there is not going to make it easier. Might as well work on your dream and make it stand out enough.

Another important reason to work on your dream idea rather than a “safe idea” you don’t really care about, is that if it actually succeeds, it will become a relatively big part of your life. We can only work on so many ideas after all.

Many of these ideas are discarded because they might feel too unrealistic (or the complete opposite, they might feel like a “hobby”). But those are actually the most interesting. Yes they can completely fail (the advice of finding out quickly is still very important!), but they can also really push things forward. A lot of really interesting and successful things wouldn’t have existed if their creators would have stopped dreaming and just stuck to something “safe” instead.

Work on something you have to, get a V1 out to customers as soon as possible for the type of product, and see if they love it. Worst case you’ve worked on something you had to anyway, no regrets, and move on. Best case they’ll love it and you’ve really added something new to the world.