Snowman Dev Update (2.2: May 2022)

Snowman 2.2 Development Updates:

It has been a long time since I’ve last written a blog post on Snowman development. So long, in fact, I can’t seem to find the last time I did this, even though I know — think I know, anyway — I did. Was it back in 2020? Maybe earlier? Regardless, it has been a long time, and now, finally, I have devoted some extended time back to updating the Twine 2 story format Snowman.

What does it mean to be “minimal?”

Snowman was originally designed by Chris Klimas before Twine 2 was released. This puts its initial inclusion in Twine 2 some time within 2014, which means Snowman is at least eight years old, maybe older. For a web development project, that’s ancient.

When I took over maintaining Snowman a few years ago (late 2019), I had it in my head that I would update it into something people would want to use more. The first step, I decided, was to switch it over from a partial object-oriented design into a full one. I slowly converted a set of functions into their own classes with properties and methods associated with their purpose and tasks. This work became the 2.X branch of Snowman.

Then COVID-19 happened, and it led to my job taking over my life until I felt the only way out was to resign.

And, so, here we are. It’s summer of 2022. It’s been around two years since I’ve seriously looked at this code and I’m back to working on Snowman again. Only this time I’ve been pondering the title of this section: what exactly does “minimal” mean for a story format that claims it is “a minimal Twine 2 story format designed for people who already know JavaScript and CSS?”

The expectations of what a story format does and what functionality it should provide have changed quite a bit over the 13+ years Twine has been around. In Twine 1 (2009), it was nearly all presentational. Moving into Twine 2 (2014), it became presentational and interpretative with, at the time, Harlowe and SugarCube moving off into different coding directions with their own individual dialects. Now, with Chris’ addition of the story format Chapbook into the mix a couple years ago, there are four wildly different story formats included in Twine 2 with one, Harlowe, also providing an editor interface as well. What people expect when they use Twine (and a particular story format) has changed significantly over the last eight years. Expectations that are, because of how Twine works, mostly left up to story format authors to fill at the moment.

Re-Writing History (API)

Some story authors want to provide a way for a reader to pause in what they are reading and return at a later time. We call it “saving the game,” borrowing from game design terms. However, as it comes to a story format, this can be a complicated endeavor. What exactly should be “saved?” How?

When I took over Snowman, it was using the History API. This was, at the time it was originally created, a great choice. The API provides a way to track if a user clicks on the “Back” button in their web browser and then the code can revert to an earlier point in its tracking of different values. Internally, Snowman would track certain values (those saved to window.story.state) and then, when asked, would convert those values into JSON and perform a LZ-based compression on the result, creating a string that could be used to return to the exact location a reader left off when navigating through a story by including it as part of the location hash.

When I did the work to convert Snowman functionality into classes, I only did some minor adjustments to the use of the History API. However, as I soon came to learn, it was annoying to unit test, and the whole process of serializing and compressing JavaScript values was frustrating to use. When I released the first 2.X branch version, it was something I wanted to return to and fix at some point.

Two years later, I’m removing all the related code. Neither Harlowe or SugarCube, which are the most used story formats for Twine 2, use the History API. In fact, both of them implement their own save systems independent of the History API, preferring to use localStorage instead. This is, in 2022, the better choice because the storage is not affected by user navigation.

In moving away from the History API, however, I’ve also opened up a new problem of managing the internal state of the story format and what should happen if the user clicks the “Back” button, something Harlowe, SugarCube, and Chapbook ignore because they provide their own “back” button as part of their interfaces. As I write this, I’m currently debating if I want to add my own “back” button for the same purpose, breaking from eight years of convention in Snowman, but matching the changed expectations of an experience a story format should provide a user.

Getting rid of Marked

There’s a good adage to remember when writing code: don’t re-invent the wheel unless absolutely necessary. It makes perfect sense, then, that Chris went with the Markdown library Marked for use with Snowman originally. It’s a fast library. It works great for what it is designed for — which is the problem, as it comes to Snowman, turns out.

Marked assumes any input it is getting is part of a document with groups of text organized into paragraphs. Passages in Twine might not paragraphs, and the content within them may be dynamically including other code or text from different sources at any time. In practice, this has meant Marked could very easily produce non-standard HTML as it did not know how an author was generating their output for a reader.

After consulting what the Snowman documentation shows (which is, itself, a copy of the Twine Cookbook), I discovered I only highlighted a handful of Markdown combinations. I realized I could create a new internal class for Snowman to perform the same tasks of Marked without also dealing with its problems and technical debt. Instead of an entire library, I could solve the same problem with around 50 lines of code and a dozen regular expression replacement rules. It is slightly re-inventing a wheel-like structure, I will grant, but it is closer to the ideal of “minimal” now than it was before the change when it included an entire library most people barely used in practice.

Tags and “little stories”

Previous to last week, Snowman did not provide an easy way to search through passages for particular tags. This was not difficult functionality to add, and I have already added a method for this. However, the reason why it is of any importance is because Harlowe added storylet support in early 2021. (This was something I had been working on myself a few months earlier.) And the push for Harlowe to add this was because Josh Grams created the TinyQBN library (for SugarCube stories only) and the Grams format of using the tags of passages to describe requirements.

While I don’t want to necessarily add an entire library or major additional functionality, keeping with the theme of “minimal,” I do feel comfortable providing some methods for searching through passages that could be used by others to create such functionality much easier in the future. It will not be the full implementation Harlowe uses, but it will be a couple more steps in the direction.