Creative writing with git-flow and the Snowflake Method

git-flow's value depends on the nature of a project. Take creative writing: Randy Ingermanson's Snowflake Method makes you start from a crude—yet complete—one-sentence story and iterate until you are left with a good story. Requirements imposed by The Snowflake Method are analogous to git-flow's role for the master branch. Given a LaTeX project managed with a combination of git-flow and the Snowflake Method ("Snowflow"), we get some interesting properties.

Assume this file system:

build.sh          # Compile PDF(s) in dist using story/
dist/             # .pdf files
concepts/         # whatever
story/            # .tex files
    aggregate.tex # \document{book}

At minimum build.sh runs something like pdflatex -halt-on-error -output-directory ./dist ./story/aggregate.tex to make a PDF of your story. The concepts/ directory contains assets describing characters, settings, conflicts and plot decisions. One rule for this project is that the concepts/ directory be checked in, but never be processed by code. This allows free-form creativity in asset production that a precise process would otherwise curtail.

Snowflow branches behave analogously to their git-flow counterparts, with some added expectations.

  • The master branch holds a project that compiles to PDF and tells a complete story.
  • An elaborate (develop) branch adds detail to the story.
  • A concept (feature) branch introduces at least one unique, self-contained concept in concept/ later used to elaborate.
  • A review (release) branch holds a complete story for review. If the story is of sufficient editorial quality it may merge to master.
  • A reconcile (hotfix) branch fixes pressing logical errors such as plotholes.
  • A seed (support) branch addresses circumstantial concerns such as differences in story arcs or fictional universes.

To enable the Snowflake Method, master should a complete story, but that story does not have to be publishable. On that note an early iteration for Therac-25's firmware shouldn't have seen the light of day. It may seem insensitive to compare death by radiation overdose to bad writing, but only if you've never read anything by Dan Sacharow.

A Snowflow project will face a "soft end" on a commit to master representing a publishable story. Unless you come up with different universes, story arcs or derivative products there may be no need to measure progression or compatibility with version numbers or tags.

In experimenting with this workflow my favorite discovery is that concepts/ eventually takes the shape of "behind the scenes" content for readers, which may be separately packaged and sold. So long as commits are disciplined, the commit history *helps you build several products at once, *where the main story you intend to publish implicitly promotes content you could produce using information in concepts/.

The concepts/ directory also serves as a sandbox for inspired writing sessions. Writing is pretty moody. Sometimes I feel disciplined and can see how to package my commits, and other times I just want to write with no distractions. So if you want to hammer out a few thousand words in a text file in concepts/, go nuts. You can always worry about structuring the content with Snowflow when you are ready.

Elaboration process

I must elaborate on the elaborate branch. elaborate may either expand on the story or perform technical tasks using LaTeX. In the former scenario, I use footnotes containing at least one question or instruction to identify opportunities to build on the story.

You don't have to use footnotes, but keep in mind that someone who reviews your work should not have to be a developer. I like having something visible in the product for an author and editor to discuss.

For example:

Bob jumped over Alice. \footnote{But WHY did Bob jump over Alice?}

To make the elaboration process sensible, you should write content that addresses a footnote either in the vicinity of where the footnote appeared, or in a location that better establishes context. When you resolve the matter raised by a footnote, remove that footnote.

When you commit to an elaborate branch you may add at least zero footnotes, but you must remove at least one of the footnotes found when you started. By the time you start a review branch there should not exist any footnotes.

Review process

  1. Elaborate on all relevant footnotes.
  2. git flow release start ...
  3. Compile a PDF to release to trusted readers for feedback.
  4. From the feedback, insert a list of footnotes (or other annotations) where applicable according to your own best judgement.
  5. Address all footnotes.
  6. Repeat steps 3-6 until there exist no footnotes.
  7. git flow release finish

Guidelines

Writing can't be constrained by too many rules, but I did note these guidelines and observations down as I worked.

  • Do not adjust the story/ to the concepts/, adjust the concepts/ to the story/.
  • Do not modify story/ when on a concept branch.
  • Your comfort and focus on writing comes before the process. Don't be afraid of relaxing with pen and paper to decide what to do. Lay down on the floor and sketch on a bunch of Post-Its next to a cup of tea before typing anything.

Licensing Snowflake Method content

If you decide to write using this process, stay mindful of where you publish your working code. If your product is a book, license it like a book. But more than anything, consult someone qualified to talk about licensing. Of course some books like You Don't Know JS are open source, but if you are concerned about distribution, do your research and choose a license.

Generate Different Resumes for Different People

spin converts résumés written in Markdown Extra to HTML5 such that you can tailor content to different audiences. If you made furniture on the side, you can generate two résumés with $ spin ./woodworker ./programmer. You can then use pandoc to finish the job by producing Word docs or PDFs. See the examples in the source code.

Hiding annoying diffs on BitBucket pull request pages

Some shops mandate checking in long, autogenerated files into source control that clutter up BitBucket pull request pages with large diffs, leaving you to scroll epic distances or depend on the top navigation to visit files key to a code review.

Despite at least three different tickets dating back to 2014 asking Atlassian to support excluding specific file diffs from pull request pages. the issue was marked as wontfix for an unhelpful reason.

This userscript creatively named "Hide Annoying BitBucket Diffs (HABD)." It's set to execute when you view a pull-request page powered by BitBucket. The script does not support binary files. Installation instructions are at the bottom of this article.

In order for this script to function, you will need to configure a couple of things. Thankfully it's quick!

You can control what diffs to remove by referencing a plain text resource listing one regular expression per line (Pointing to a raw Pastebin helps here). *A diff is removed from the pull request page if the filename corresponding to that diff matches ANY of the expressions in the resource. *

For example, this resource hides file diff blocks for package.json and yarn.lock, both of which are common in Node projects and are checked into source control.

package.json$ yarn.lock$

If we're clear up to this point and you want to use the script, let's get you set up. If you encounter any problems, please report an issue.

Script installation for TamperMonkey

  1. While using Chrome, add TamperMonkey.
  2. Go to HABD's page on OpenUserJs.org and click "Install." You will see the source code and a confirmation. Confirm the installation.
  3. Click the Tampermonkey icon in Chrome and click "Dashboard." You will see a list of installed scripts.
  4. At the far right of the row where you see HABD, you will see a little notepad icon. Click that to edit the script.
  5. Change the line starting with "@resource" to point to a raw PasteBin or some other clear text resource with one regular expression per line. The link already in the script points to an example. Remember that the regular expressions match against filenames. If ANY of the expressions match, the diff is removed from the page.
  6. (Optional) Change the line starting with "@match" so that it matches only the pages in which you review BitBucket pull requests.
  7. (Optional) If you want to hide diffs on both pull request and commit pages, use the @include rule to add another URL matching pattern.

Script installation for GreaseMonkey

  1. While using Firefox (or some other supported browser), add GreaseMonkey.
  2. Go to HABD's page on OpenUserJs.org and click "Install." You will see the source code and a confirmation. Confirm the installation.
  3. Click the arrow next to the GreaseMonkey icon in Firefox and click "Manage User Scripts." You will see a list of installed scripts.
  4. On the row for "Hide Annoying Bitbucket Diffs", click "Preferences," then "Edit This User Script."
  5. Change the line starting with "@resource" to point to a raw PasteBin or some other clear text resource with one regular expression per line. The link already in the script points to an example. Remember that the regular expressions match against filenames. If ANY of the expressions match, the diff is removed from the page.
  6. (Optional) Change the line starting with "@match" so that it matches only the pages in which you review BitBucket pull requests.
  7. (Optional) If you want to hide diffs on both pull request and commit pages, use the @include rule to add another URL matching pattern.
How to tame Mithril's router and write clear apps

I moved to Mithril and I seriously love it! Sadly, the router alone has been enough of a pain point to make me rethink how to use Mithril. I will use profanity on the subject as a coping mechanism while I write, but don't let that fool you into thinking that I blame anyone but myself for any bad code I write, or that I reserve anything other than respect for Mithril's patient and knowledgeable maintainers.

If you don't know what a router is or does, don't worry. I wrote a section to get you up to speed.

For those of you new to Mithril, I want to flatten your learning curve for the v1.1.x Mithril routerand then show you how taming the router tames your Mithril apps. Everything I will show you can already be found in the documentation, but I read the docs and still made "gotcha" mistakes that didn't register until they happened. My hope is that by paraphrasing the docs in terms of the router's quirks, others won't have as frustrating of an experience. For that reason, if you are already familiar with single-page application (SPA) routers this article will still help you avoid painting yourself into some corners.

Once we're squared away on the router I will show you how to cleanly separate routing policy to make your app easier to change. Finally, I'll show you one demo Mithril application organized in this way.

I assume you are either curious about getting started with Mithril or have used it just enough to wonder how to improve your use of it. I also assume that you know how to write Mithril components.

Preamble for routing newbies

If you've used a SPA router before, skip this section.

A SPA router takes a path you type into the address bar and navigates the end-user to a specific spot in your app. Unlike how navigation traditionally works, SPA routers do not refresh the browser when the user moves about. This makes web applications feel more cohesive and fluid. A route might look like /pages/home or #!/shop/checkout, depending on what part of the web address the router uses to track the user's location.

Typically routers want you to set them up by telling them all the places the user can go, along with what happens to the user at each of those places. This can be done in several ways. Sometimes you give a router a big object (sometimes called a routing table) with the routes as keys and some implementation-specific type as values. Routing tables typically show clear intent.

router.config({
    '/': HomePage,
    '/about': CorporateInfo,
    '/shop': Storefront,
    '/support': HelpDesk,
});

Other routers might give you a decorator function that mark other functions to handle specific routes. The premise is the same.

@router.route('/')
function HomePage() {
  // ...
}

@router.route('/about')
function CorporateInfo() {
  // ...
}

You use routers to map out your application from a navigator's perspective. Routers tend to have other features, such as parsing values from routes as function arguments, or history manipulation. The Mithril router is just one of many variants out there.

Mithril's Router

You summon Mithril's router using the m.route() function, a routing table, a DOM node that will hold rendered content, and a default route. Here's an example CodePen.

Each route can be associated with either a Mithril component or a RouteResolver object (discussed later) that commands more refined control. In the above example, I use a Mithril component that will print Hello, Sage! when you visit the /hello/sage route. You can see that the name was parsed out of the route itself by Mithril.

In your code, you can tell the router to navigate somewhere using m.route.set(). You can ask for the last resolved route using m.route.get(). You can parse our variables in your route using m.route.param().

For these simple cases, that's all you need to know. But I want to jump right into the gotchas lest you end up with a false sense of security.

Mithril resolves routes on its own schedule

The Mithril router resolves routes asynchronously. Say if you are on a /cart page and you want to go to your profile page at /profile. You might use m.route.set() to navigate. But if your code then asks Mithril's router for the current location with m.route.get(), your state is going to be wrong.

This example shows a view that correctly prints the active route, but the console.log shown in the example says the route is undefined right after m.route.set() kicks off navigation. That's because m.route.get() only tells you the last resolved route, which can lead to confusing bugs if you use m.route.get() to update a view model at a bad time.

To expand on this point, did you ever make a view that looks like it has data from one page "ago"? If so, then you used m.route.get() or m.route.param() to change state out of sync with the Mithril router. In some cases, you want the pending route, that is, the route that the router is currently resolving when you are trying to sync up your app state to router behavior. The component in the above example doesn't have this problem because by the time it renders the Mithril router has already deemed the route "resolved" so that m.route.get() and m.route.param() deliver the latest data.

Mithril has no gimme-the-pending-route API, and you can't use window.location without tracking the router's prefix (like #!)—which also lacks a getter. You can only set the prefix using m.route.prefix(). Mithril is basically clamming up so that you have to solve your routing needs with your state. The only way to reliably access and use the pending route for tracking purposes is by writing RouteResolvers.

RouteResolvers: To block, or not to block?

When Mithril matches a route, it will start rendering once you give it a component. In this example, a Mithril component renders outright and redraws implicitly as per the default behavior of m.request().

This approach is easy enough, but if the network fetch finishes quickly, the loading indicator may look like an unpleasant flicker. To fix this, the docs suggest blocking rendering using a RouteResolver, which exposes the pending route as discussed earlier. But I won't show that here because I instead want you to understand how RouteResolvers change the flow of route resolution. All you need to know is that your pending route info is in the arguments to the onmatch() function (which fires when, guess what, the associated route matches).

This revision removes the flicker by removing the loading indicator entirely. The documentation acts like the scorched-earth approach is a selling point, but tell that to users with bad connections. So long as you return a Promise, that onmatch() can be used to delay rendering until data is available for a component. Rejecting the promise makes Mithril fall back to the default route. A more robust approach would always allow for an indicator to account for slower connections while preventing flicker. You can also use RouteResolvers to optimize layout diffing using an optional render() function. For now both techniques are left as an exercise to the reader, but if you ask me, never block rendering and be smart about loading indicators.

We now know enough to make a mess.

Introducing policy

Assume you are working on an admin dashboard for "tenants" that make widgets. We need:

  • A widget listing page for one tenant. Authenticated users only.
  • A widget detail page. Authenticated users only.
  • An error page to display problems loading data, and
  • A login page.

I call the decisions on how to navigate and handle related problems the routing policy because they are navigational requirements no matter what. Here's a Mithril router configuration that meets these requirements.

m.route(root, '/login', {
  '/login': Login,
  '/error': ErrorReport,
  '/:tenant/widgets': {
    oninit() {
      if (!app.user) {
        m.route.set('/auth');
      } else {
        app.tenants[m.route.param('tenant')].loadWidgets().catch((err) => {
          m.route.set('/error', null, {err});
        });
      }
    },
    view() {
      const {widgets} = app.tenants[m.route.param('tenant')];

      return (widgets)
        ? m(WidgetsOverview, widgets)
        : m('p', 'Loading...');
    },
  },
  '/:tenant/widgets/:id': {
    onmatch({tenant, id}) {
      if (!app.user) {
        return Promise.reject();
      }

      return app.tenants[tenant].loadWidgetDetail(id)
      .then((detail) => {
        return {view: () => m(WidgetDetail, detail)};
      })
      .catch((err) => {
        m.route.set('/error', null, {err});
      });
    },
  },
});

The policy says that guests are redirected to /login if they request a private route. To do this, the route handlers do different things under the assumption the app state is in scope.

  • The component attached to /:tenant/widgets redirects the user in oninit()
  • The RouteResolver attached to /:tenant/widgets/id returns a rejected Promise.

The latter case causes Mithril to fall back to the default route, which is /login in this case. This implicitly programs the same behavior. You could still call m.route.set() in the RouteResolver and return undefined assuming there isn't a render pass that would be surprised by that.

...

Now.

I intentionally left out error handling and the origin of app for brevity, but in the shoes of a junior who has never seen a router or Mithril before, does this code make any goddamn sense?

Look at how much stuff we have to know just to write code that block guests. If one flow blocks rendering and the other doesn't, our policy shouldn't have to change shape to accommodate in this case.

You might (correctly) argue that this case is easy to refactor into a more consistent form. Inconsistency in how we resolve routes is not Mithril's fault. However, Mithril does oblige us to write our routing policy in its router's terms. Once routing tables get large and you have juniors on your team, keeping order will become a chore. For example, to bring back a loading indicator in /:tenant/widgets/:id (Product will ask for it; don't act like they won't), you have to either:

  1. Replace the RouteResolver with a component to render that indicator immediately; or
  2. Make onmatch return a component immediately, out of sync with the loadWidgetDetail call.

That's not obvious to everyone. And if you are from the "pass state down as attrs" school of thought, you can _get fucked _because changing around Mithril's routing code means you have to rewire how state circulates to your components (No, I will not import my app state in every JS module I write. That's a different article). It's easy for a team to end up in an awkward position where simple changes lead to non-trivial refactor jobs.

Let's make this more interesting: What do you do if you want several loading indicators starting with a big spinner, then a dashboard breakdown with a bunch of little baby spinners on analytics widgets crunching numbers? The answer is not worth thinking about, but I can tell you my first attempt involved an unholy union of decorated RouteResolvers.

Organizing code around policy

Again, the struggles with Mithril's router is not a statement on its quality. However, something is backwards. Our routing policy should not depend on Mithril's router. The opposite must be true to clean up our code. From the Bob Martin school of thought, it is the rules _that make an app work that should sit at the center of your software. While we cannot physically force Mithril to depend on our code, we can reorganize to say that the rules of our app dictate _everything from routing, to state, to views, and so on.

After writing and rewriting my routing code several times to do what should be simple things, I asked myself what I wanted my code to look like, with this in mind.

For aforementioned reasons I knew that the code I wanted would have to meet the following requirements:

  • The code says what it does and does what it says.
  • You should be able to change routing policy without needing 6 candles, a virgin's sharpened femur, and a sheep.
  • Application state is more authoritative than the router's state.
  • Never block rendering. Views render the app at any moment. If the views look wrong, I either changed state wrong or redrew at a bad time.
  • The Mithril components are pure (attrs return content deterministically).

I came up with this:

dispatch(document.getElementById('root'), '/error', app, {
    '/': () => Login,
    '/login': () => Login,
    '/error': () => ErrorReport,
    '/:tenant/widgets': loggedin((app, task) => {
        const {tenant} = app.spaRequest;

        task(() =>
            app.selectTenant(tenant)
               .then(() => app.loadWidgetListing()))

        return WidgetsOverview;
    }),
    '/:tenant/widgets/:id': loggedin((app, task) => {
        const {id, tenant} = app.spaRequest;

        task(() =>
            app.selectTenant(tenant)
               .then(() => app.loadWidget(id)));

        return WidgetDetail;
    }),
});

dispatch() also sets up the Mithril router, but you have to interpret the setup differently. It assumes that all state comes from one place, a la Redux. That's where you see app passed in. It can be a plain old empty object if you want. It also assumes that the pending route and all related arguments are part of application state, and makes it available as part of app.spaRequest.

Second, the /error route specified is not just a default route. It is considered the route you visit in error conditions, and I treat it that way for the same reason I would write try..catch in an entry point.

Finally, the routing table itself is a table of routes to functions. The functions return components to render synchronously but may *change state asynchronously *in terms of the pending route using a task() function_._The task function takes a function that returns a Promise and redirects to the error route in the event of an unhandled failure. Otherwise, it starts a new render pass when the task finishes.

That loggedin() function you see is just an app-specific decorator that redirects users to /login if an authenticated user is not in app state. You will see it in the below CodePen. I want you to see that as an example of why it should be easy to express routing policy. Previously policy had to be parsed from Mithril semantics, but in this case I could remove Mithril from my project entirely and not violate the rules as governed by this approach. If you want to guard a route from unauthenticated users, slap loggedin on it. If you want to add a colony of nested loading indicators, do that in your component, then run task calls that iteratively update state with data in stages.

Some might also want to factor out the "authenticated tenant selection" pattern in the handlers for cleanliness. That is now an easy change, and you can clearly see what guarantees are made about state with function decorators, even if the flow is highly subjective.

function tenantRoute(fn) {
  return loggedin((app, task) => {
    const tenantTask = (next) => task(() =>
      app.selectTenant(app.spaRequest.tenant).then(next));

    return fn(app, tenantTask);
  });
}

dispatch(document.getElementById('root'), '/error', app, {
    '/': () => Login,
    '/login': () => Login,
    '/error': () => ErrorReport,
    '/:tenant/widgets': tenantRoute((app, tenantTask) => {
        tenantTask(() => app.loadWidgetListing()))

        return WidgetsOverview;
    }),
    '/:tenant/widgets/:id': tenantRoute((app, tenantTask) => {
        tenantTask(() => app.loadWidget(app.spaRequest.id)));

        return WidgetDetail;
    }),
});

This approach is not without caveats. For one, you are obliged to write LBYL-style components that check for incomplete state to render one of many possible variations of a view. Second, you have to be extra careful to synchronize state between redraws. This should go without saying, but the approach in dispatch() makes it you to spawn concurrent Promise chains that all mutate state. But those of a Redux mindset would see an opportunity to extend task() with the transactional behavior expected from reducers.

Demo, plus other considerations

If you want to see an example of dispatch() in action, play with this CodePen.

The CodePen contains the transpiled demo from the mithril-dispatch GitHub project, which uses a routing policy based on a real-world SaaS shop despite the simplistic content. I encourage you to start reading here to see the code organization benefits brought by dispatch(). As a bonus, the demo also shows a highly-opinionated use of layout diffing optimization for users familiar with RouteResolver.render(). You can also see that the components end up pure despite complex, slow operations that would involve heavy use of lifecycle methods otherwise.

Conclusion

In this article I have introduced you to SPA routers. From there, we learned about Mithril's router and how it handles different policy decisions when routing. We learned that changing the routing policy to meet simple requirements is harder than just expressing the policy in plain language. By adding a dispatch() function to separate policy from Mithril semantics, the Mithril components we write only render what they are given, state concerns itself with the data, and route resolution merely connects the two together.

I also emphasized that more than anything, the rules of your app are king. Just because I wrote dispatch() in the manner shown does not mean I will use that exact implementation later. Just be sure that you do not make your rules depend on Mithril's router, or even Mithril in general. Frameworks are meant to enable your productivity, not tell you how to code.

I love coding and have done so professionally for over a decade. I use what little free time I have to experiment on ideas of various quality and to help others write quality software. If this post was helpful to you, please leave a tip and share this article with anyone that need to tame the Mithril router.

Mind map freeze-dried web searches with Eyedact

Mind maps effectively break down any topic. Assume a mindmap may be an indented text file. The file's shape guides self-study even if the coverage of the topic is offensively incomplete. Mind maps are not for small details anyway; they are for leading readers to existing knowledge.

American Football
    Rules
        Downs
        Penalties
            Leverage
            Clipping
        Positions
            Linebacker
            Defense
            Offense
            Quarterback
    Teams
        Patriots
        Giants
        Cowboys
        Packers

Eyedact helps mind mappers search the web. Much like with Aloe, it's completely non-invasive. Uninstall it, and you can still do what it does, just a little slower. Here's a video demo.

Obviously you could skip Eyedact and use Google directly. But those new to a subject might deal with vague technical terms with no reading order. Eyedact lets you write searches that are too vague for Google, give them a reading order, and still get specific results.

Mind-maps shape upcoming research for note-takers, and provide implicit documentation for maintainers. If I'm writing a framework with a big stack, I could write an essay explaining what we use and why, or I can write the below mind-map.

GraphQL
    best practices
    caching
    tutorial
Docker
    best practices
    build
    container
    image
    tutorial
Webpack
    plugins
    tutorial
Restify
    caching
    plugins
    tutorial
Redux
    actions
    async
    tutorial
    best practices
React
    component
    async
    tutorial
    best practices

In a hypothetical package.json with the above mind map called what-you-should-know, running npm run doc:beginner down to npm rn doc:advanced will bring the related topics up for study.

{
    ...
    "scripts": {
        "doc": "eyedact what-you-should-know,"
        "doc:beginner": "npm run doc tutorial -al",
        "doc:journeyman": "npm run doc practices -al",
        "doc:advanced": "npm run doc caching async plugins -a",
        "doc:local": "less README.md",
    }
}
Record your terminal sessions in space-efficient GIFs with no awkward pauses

When recording terminal sessions, tools like ttystudio will capture your activity in real-time. This bloats output GIFs and captures unwanted pauses and typos.

Use toughtty when you want to capture frames only when their contents have changed, allowing more careful sessions. If I went borderline comatose and spent 15 minutes typing one command accurately, the recording would not use any more frames or bytes than necessary, yielding a fluid and cohesive playback. The catch is you must manually pad frames using toughtty --padding while recording.

I can't believe that name wasn't taken!

Track Code History with Qi

qi prints git revision hashes at the end of fixed time intervals. This enables running an analysis at, say, the end of every week over the past two quarters to check if a team decision in January is having an intended effect in June.

For a toy example, running qi --every year --past "12 years" --command "du -sh ." on the Linux source shows annual disk usage growth, starting from the time on HEAD.

[sage linux]$ qi -e year -p "12 years" -c "du -sh ."
2.8G . # 2017 (to date)
2.7G . # 2016
2.6G . # 2015
2.6G . # 2014
2.5G . # 2013
2.5G . # 2012
2.4G . # 2011
2.4G . # 2010
2.4G . # 2009
2.3G . # 2008
2.2G . # 2007
2.2G . # 2006
2.2G . # 2005

For a different example, running qi --every month --past "3 years" --command "./analyze.sh" in the React source with the below script will generate monthly complexity reports.

#!/bin/bash
TIME=$(git show -s --format=%ct HEAD)

# Using https://www.npmjs.com/package/complexity-report
cr -e -f json ./src > $(git show -s --format=%ct HEAD).json

I wrote qi in a shop producing hundreds of commits daily, discovering that the team was, on average, creating seven production code files for every one test suite and leaving roughly 20% of our JS and CSS unused. Data like this catches attention and can sway discussions in favor of your proposals.