Building a Dungeons & Dragons battle tracker in vanilla JavaScript

This entry is part 1 of 1 in the series Dungeons and Dragons Battle Tracker js project

I just finished Wes Bos’ ES6 for Everyone course and I’ve decided to try to test myself and what I’ve learned by building something somewhat useful.

I just started running a Dungeons & Dragons campaign for my kids and their friends. We had 10 people (kids and adults) playing the first session and we may have more coming. That’s a lot of people to keep track of generally, but especially when it comes to figuring out things like initiative, damage, etc. during fight scenes. And since we had one in the first session, trying to manage it on paper got to be pretty crazy.

There are mobile apps to manage this stuff, of course, but for that, you need to add a whole bunch of stats to even get it to work. And the one I’m thinking of (Game Master 5th Edition) is great, but it really has a lot more to it and is for running full on campaigns and isn’t so great with ad-hoc fights with random NPCs you hadn’t previously added to the system.

So, I decided to try to build a tool that would do a couple things:

  • manage all the characters I need to keep track of, including NPCs
  • allow me to enter in (manually) the initiative rolls and add the bonus for the characters automatically
  • sort them by their initiative
  • track their health over the course of the battle
  • allow the initiatives and damage to be reset at the end of the fight

I’ve thrown together some code and decided to document this as I go, which means this post will be a little stream-of-consciousness. The first part was building a boring HTML file with some really basic elements that I can start adding more elements to dynamically with JavaScript. I started out dumping all my js in the html file, but ultimately split it out into a separate js file. So the HTML file just looks like this:

The idea is that when you click the Add New button, you can add a new character. You’ll an input for the character in the unordered list and then you can Add that character to the list. You will be able to add any number of characters.

To start, I need to set some assumptions. I created some variables to identify the core pieces of this markup that I will be working with, namely the character-list itself and the add-new-character button. After playing around with some different things, I ultimately built out a function to add an input (for the character name) and an Add button and insert those into the character-list. I then bound this to an event listener on the “click” event on the “Add new” button.

At this point, I have a button that adds new elements to an unordered list. Each list item has an input field and another button. Now what I need is a way to “save” the information (in this case, character names). And I’ll need to add another event listener inside the event listener that’s hooked to the “Add new” button, because the list items don’t exist until you click that button. You’ll notice that I have some logic to keep track of a character count — this wasn’t actually in the first pass. This is so I can uniquely identify each new character by an id and tie together the input and the button and the list item. This is handled by a function that will extract the numeric value out of any element id.

With the getId function, I have everything I need to get rid of the inputs, replace them with a text node and “save” (at least on the page) a new character name. Here’s what the updated event listener looks like:

And this is a gif of it in action:

Animation of early iteration of battle tracker functionality.

I’m pretty happy with this but there’s a few more things I need.

First of all, I need to determine a character’s initiative and their initiative bonus. Initiative comes from a d20 dice roll with their initiative bonus added to that result. The bonus comes from the character’s Dexterity bonus. Now, one thing I could do is just add an input field just for the initiative bonus. But one thing I constantly struggle with as I’m making and working with characters is remembering the bonus table — e.g. what the bonuses (or negatives) are in relation to what ability scores. It might be handy to have a generic input for dexterity and then calculate the bonus for you. We’ll also need an input field for initiative.

Being from a PHP background, using a switch makes the most sense to me, even though it’s more complex, because there are a lot of conditions where the ability score modifiers don’t just follow a linear progression. I’ve built a function to take an ability score (from 1 – 30) and return the modifier for that score.

Ultimately, we’ll use these to take an input Dexterity score and add the modifier to an input initiative roll. So let’s take care of those next. First I need to add an input for Dexterity. Since initially, the Add button disappears when a name is added, I added some somewhat complex logic to handle what happens if a name but not a dex score is added and vice versa. The end result is, the button stays, but the button text changes to reflect the thing that needs to be added. This will be helpful for validation later, if I add that.

I split all this functionality out into its own function that gets fired within the initial event handler for the Add New button.

I realized that the Dexterity input field should be numeric, and probably should have a specific range to support realistic numbers only (e.g. 1 – 30), so I went back in and tweaked the initial function that builds out those inputs to add a couple attributes to that input. In doing so, I realized that now the placeholder text is too large to be entirely visible. The size attribute doesn’t work on numeric input fields, so I can’t make it larger that way. Since I plan to apply a layer of CSS to this at the end, I’ll need some classes, too, to identify certain types of inputs. So, I went back to add some classes to those. element.classList.add() still feels new to me, having only previously done this with jQuery and $('element').addClass(), but it doesn’t feel any harder and literally accomplishes the same task.

So now my rendered inputs look like this initially:

And they look like this after they’ve gotten values added to them:

This gives me something to work with, styling-wise. Moving on, I realized that the initiative bonus value isn’t actually being stored anywhere after it’s calculated. I’ll add a data attribute to the span so I can pull that out later to do the math for initiative totals. I’ll also need to add another input after the character is listed for that character’s initiative. At some point, I’ll need to add a separate section for enemies/NPCs, and then I’ll want to order all of them together by initiative value.

After taking care of the data attribute, by again making use of element.addAttribute(), which is quickly becoming my most-used function, I have something that looks like this after Dexterity scores are calculated:

So far, so good. Now let’s add another input for initiative after the character is added, then move on to NPCs. I’ve been wondering if using promises might be useful here — to wait for all the data to get back before doing…something. I don’t really think it’s applicable because we’re not fetching from a remote API and, while there are a number of things that will be rendered eventually it’s all user-input, so it seems like it doesn’t make sense in this context. However, the entire process is all about chaining functions — do this, then do this, then do this — which is something that promises let you do. In the meantime, I’m literally adding a call to the next function within the previous function. I guess that works.

The other thing on my mind is adding in Webpack to handle a build process and render SaSS when I start adding styles and then throwing the whole project on GitHub. Ultimately that will change the architecture of the project, but that will come later…

Handling initiative means we need to do some math — we need to take the initiative roll and add (or subtract) the initiative modifier. At the same time that we add the initiative roll, I’m adding an input to record HP (hit points). First I need to add the initiative input. I add this to the end of the function that handles the Dex and character name, and the initiative input only displays if we have values in both of those fields.

This, then adds another event listener to the new “Save Initiative roll” button which calculates the initiative value and passes it along to another function which saves it to a data attribute. At the same time, I also add a field for managing Hit Points.

Now that I’ve got an HP field, I might as well manage hit points. I’ve added a function that not only stores the max hit points based on the initial value added, but also tracks the current and last hit point values. This is so I can display a message that says something happened.

If a character is reduced to 0 HP, we record that they died and remove the Update HP button.

Initially, having to click the Update button twice after entering 0 seemed like a bug, but in retrospect, having that level of “are you sure” actually seemed like a good thing for a DM — it allows the possibility to come back via death saving throws or spells that prevent a character from dying. So I actually added a condition that built on that and added an additional message for resurrections.

One thing that this tracker doesn’t do is prevent characters from going above their max HP. There’s a whole rabbit hole that I could go down if I start taking into consideration “temporary hit point” or other things outside a hard and fast HP value, but if we’re saving max HP, we should probably prevent the HP from going above that. It also seems a bit counter-intuitive as I’m using this to update the HP total as opposed to entering the amount of damage dealt. That means I need to do the math in my head first rather than letting the tracker take care of that. I might come back to that problem…

Handling not going above a character’s max HP is pretty simple — it’s just a condition if the input value is greater than the max HP data attribute value, and if it is, we set the input value to the max HP. This looks like this when it comes up:

To add monsters and NPCs, I can just use all the existing functions and event listeners used for characters. I just need to attach the initial click handler to a different button in a new section. That makes everything super-easy, though, because the code is already written and I know it all works and is tied specifically to character IDs which are unique to the element that’s added.

The last thing I need to do is add a way to sort characters and NPCs by their initiative values. And in doing this, I need to identify which characters are player characters and which characters are non-player characters and monsters.

My initial thought was that I’d literally just take the existing list(s) and re-sort them based on initiative. However, on reflection, maybe I should create a new, separate list that renders once when everyone is added, in the order they should be. And then maybe I could add an input field to track the actual amount of damage dealt to the character rather than having to enter the current total HP — then the HP field just updates automatically or is removed and replaced by just text. Since I have the input, I think I’ll leave it but maybe make it disabled so it can’t be edited manually…

First, though, I need to create a new list for the actual initiative order. But this list shouldn’t be displayed until we have all the characters and their initiatives calculated. Whether it’s rendered as an empty list on the page first or added to the DOM by JavaScript doesn’t matter too much, but I generally like the pattern of adding the empty element to the page and then filling it in later.

In order to know when we’re done adding characters, we need some sort of “save” button. I’ve added a button that says “Let’s go! 💥” that will act as the start of battle trigger. However, this should not be active all the time — only when we have characters. And if we have an incomplete list of characters, we shouldn’t be able to start a fight. I’ve built out a button, event listener, and a couple functions that check for contents in the two lists (character and NPC/monsters) and if one or the other of those are empty it displays a message saying you’re not done, so you understand why clicking the button doesn’t do anything. The buttons and messages go away when there’s at least a single PC and a single NPC.

I’ve removed the description that appears below the button in the code after this gif was taken…

That gets us all ready for creating:

  • a list of characters and NPCs/monsters
  • …ordered by initiative
  • …with an input for damage taken

After creating the list with the damage input, I’ll remove the update HP button and do a bit of refactoring for the messages so they are triggered differently.

First we need to figure out the data for each character that we need to pass and track and the best way to store that is in an array of objects. Then we can sort these character objects by initiative and loop over them to add them to the list. I have a function that will scrap the page for all individual characters and return the data we want as an array of objects that we can work with.

Now we can use that information to fill in a list for the initiative order and damage tracking.

The recordCharacterDamage function doesn’t exist yet, but that’s what I’ll build next. Essentially, what that’s going to do is replace the earlier functionality I built in that triggers on the “Update Hit Points” button (which I’ve removed). The actual functionality of the triggered event will remain mostly the same, which is displaying a message and updating the HP, the difference being that I’ll also want to update the initiative order. If a character dies, we’ll want to display that somehow in the list of characters.

At this point, we can build a list of characters. Here’s an early look of what that looks like. The code snippet above includes their initiative score in the line, but I added that after I took this screenshot…

I wanted to visually indicate whether a character was a player character or non-player character, so I’ve added that as a data attribute that gets stored that I pull out when building the initiative list so we can see that visually. And I thought it was nice to be able to see what each character’s initiative was, even though it’s not absolutely essential. Next I need to refactor damage tracking so it’s triggered by the new “Record Damage” button rather than the (now non-existent) “Update Hit Points” button.

Now I need to refactor how damage is recorded. I’m not going to detail all this because it is a lot of what was already built, just in a different order. The tl:dr; is I broke out a separate function for initially establishing the hit points, and then I used the existing updateHp function to deal with recording and updating that. After getting that working, I was left with some duplicate code updating data attributes, because now I want to track them in the initiative list rather than on the initial character and monster list items. As long as the input value gets updated, we can pull that value in as the current HP and everything is fine, so we don’t need to update the data attributes after the first time things are set up.

Now we have a working damage tracking system and a short gif of it in action is below. It’s ugly as sin and I want to make it pretty so the next step will be pulling in some build libraries to handle SaSS and minify the js. I’ve posted the code on GitHub and I’ve created a pre-release at this checkpoint. You’re free to check it out, comment, and suggest pull requests if there are things you see that could be handled better.

I’ll write a followup post when I start working on making the front-end more presentable.

Gender parity in the Sad Bastard Music Club

You may or may not be aware of a thing I started doing a few years ago called the Sad Bastard Music Club. Many of my friends and people I follow on the internets periodically release mixtapes for people to download or listen to. Historically, I’ve done this as well and, being a DJ at heart, I enjoy sharing music with people. But, well, people don’t download things, and being able to share something regardless of where you are or what device you’re on is kind of cool, so the Sad Bastard Music Club is a series of Spotify playlists and if you sign up for the newsletter, you get notification of when they are going out.

The name comes from a sort of inside joke being that “I only listen to sad bastard music.” The logical conclusion if I only listen to “sad bastard music” is that any playlist I make would, by definition, be “sad bastard music”, presumably because I’m a sad bastard. And while typically I define “sad bastard music” as being anything sounding like Nick Cave or The Cure, I try to be a bit more diverse on the Sad Bastard Music Club playlists because, in reality, I listen to, and enjoy, a lot of different things.

I also enjoy listening to a diverse group of artists. It’s actually a bit of a point of pride that I try to make an effort to have diversity (be it gender, ethnic, or sexual identity) in the music I listen to and the music I share. And this was something I wanted to particularly include, from the beginning, when I started doing these Sad Bastard Music Club playlists on Spotify.

A few months ago, I actually went through all the various SBM playlists and ran some numbers. The result was that I still have work to do. It was a bit of a surprise as I was sort of patting myself on the back for how well I felt I was doing in making sure the playlists were pretty balanced.

Some notes on how I ranked the data:

Any group or artist where the primary (lead) member, or the lead vocalist, was female was ranked as female. Obviously this is a bad practice from the beginning — just because Blondie is led by Debbie Harry doesn’t change the fact that there are a bunch of dudes in her band. But, in the case of Blondie, Debbie Harry is very much the primary focus, as is the case much of the time with mixed groups. It’s a vanity metric, but you need to draw the line somewhere, so I drew it there.

Artists where a man and a woman got equal billing or representation (a good example is The B-52s) were classified as M/F. This also includes groups of mostly men with a female guest vocalist.

I added a “T” classification for artists or groups that identify as non-binary or gender fluid or include a member who identifies as such. Le Tigre and Against Me! would fall under this category, although in some cases I had to make assumptions because I was not sure how they identified publicly (as with the case of IAMX, which I listed as T because Chris Corner presents as gender fluid but I’m not sure what they would describe themselves as).

The result is, still, 51% of artists on Sad Bastard Music Club playlists are male, and that number is bumped to 58% if you include artists in which women get equal billing with their male counterparts.

I’m not going to diminish the win here — this is far better than the music industry’s representation as a whole — but I, personally, can and should do better. I hold myself to a higher standard than just what’s normalized.

My first reaction was “wow, I would need to do all-women SBM playlists just to right the ship” — and then I caught myself. So what? What’s wrong with that. With the music industry being male dominated for generations (and I’ve given presentations that mention precisely these numbers), what would be wrong with focusing on women for a few iterations? While there may be a lack of popular female artists in pop music, there’s no lack of actual female musicians and they can, generally speaking, benefit from any amount of publicity or exposure they can get.

Coincidentally, as I started thinking about these things, just a couple weeks ago Spotify tweaked one of my “Daily Mix” playlists to be predominantly women artists across a variety of genres which I have been really appreciating.

The next Sad Bastard Music Club playlist — which I plan on publishing this week — has a majority of amazing women artists and, moving forward, I want to continue to keep gender parity when I’m making these playlists.

Just another day of being an asshole on the internet

TL;DR:

  • Hunter.io is a service that email marketers use to get huge email lists.
  • Go to their email finder to see if you’re in their list of 200+ million addresses (you probably are).
  • Go to their claim email address page to (attempt to) remove yourself from their database.

I get a lot of emails. An overwhelming amount, in fact.

A lot of the time, I have myself to blame: they are from lists I signed up for (intentionally or otherwise) or places I have made purchases. Most of the time there is an unsubscribe link at the bottom and I just need to muster enough energy to go through the hundreds of emails and hit that link.

But sometimes, the emails are more personalized — like, actually written by a human being, not a robot — and those are far more difficult to get rid of. Here’s how an email like that might go:

Hi Chris!

I read your article https://jazzsequence.com/category/ministry-of-music/ and was really impressed! I have a site that has similar content and since you write about music, I think it might be relevant to your readers.

Can you take a look at my article at http://totallyfakemarketingwebsite.com/music-therapy/ and, if you like it, link it from your article? If you could that would be great! Looking forward to working with you!

Now, on the internet, when you are confronted by an unsolicited email or private message, you basically can do one of two things: ignore it, and hope that it goes away, and respond to it (either positively — “absolutely, I will definitely link to your content!” — or negatively — “hell no, take me off your list”). If these were sent by a robot, ignoring it would have no consequences. You could happily delete the email and go on with your day. But these aren’t sent by a robot, they are sent by a human. And dealing with it in any way other than an outright “go to hell” will result in a followup email.

Hi Chris!

I was wondering if you had a chance to read the email I sent you last week. Looking forward to hearing from you!

I want to not feed the trolls, but, it turns out, these content marketers aren’t trolls

It’s at this point that the internet rule “don’t feed the trolls” shows cracks. I want to not feed the trolls, but, it turns out, these content marketers aren’t trolls — they are some other kind of creature — and not feeding them, doesn’t make them go away. Because continued, conscious ignoring and deleting of the emails they send will just result in more emails…

Hey Chris!

Just checking in to see if you had considered my offer. Let me know what you think!

I haven’t tested how long these will go on unchecked. I usually give in and respond after the second or third iteration. Sometimes, I forget and it’s the fourth or fifth. But I haven’t found a point at which they don’t keep sending followup replies. At some point, if you want this person to stop emailing you, you’re going to have to hit the reply button.

Don’t call me a Monopoly player

It was one such exchange I had this week. I got an email from one of the two partners running gamecows.com. Now, looking at their site, I can’t tell what their business model is. Maybe it’s through affiliate links, although I don’t see any. Maybe they are just trying to build up a collection of list-icles to go on their resume for future writing gigs. There’s no advertising on the site, just a newsletter you can sign up for (and I’m not signing up for the newsletter just to test this experiment).

Whatever it is, I got an email from them that linked to my games list page. Now, this is not a post. This is not an article. This is literally  just a list of all the games that I own. It’s an experiment, and it’s a demo of my Games Collector WordPress plugin, and it’s a way for people to see what I have already, so if they wanted to get me something I don’t already have, there’s an easy way to figure out what I do have (that was the original reason I built the plugin, other features just expanded from there). There’s no content to speak of, and there isn’t even anything relevant to link from — the only links that are on the page are to Board Game Geek as a way to provide more information about a game. I suppose I could link to them in one of those, but I’m not trying to link to a review, I’m linking to a game description. I could link to Amazon if it wouldn’t then look like I was trying to profit from the game. I could link to the game’s website, but then I’d have to track down every game publisher. BGG has out of print games in its database, which makes it a much easier and more central place to get information about games. And much less biased, given that any reviews that appear on BGG are from people who’ve actually played the games and aren’t trying to profit in some way from their review. The more popular games have multiple reviews.

Anyway, this isn’t an ad for Board Game Geek. I digress.

The email was asking me to link to their review of Dominion (see what I did there?), a game that’s definitely one of our favorites. But, again, even if I did want to link to them, I have nothing relevant to link from. Not on that page. It wouldn’t make sense to link to their review of Dominion from my listing of Dominion in my game collection, it would be more confusing because it would be inconsistent with the other games on the page. Plus it wouldn’t be impartial.

I might have originally intended to respond, just because it was about games and I like games, but I didn’t. And so, sure enough, the second email comes. Except this one comes with a bite.

Chris, did you get my last email? If I don’t hear back, I’ll assume you’re more of a Monopoly person. Nothing wrong with that of course. ;)

Woah boy.

Now, it should be obvious that I’m not a “Monopoly person” just by looking at the page they linked to. If I was, there’d probably be several incarnations of Monopoly on the list. There are not. I have distinct memories of losing horribly and being angry at my Dad for winning so overwhelmingly and feeling like a failure at the game and as a human being as a result of Monopoly. Monopoly is not a fun game. Unless by “fun” you mean one person wins and makes everyone else’s lives miserable — which describes a lot of board games of the past, Risk is another great example of this. I make it a point to avoid games like these at all costs.

What’s more, if you look at the history of Monopoly, it wasn’t supposed to be fun. It was designed to illustrate the evils of capitalism, not how great capitalism is. According to Wikipedia “it was intended as an educational tool to illustrate the negative aspects of concentrating land in private monopolies.” Even their own about page mentions “the family-destroying dynamics of a ‘friendly’ game of Monopoly.”

I was angry at yet another of a long series of unsolicited emails from which there is no unsubscribe

This was an obvious baiting tactic, and one that, I felt, was particularly offensive, given that it was coming from someone who claimed to like games, directed to someone who (I should think, given that there are 100+ in our collection) also likes games, and is very much not a “Monopoly person” — something that should be obvious if you actually read the page you’re requesting a link from. So, like a chump, I took the bait. And I wrote a nasty email. Because I was pissed at the implication and I was angry at yet another of a long series of unsolicited emails from which there is no unsubscribe.

A possible solution

Here’s where the story shifts from the norm. Normally, I would respond to one of these (nasty or otherwise) and never hear from them again. In this case, that didn’t happen. On some days, I would be even more exasperated, but in this case, I made an implication that “if you just write good content, the traffic will come” which I know, really, isn’t the case. But I also know that emailing me, is not going to give them a bump in their traffic. My site doesn’t get traffic. You’re better off soliciting, well, Board Game Geek for one, to get links to your site. Or Geek and Sundry. Or, I dunno, anything else, really, because I hardly get hits on this site, and definitely not enough to make an incoming link from jazzsequence.com result in a higher ranking on Google. You’d be just as good building your own site, call it sequencejazz.com and write your own incoming link for all the good my Google juice would do you, which is the other reason why these emails exhaust me.

So they apologize for striking a nerve and I apologize for being an asshole and I said something like “I wish there was a ‘do not call’ list or something for these emails…” And this is where the real nugget of wisdom happens.

They shared with me the name of the tool that they — and many other content marketers — use to gather emails: Hunter.io. By all appearances, this seems like a fairly legit way of gathering lists of email addresses to spam send your wonderful emails to. They boast 200+ million email addresses in their database, all tested for sendability and ranked with a score. They have a search tool right on the front page their site where you can search by domain and get a list of results (with parts of the name blocked out, although with some social engineering you can figure them out a lot of the time). Go ahead and try your own (assuming you don’t have a Gmail account) — you’re probably in there.

You can even find the sources for the email addresses, and here’s where it gets really interesting. Their email finder lets you type in a full name (first and last) and a domain and it will give you the matching address. This is easy and you can do it right now to get an actual individual’s email address from, basically, anywhere, provided you can give those two things. But the sources, for me, were the most revealing. Two of my results had the tag “Removed”. I don’t really know what this means, perhaps just that my address no longer appeared on those pages. Those were my ancient ReverbNation page and a tag archive for the term “art” on jazzsequence.com! (And not just any tag archive, but, in fact, page 2, randomly.)

Hunter.io results for Chris Reynolds

My email address got entered into their database because I committed a piece of code. That code is open source and includes my email address and this is considered fair game.

The remaining (not removed) public listing of my email address is on Trac. Yes, plugins.wordpress.trac.org. So, to summarize, my email address got entered into their database because I committed a piece of code and standard practice for copyright and GPL notices in code is to put the author’s email address in the code. That code is open source, and therefore exists on the internet, and because that code is on the internet, and the code includes my email address, this is considered fair game to add me to a database of 200+ million other people who can be spammed receive unsolicited emails be emailed by this company’s users.

Hunter.io has a contact address — [email protected] — but when I emailed it, I got an auto-response that it couldn’t be delivered. The message response was, get this, “Leave failed, not a member.” This seems to imply that, because I am not a member of the service, I can’t email their public email address. Fabulous. Isn’t that fabulous?

Claim your address on Hunter.io

I did a bit of digging and, through a FAQ on their site, found that the Claim page on their site is how you can (attempt to) remove your address from their database. By entering your address into the form, you are “claiming” that address, and you can then either edit the preferences on the address or request that it be deleted entirely. Alternately, it’s entirely possible that you’re just adding your email back into their database, I guess time will tell.

At any rate, I claimed all of my email addresses, even those that didn’t come up when I searched for them. I don’t think it will block them from being added again in the future if they get indexed again, but hopefully it will null the existing matches.

If you get these emails, too, go to Hunter.io and claim your address. Bookmark the damn page and do it again in six months. I’d recommend writing a nasty letter to them, but I tried that and it bounced, so ¯\_(ツ)_/¯.

Binary Jazz

A few months ago, this happened:

After several months of thinking, procrastinating, conjuring reasons to not start a podcast and waiting for the idea to sound like a bad one (it didn’t), we decided to get our act together and get serious about the idea. I put together some notes, we came up with a format, we decided on a day of the week to record episodes and we’ve recorded our first three (two are available to download/listen to, one is scheduled for next week).

I have been peripherally interested in podcasting for a while but I am less inclined to do a solo venture (who wants to listen to me talk?), but doing one with a couple of my favorite people sounded like something that would keep my interest for a while. Plus, since our topic changes episode-to-episode there’s less of a chance that it will get tired and boring, both for us on the podcast, and (I hope) for anyone listening.

So far it’s been pretty fun. You should check it out.

Human for a year

I celebrated my 1 year anniversary with Human Made a few months ago. I wrote up a review for our company P2 but realized I haven’t said much over here. The following is a slightly edited version of that one year recap.


Last year, the Monday following Thanksgiving was my first official day as a Human. I think it’s poignant that my anniversary at Human Made falls in line with Thanksgiving (leaving the historical context of the slaughter of thousands of Native Americans aside for a moment) because I have much to be thankful for.

I have a tendency toward antiauthoritarianism. I stopped working traditional 9-5 jobs because I always ended up in these awkward situations where I (intentionally or unintentionally) challenged authority and ended up getting myself into trouble of one sort or another. It happened pretty consistently until ultimately I decided to start freelancing so my only boss was myself. I bring this up because since moving from freelance to agency work, I have gotten into similar situations (though not nearly as extreme) and it comes from having fairly strong opinions and wanting to voice them and then expecting that someone actually listen to and acknowledge those opinions. This was a fundamental difference in moving from a normal “backend developer” to “developer lead” at WebDevStudios — suddenly, when I became a lead, my opinions and thoughts felt like they mattered. People were listening when they weren’t before. And it made me more inclined to try to champion the ideas and opinions of the developers on my team(s) because I knew that I was often their only representative to make sure their ideas were heard.

Imagine how refreshing it feels, now, to be here at Human Made, where — as far as I can tell — we’re all extremely opinionated, we all demand that our ideas be acknowledged and, hey, they actually are!

More than the work, more than the dedication to open source, more than the people — though I love you all dearly — this is the thing I am most thankful for in my first year (of many!) as a Human. The acknowledgement that we are all valuable, that all of us have ideas that are valuable, and that we all deserve to be treated with compassion and understanding and empathy. I truly feel valued here and I am thankful every single day (and sometimes, still, a little amazed — am I dreaming?) to be lucky enough to be part of this truly inspirational and awe-inspiring team.

When I applied to Human Made more than a year ago, I really expected nothing to come of it. I had loads of imposter syndrome but I knew what I wanted and what I didn’t want. I was pretty clear on that, actually. I wanted to be treated with respect. I wanted transparency in the company and processes and I wanted the ability to speak up if I had ideas about the company — or what it was doing — without fear of retribution. I wanted the acknowledgement that I am not my work, I have other commitments — to my own open source contributions, to WordCamp and the local WordPress community, to my family, to my own health and sanity — and that those things  

Human Made was one of the few companies that actually ticked all those boxes. And I was a little shocked and disbelieving when Tom replied to me and that Joe gave me the time of day and somewhere in that process I was given a trial project and that everyone on that project was so amazing and warm and that, despite feeling like I contributed basically nothing to the project because it was such early days, I still hear, a year later, that some of my code is still there and valued by the team.

Back when I was freelancing — which was before Automattic really exploded, when they were still <100 people — I would longingly gaze at their Work With Us page. I would read stories about what the work environment is like, drool over the benefits, talk to Automatticians and generally try to suck up as much information as possible about Automattic. I said, that right there is my dream job. And that was what I aspired toward. I applied numerous times for various positions, went through a couple interview processes, even did a trial project once, but nothing really fit. Eventually, frustrated, I put it on the back burner for a future attempt “when I’m ready”, still ultimately thinking that Automattic was my dream job and that I would apply again, if they’d still have me.

 I no longer think that Automattic is my dream job. Or even remotely close, if I’m honest. My dream job is working for Human Made. And I am thankful to all of you for welcoming me, for valuing me (and each other), and for making this team truly the best to work with and the only gig I ever want to have.


I got an incredible amount of positive feedback for this post, including the following which makes me feel like I’ve found the right place:

You represent Human Made so much for me that I couldn’t imagine HM without you!

Since I wrote this, I met about half the company again at WCUS in Nashville where we hung out, visited a record pressing factory together and had our first US-based end-of-year meal. Every day I feel lucky to be a part of this incredible group of individuals.

 I no longer think that Automattic is my dream job. Or even remotely close, if I’m honest. My dream job is working for Human Made. And I am thankful to all of you for welcoming me, for valuing me (and each other), and for making this team truly the best to work with and the only gig I ever want to have.


I got an incredible amount of positive feedback for this post, including the following which makes me feel like I’ve found the right place:

You represent Human Made so much for me that I couldn’t imagine HM without you!

Since I wrote this, I met about half the company again at WCUS in Nashville where we hung out, visited a record pressing factory together and had our first US-based end-of-year meal. Every day I feel lucky to be a part of this incredible group of individuals.