I made a side project. I wanted to reimagine what a cookbook might look like if it were reinvented for a dynamic medium. In this world, recipes wouldn’t be fixed to a specific scale. The recipe would be well-laid out on any size screen it was rendered on.

Printed cookbooks are a static medium: the author decides at printing time how the recipes will be formatted and presented. The units, measures, font-size and even the language of the recipe is fixed. The promise computers and programming afford us is that separating the rendering and display from the data grants the user ultimate control. Because the system could understand what units and ingredients were, it could display amounts in whatever context the user wanted: metric or imperial, weight or volume.

To build this probject, I took a few concepts, like Bret Victor’s work with explorable explanations, abstraction, dynamic media, and brought them to the data in a cookbook.

Introducing culinary.af

The proof of concept for this idea is hosted it at culinary.af. It hosts a few recipes of mine, which I hope you’ll check out.

culinary.af is built on top of pepin, which is the JavaScript library I wrote to parse and process the ingredients. You can find pepin on GitHub. The site is rendered with Jekyll, and the data for each recipe is stored in simple YML files. (Currently all on the files for pepin and the Jekyll renderer are in the same repository. In the future, they might be separated.)

What does culinary.af do?

culinary.af does a few things. First, it’s a home for my recipes. Cooks often tweak recipes, sometimes to work better for the altitude or humidity in their location and sometimes to accommodate the tools they have in their kitchens, like ovens that run too hot because their thermocouples are broken.

You can use pepin to make a home for your recipes, too. If you want to host your own version, fork it on GitHub, replace my recipes with yours, build with Jekyll, and host anywhere. The files are static and all of the logic is executed client-side.

I make apps during the day, but culinary.af is a website. Why is that? A few reasons: first, making a website, especially a responsive one, works on tons of platforms out of the gate. Recipes benefit a lot from being easily shared with URLs, which don’t work nearly as well with native apps. Lastly, when prototyping, having a platform as flexible to develop for as the web really pays off. I was able to move a lot quicker to make stuff happen, although I did feel very hampered by the lack of a type system, especially later in the game, when I was refactoring a lot more to support new features. I wrote a comprehensive test suite to account for this, which I’ll discuss soon.

The primary way that culinary.af takes advantage of its dynamic medium is that it allows you to scale recipes up and down easily. On any recipe page, grab the number next to the word “Scale” and slide it up or down. You can scale up to 10, and down to 1/6 of a normal serving.

Scaling recipes is unit-aware. That means if you scale 1 teaspoon of salt, to 2x, you’ll get 2 teaspoons of salt (note the pluralization). If you then scale to 3x, you’ll get 1 tablespoon of salt, because 3 teaspoons is one tablespoon, and nobody wants to measure out 3 teaspoons if they can just use one tablespoon. It’s needless to make that conversion in your head. This is the kind of thing computers is good at: taking mundane tasks, doing them for you, and giving you the data when you need it. As far as I can tell, no other recipe tool on any computer does unit-aware scaling. It’s a feature I’m pretty proud of.

This unit-aware scaling happens on larger units, too. 4 tablespoons becomes 1/4 cup, and so on. Each unit knows what the smallest form it can be represented in: for example, there’s no such thing as a 1/2 tablespoon, but there is a 1/2 teaspoon. Teaspoons go all the way down to 1/8. Cups go down to to 1/4. Gallons only go down to 1/2, because 1/4 gallon is just a quart. And so on.

To make this happen, pepin uses a unit reducer (link to code). It works by brute force: it converts an amount (say, 1/2 tablespoon) to every other unit that it could be represented by: 1 1/2 teaspoons, 1/32 of a cup, etc. It then finds the unit with the smallest corresponding number that is considered valid (bigger than the smallest acceptable amount for that unit). Since 1/2 tablespoon and 1/32 cup are invalid measures, it displays 1 1/2 teaspoons.

pepin also scales servings and yields for a recipe; it doesn’t scale the prep time or cooking time, because it’s never clear how scaling the recipe will affect those times.

A lot of culinary.af’s usefulness comes from its stylesheets. I’d like to call out two particularly useful features. First, the site is responsive. Recipes need to render on my phone, my iPad, and my laptop, because I might have any of them in the kitchen with me at any time. I also want it to work well on a TV-sized screen, because if my apartment had a layout that let me see the TV from the kitchen, I’d want culinary.af to work there too. culinary.af renders nicely in all those formats. I also blew up the font for viewports bigger than 1200px, which is useful for a laptop that’s a few feet away.

The other feature that the stylesheet provides is custom fraction rendering. Unicode provides support for what they call vulgar fractions, like ¼ or ½. In the beginning, I started by rendering these values. As I added custom fonts to the project, I learned that many fonts don’t support these codepoints. Since making it look nice was an important piece of culinary.af, I rendered my own fractions, using the techniques described on this page. The final css I ended up with was:

.frac {
  font-size: 75%

sup.frac {
  line-height: 10px;
  vertical-align: 120%

Having proper fractions adds a nice bit of shine to the project. pepin also converts decimals to fractions, so if your original recipe has “0.25 cups of flour”, it’ll render as “1/4 cup of flour”.

The last fun HTMLy component of this project was conform to the Recipe schema so that other sites can parse the information on culinary.af. The recipe schema is what allows Google’s search results to show prep times or star ratings when linking to other recipe sites, like Epicurious or Allrecipes.

Conforming to the schema on this site is simple. For the HTML element that encloses the item, you can declare:

<div class="recipe" itemscope itemtype="https://schema.org/Recipe">

From there, each HTML element that holds data gets an itemprop attribute describing the data. For example, this span’s content would be the yield of the recipe:

<span id="serving-amount" itemprop="recipeYield">

To test your schema, you can use a testing tool that Google provides.

It’s not clear what conforming to a schema does for you, other than a slightly nicer display in Google’s search results, but I think they represent the promise of the semantic web. Web developers have always been willing to put in the slight extra work of using semantically correct tags for their content, and these schemata seem like a natural extension of that, so I’m happy to support them.

There are also a few properties of the code that deserve mention. pepin is entirely served in static files; no code at all runs on the server. This was a useful quality of the project, since it means anyone can deploy it pretty much anywhere. It doesn’t need a database to run, or a Redis instance, or a Rabbit message queue, or anything like that. Just generate the HTML files and stick ‘em on any server. All the data for each recipe is stored in HTML. All the data for processing and converting units is stored in JavaScript. All the logic for parsing and presenting the data happens in the browser.

This frees you up in a lot of ways. There’s no crazy Docker configurations, no worries about scaling limited server resources, and no expensive hosting. Another weird benefit of all the processing being done client-side is that it’s open source by default. Because I know anyone will be able to check under the hood to see how the scaling and parsing logic works, I might as well just make it open source.

A few friends asked if I was going to try to make any money off of culinary.af, but because a) nobody pays for stuff like this and b) all JavaScript is already open source anyway, the answer was clear. I open sourced it early on, and I had the side benefit of being able to show my friends the code easily and ask them what they thought about a particular piece of code.

The second interesting property of the code is that pepin is the first project I’ve ever made that was truly test-driven. In past projects, a lot of the logic was poorly factored-out, or asynchronous, making it tough to test. In the cases where the logic well-separated, I’d usually write the tests after the unit was more or less completed, or I would write them for a unit with particularly complex logic and lots of edge cases.

In the case of pepin, the entire domain is data-in-data-out, making it super easy to test. Also, as you make changes to support new patterns of ingredients (“1 cup milk” vs “1 cup of milk”), you have to make sure not to break existing patterns, and TDD was perfect for this case. iOS apps don’t have much logic in them, but where they do, I’m going to try to structure them to take advantage of testing.

Because there’s no API component and no database to hit, my tests are blindingly fast. The entire test suite (50 tests) runs in 30 milliseconds. It’s very easy to run the entire suite after even the smallest change. (To be honest, the test runner should probably watch the folder and run after any file is changed, the same way that jekyll serve regenerates your site every time you save.)

I finally understand what people like Uncle Bob mean when they say that unit tests need to be fast. If your tests are hitting the API or the database, they’re going to be way to slow to run often. Isolate your logic, and run your tests a lot.

Where culinary.af is going

There are a few interesting problems in the domain that I would have liked to solve before launching, but they are unfortunately quite complicated.

One problem is that measures like teaspoons, tablespoons, and cups can represent both dry and wet goods, whereas quarts and gallons can only represent liquid goods. Currently, pepin doesn’t have an understanding of what the ingredients part of an amount means. Ideally, it would know that flour is a dry good, and its density is 2.1 grams per teaspoon.

With that information, the user would get to choose whether they want display in metric or imperial, and in volumetric or weight measures, and get exactly what they want to see. This is the dream of culinary.af: you shouldn’t have to do any conversions you don’t want to do.

Knowing what the ingredients are and how many calories are in each gram would also let us generate a nutritional facts table for each recipe. Yummly currently does this, and it would be a great feature to support.

Another great small feature I’d like to steal are the clickable timers Basil and Paprika have. These would detect times in the instructions, like “15 minutes” or “for an hour”, and turn them into timers that the user can activate with a tap. This is a feature that works better in an app than on the web, since for an app you can fire a UILocalNotification when the timer is over, and the web has no such mechanism. I will probably take advantage of HTML local storage to store the timers, so that leaving the page and returning to it won’t destroy the timer’s state.

The last big feature that I’d love to build for culinary.af is a good way to display images of the food. To really fill the roll of a cookbook, it needs to be beautiful as well as functional. This is a tough one for a few reasons: I need to have really beautiful pictures of my recipes, which are hard to get; the pictures need to go in the right places for each scale that the app supports; and the hosting of the pictures is an additional cost in complexity and hosting fees. I’m hoping to figure this out soon.

culinary.af isn’t not done; software projects never seem to be. Nevertheless, it’s cool, stable, and fun to use. I hope you enjoy it.