Chris Padilla/Blog


My passion project! Posts spanning music, art, software, books, and more. Equal parts journal, sketchbook, mixtape, dev diary, and commonplace book.


    I Made A Video Game - AC New Murder

    Overview

    Bucket list item complete — for the past year and a half I've had the chance to develop a game with my sister Jenn!

    Home Screen

    A massive labor of love, AC: New Murder is a web-based visual novel where you are investigating the details of the almost-murder of a villager on the island. In classic Phoenix Wright style, you are helping clear the name of Ñenn, the initial suspect who claims to be innocent. By chatting with the islanders and visiting a real New Horizons island on the Nintendo Switch as a companion to the web app, you'll uncover clues that will help reveal who-dun-it! There are even trials for you to put your knowledge to the test, presenting evidence, and objecting to suspicious testimonies!

    Jenn wrote the script, illustrated the characters, and handled all of the Animal Crossing customizations for the New Horizons island that players are encouraged to visit on their own copy of New Horizons. Her story and characters are dramatic, hilariously funny, and are overflowing with charm and personality!

    Ankha looking frustrated

    My part was designing the application, developing the software, and creating a custom CMS to load in assets, story, and handling any story-related game logic. Also, I wrote some music for it!

    This was a blast to work on! I had the chance to hone my web development chops, take an app from ideation to release, and collaborate on a massive project. Read on for the story of how this came to be!

    Application Features

    When we started planning this in November 2020, we had played around with different ways that this could come to life. A full on web comic would have taken ages with creating individual art assets for the story she had in mind. Software like GameMaker were a viable option, but had limitations for how much genuine control we had over the feel of the game. We also wanted an experience where there were very few barriers to entry — nothing to download, just a link to visit, a la the web experiences we grew up with on sites like Homestar Runner or Homestuck.

    Enter me and my skillset! Working primarily in React and web development technologies, this felt like the perfect opportunity to craft a web app for both the players as well as Jenn to be able to load her pages and pages of dialogue into, controlling also the emotions characters made with each phrase, who they were talking to, what evidence was required, and so on!

    Julian riddling you a riddle

    This project exists as two separate applications, similar to a blog: A client site and a backend portal for editing the application data.

    Our most important features for the app were:

    • Playable game that would save user data as they progressed
    • Accessible from a web browser
    • Allow for branching dialogue paths
    • Handle storing and presenting evidence
    • Showing different dialogue based on different game flags being fulfilled
    • Allow a "Free Roam" mode to ask villagers questions about any item in the inventory

    For the back end application, our CMS features:

    • Intuitive interface of storing different types of data (text, images, and preferences)
    • A way to referencing other documents
    • Flexible UI for hiding any uneccesary fields when inputing game assets
    • Ways of easily updating the schema as features were added without invalidating older documents

    The Tech Stack

    Some tools familiar to me and some new!

    Front End (React, Styled Components, and SCSS)

    My most fluent framework when I began the project. To accomplish that feeling of it being a game and not just a website, using React entirely for rendering pages, transitioning, and responding to state change was a natural choice.

    Page level styling is done in SCSS and component level styles are done in styled components. This was more a change to match the workflow that worked best for me at whichever point I was developing the app.

    Data Layer (Redux)

    I opted for another familiar friend Redux for managing application level state and data. While I investigated options like react-query for caching and making requests, it felt like an oversized solution for our use case. Each page would only need to load once and it was unlikely that players would return to those pages later, so I wasn't concerned about the built in caching. I was much more interested in having a fine-grained control over the structure of the data store and how individual requests and state updates were handled. Although it can be a more tedious solution in the short term, Redux served this end well.

    User data is simultaneously updated in Redux and Local Storage. On load, the app checks for save data in local storage, and then loads it into the Redux store for use while playing the game.

    Agent S keeping it cool

    CMS (Sanity)

    The project was complex enough to reach for a CMS instead of developing my own backend. We needed something flexible enough that would be able to handle our game logic (we weren't just building a blog, after all.) I wanted something that came with an intuitive interface out of the box for Jenn to use. And, JavaScript being my language of choice, I wanted a service that allowed me to develop in my primary language. Sanity checked all of our boxes here!

    With Sanity, Schema development was a breeze. There was plenty of flexibility to reffernce different documents, create unique document types, nest documents within other documents, the whole sha-bang! Querying the data on the client was elegent as well. Sanity uses their own language GROQ, which will feel familiar to anyone that's used GraphQL. All in all, for this project, we're pleased to have gone with Sanity!

    Hosting

    Site hosting and building is done on Netlify. The code for the site is hosted on github. Every time changes are pushed, Netlify will take in the new code, build, and publish the updated site all in one go.

    Sanity takes care of CMS hosting on their platform easily. For our scope, it was straightforward and cheap to implement.

    Collaborating — Left and Right Brain

    It's an oversimplification to say that our roles were focused one hemisphere of the brain — Code is as creative as it is literal, as is illustration and story writing.

    We were coming from different skillsets, though, and were both learning along the way how best to communicate the needs of our side of the project.

    What got us through wasn't so much a particular tool, but more of a mindset of being open to finding creative solutions to walls, accepting limitations where we had them, and creating an accepting environment for ideation and flexibility. Thankfully, we're respectful, kind, and resilient in our lives as brother and sister outside of the project, so this was a natural flow to get into for us!

    Our process for coming together involved meeting regularly to plan our next priorities together, work out what we needed to do individually, and getting feedback on recent steps.

    Not to mention celebrating our wins! My favorite meetings were ones where I could say "I was testing this part of the site, and just saw your new illustrations for Katt! SO CUTE!!!"

    Project Management

    We did end up needing a tool for staying organized! We've been using Notion at work along with their Kanban Boards to keep track of projects at work.

    Pulling back the curtain to show an internal guide written in Notion

    I created a board for our project to keep track of development tasks, and it soon expanded. At the end of development, we had a shared board, road map, and several Sanity guides that I wrote for Jenn to reference when she needed to add in some in-frequently used game logic to CMS

    Wrapping Up

    Growing up, I really wanted my sister and I to collaborate on a big creative project together! I personally envisioned Jenn and I would start a virtual band similar to the Gorillaz, me making music while she illustrated band mates. BUT! This, I think, is even more fun than what I fantasized!

    Cool animal animations!

    I had fun bringing life and interactivity to Jenn's story! It was rewarding to continue to learn and explore the technologies I was familiar with in a new way. We had ups and downs (and left and rights!) during development, but we were able to work through all of our blocks to create something really unique and special.

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Developing A Game In React

    What are the differences between developing a game and making a well organized web application? Surprisingly, there aren't as many as you would think!

    Before starting development on AC: New Murder, I thought the move away from flash on the web meant the end of web games and shows like homestarrunner.

    Gameplay of Elvis getting questioned

    In listening to an interview Drew Conley gave on his game Danger Crew, my hope was sparked. Danger Crew and New Murder both are built on modern web development technology. At the end of the day, a game manages state, controls interactive elements, and renders specific views for different actions, just as a web application does.

    Here I'll be pulling back the curtain on how those similarities came through in developing New Murder.

    Rendering Dialogue

    In this Sanity article we looked at the different data types required for all of the games assets and logics. A majority of that data is dialogue text. And much of the actual game logic in the app is handling what to do with that dialogue, especially when they move towards branching paths.

    I was inspired by Twine, software I discovered way back when Ryan North of Dinosaur Comics fame wrote his first chose-your-own-adventure book. The software breaks down key moments in the narrative into individual nodes. The nodes can connect to each other by different response options and paths.

    The client app for New Murder does the same. A dialogue scene runs through the phrases and animal illustrations. At the end, the user is either prompted for evidence, or is asked to answer a multiple-choice response prompt.

    From there, the user is taken to the next dialogue scene based on their answer.

    Julian Riddling you

    My favorite example of this is Julian's Testimony in the first trial. If I were to graph out the structure, it would look like this:

    0 => 0 => 0 => 0 => Repeat or move on to next dialogue
    |    |
    V    V
    0    0
    |
    V
    0

    By checking the dialogue properties, the game knows whether to render an objection option, and then once following down the next dialogue path, the game also handles when to offer presenting evidence or offering response options. When reaching the end of an "objection" path, the user is then sent back to the main testimony path, one step further than they were before.

    State Management

    This is where I was surprised to see that I felt most at home developing that app from my experience with web development. There were certainly more pieces of state to manage with a game that stored the player's inventory, previously visited conversations, what conversation they were in, what paths they were on, and the list goes on. Managing that state, however, and the best practices for organizing it, very closely followed what goes into a web application!

    UI state, API data, and player save data were stored through separate reducers within the store. The store's structure at a top level was structured like so:

    store/
    ├── app/
    ├── dialogue/
    │   ├── currentDialoguePosition
    │   ├── currentdialogueName
    │   ├── phrases
    │   └── . . .
    ├── conversations/
    ├── inventory/
    ├── health/
    ├── sprites/
    ├── notepad/
    ├── loaded/
    ├── inquiry/
    ├── act3Scenes/
    └── specialEvents/

    State updated in three instances:

    1. On App load, user's save data was loaded in from local storage, where the game continually simultaneously saves when the user makes progress
    2. On page load, the specific conversation or menu screen requests for the relevant data from the API
    3. On interaction, UI changes are made, including progressing through the dialogue, adding to the player's inventory, or receiving an achievement

    All familiar tasks when designing any other web application, such as a user account portal. Even marking their game progress and saving their game data is not too dissimilar from managing form data, just without the inputs and validation.

    A look at Agent S's Notes

    A particularly interesting piece of state are Agent S's notes. These are tasks within the game with particular ways of completing them. An Agent S Note (SNote, for short) is stored into redux like so:

    {
        count: 4,
        description: "Get Lucky to remember yesterday by presenting evidence to him.",
        name: "luckyMemories",
    }

    There are two types of SNotes:

    1. One-offs, requiring only one action to check them off
    2. Counters, requiring multiple actions to check them off.

    In the game, Jenn is able to add points where player's achieve a SNote by adding the SNote's name to the dialogue field. The app knows that when it comes across "luckyMemories" while going through dialogue, to check off the task. If it's a one-off, it's done! If it's a counter, it increments the number of times it's seen "luckyMemories" until reaching 4, then it's complete!

    Achieving SNotes eventually leads to unlocking the final part of the game, where the player needs the most evidence and deepest understanding of the events that transpired. These were a fun way to add direction and celebrate progress through the game!

    Toast messages filling the screen in the mobile app

    Saving Player Info

    As mentioned earlier, I opted to use local storage to maintain player save data.

    I considered the option of spinning up a server, allowing users to create an account, and log in from any device to continue playing the game. For this project's particular requirements, needing a very thorough front end application and a thoughtful crafting of the CMS for Jenn, I was looking to see where I could save time and energy for those two major focuses.

    Most players will likely only play the game on a single device. And save data is not highly sensitive or personal. That considered, using local storage was a reasonable compromise for the project.

    Katt looking cool

    Animations

    The app really came to life when I started adding in animations! I wanted to give as much of a native game feeling to our app as I could. The animations are mostly small transitional touches that add a bit of life and playfulness to the game. The techniques I used were:

    • Text Animation with Typist
    • CSS Transition Animations
    • Keyframe animations
    • Framer Motion Animation Library

    Pure CSS Animations

    Built in CSS animations cover the small touches, such as menus swiping in and icons shaking or bouncing. Below is one of my favorites: Agent S's Notepad.

    Slide in and out effect for SNotebook

    One of the 12 Principles of Animation is Anticipation, a gesture preparing the viewer for an action. The pulling back before springing forward motion here is just that, and was accomplished with this simple line of css:

    .notepad_wrapper {
        transition: transform 0.8s cubic-bezier(0.47, -0.51, 0.46, 1.64);
    }

    Here's another example: keyframes for a bouncing caret icon or the error message shake.

    @keyframes bounce {
      from {
        transform: translate3d(0, 0, 0);
      }
      to {
        transform: translate3d(0, 10px, 0);
      }
    }

    JavaScript Animation

    Since it's a heavy resource, I wanted to keep animating with JavaScript to a minimum. It's worth noting that this is different from JS merely firing off animations that are then handled by CSS. That we'll explore how JavaScript is firing off css animations in the section below.

    The one case where JavaScript was the best tool for animation was with the dialogue text. Character speech was a major part of the game and would be a big contributor to making the app feel like a true native game.

    Dialogue text appearing on screen

    I initially scripted out a vanilla JS type-writer function that would lay out pieces of dialogue character by character. Eventually we went with another package for this functionality to more easily handle non-text portions of the dialogue, such as icon images and text highlighting.

    CSS Transition Group

    Animal illustrations were loaded in dynamically, making them a perfect use case for React Transition Group. Animals slide in and out, sometimes taking center stage, or being pushed off to the side when another animal hops in

    Characters sliding in and out of frame

    There are different classes dynamically set for the animals based on their positioning when they enter. Different animations handle the transition from off stage to center, center to left, and left to right.

    Framer Motion

    The true native feel came from bringing in page transitions with Framer Motion. Each page has a loading screen overlay that swipes in and out between the stages, almost like a curtain for scene transitions.

    Slick Scene Transition

    Here's how it works: Framer Motion has built in functionality for working with React Router. What needs to happen is a handshake for a really smooth transition. After a link is clicked (or an action leads to switching pages), Framer Motion intercepts the request to React Router. It delays the new page from loading until the exiting animation has occurred.

    Then, the next page loads. The next page also has an on load animation, mirroring the exiting animation of the previous page. So on the first page, the loading overlay swiped in. On the next page's load, a copy of the overlay is displayed and swipes out.

    Mobile and Desktop View

    With much of my time and energy focused on handling game logic and creating the CMS, the flexibility of the game's UI is another area where I had to eventually make a compromise time-wise.

    Our initial ambition was to create an application that looked elegant on all devices. Phones, tablets, and desktop. When working with primarily text and an endless scrolling page, there's a lot of room for flexibility and responsive design. When it came to developing a game with specifically sized assets and menus, it became more complex.

    Ultimately, I was able to create two views: Desktop and Mobile, with tablets rendering the desktop version.

    Desktop has a fixed window for viewing the game:

    Desktop view of the app

    Mobile is a full screen rendering of the game:

    Mobile view of the app

    The Mobile version was optimized for most popular dimensions, iPhones being my basis. Things got strange with tablet dimensions, however, and it became difficult to handle a very particularly staged set of character positions, menus, and fixed background size in this size range.

    That's what lead to showing the desktop version on tablet devices. We had hoped for an experience similar to mobile, but given the constraints of a position sensitive layout with less flexibility, it's another one I saw as worth taking.

    Wrapping Up

    There's much to be proud of with this application, but I'm happiest with how the front end turned out! Working through game logic was an interesting puzzle. Managing the application's state and performance was delightfully surprising! The process drew from my experience working in web development seamlessly. And the small touches of animation help make the story come alive. Even deciding on trade offs boosted my confidence in my ability to work with the resources I had to create a fun piece of software.

    Merengue questioning Agent S's Question

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.

    Sources

    Building Steam Games With React

    Drew Conley

    Danger Crew

    Twine

    Ryan North On ‘To Be Or Not To Be,’ ‘Adventure Time,’ And Why Books Where You Don’t Choose The Adventure Are For Babies

    To Be Or Not To Be


    Using Sanity as a Level Maker

    Something that goes under appreciated when it comes to developing anything — games, websites, music — is how much time goes into making the tools that you need to make your project.

    Musicians familiarize themselves with scales and harmonic language.

    Developers use libraries, frameworks, and even still, build their own reusable components on top of it.

    And Game Makers develop level builders.

    And so, that's what I did for my collaborative visual novel!

    I mention in my previous article the why and how of choosing Sanity as our backend CMS. Here I'll be describing the though process behind developing the Schema for the game along with how some of that translated into the game.

    Schema Development

    Sanity is a document based database. The structure is not too different from MongoDB. You have a collection of documents, which can have nested elements to them, and that can connect to other documents relationally. We have several collections for the game:

    Content
    ├── Dialogue
    ├── Inquiry
    ├── Conversation
    ├── Emotions
    ├── Animal
    ├── Asset
    ├── Animal Images
    ├── Item
    
    etc.

    Here's an overview of the schema in action:

    Conversations => Dialogue (contains phrases)

    Phrase => Text => Speaker => Emotion.

    Let's dive into a few to get a sense for the game's structure.

    Dialogue

    The heart of the game is text. So this was a jam-packed Schema.

    Dialogue objects look like this:

    {
        name: String,
        Conversation: ConversationID,
        phrases: Array,
        responseOptions: Array,
        isFinalDialogue: Bool,
        ...
    }

    There are a few other properties, but the important ones are displayed.

    Each dialogue is linked to a conversation. The conversation is the equivalent of a whole scene with an animal, say, you talking to Ankha in the first act. Conversations are broken up into separate dialogues, with player responses being the separator.

    Within each Dialogue is a list of phrases. A little More on those later, but essentially, these are the individual lines of text, about 2-3 sentences.

    Response Options are displayed at the end, giving the player the chance to branch down multiple paths, if needed.

    The last dialogue in a conversation is marked with as is final dialogue to signal to the game that it's time to transition to another page.

    Phrases

    These are a subdocument, so they don't have their own collection. But, their schema is pretty interesting! A lot is packed in here:

    {
        text: String,
        emotion: emotionID,
        speaker: animalID,
        centeredAnimal: bool,
        animalOrientation: "left" || "right",
        specialEvent: eventID,
        ...
    }

    Again, a bit of a simplification here. But these are the main components:

    Text to render, who is speaking so we can highlight their sprite and show their name in the dialogue box, and several other positioning properties.

    Achievements, mission clearing, and being given an item is handled in our special event property. When the game comes across this point in the conversation, it looks up the event and has special logic for handling it.

    Linking Dialogues

    Handling multiple branches took some doing, but stayed a lot less complicated thanks to the modular nature of our schemas!

    As mentioned above, we're able to go down branching paths depending on the dialogueID that's linked to a given response option. This is also possible when presenting evidence.

    There's a point in the game where it's a bit of a free for all. Animals can be asked about any item in the users inventory, sometimes providing different dialogue depending on what the user has already achieved in the game. Implementation here was largely similar to the above - link items to a specific dialogue document. BUT, if a prerequisite event is required to see a secondary response, make that check first.

    The nitty gritty of that implementation is too large for a single blog post. It all comes back to this main implementation of separate dialogues linked relationally by items or response options.

    Customizing the Back End Client

    Sanity Studio was great to work with! Even for contorting it to suit our needs for level building, it was flexible enough to accommodate us.

    Here's a quick peak at what a dialogue document looked like in Sanity:

    Sanity Studio

    Conditionally Rendering Input Elements

    We had a few situations of "If prompting for evidence is true, we also need these fields to show."

    Sanity also handled this really nicely! It came in handy as a reminder to Jenn what was and wasn't needed in certain game scenarios by having the back end client nudge her in the right direction.

    Here's an example of what this looks like in Sanity's schema, from their documentation

    {
      name: 'link',
      type: 'object',
      title: 'Link',
      fields: [
        {
          name: 'external',
          type: 'url',
          title: 'URL',
          hidden: ({ parent, value }) => !value && parent?.internal
        },
        {
          name: 'internal',
          type: 'reference',
          to: [{ type: 'route' }, { type: 'post' }],
          hidden: ({ parent, value }) => !value && parent?.external
        }
      ]
    }

    Wrapping Up

    There was a fairly large upfront cost of planning out how to structure the game and enable Jenn to add in assets and story. We saw it evolve pretty quickly as the game grew and grew in complexity. Taking that time upfront helped save a lot of headache down the road! I'm grateful to have used a system that was resilient enough to change as our needs inevitably pivoted.

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Project Management for Game Development

    Project Managing

    Guides

    Our CMS continued to grow and grow. Often times with features that were important, but only needed using a handful of times.

    In most cases, the CMS had descriptions and self-explanatory field names that would guide Jenn's work. And then there were a few that needed a bit more explaining. I realized we needed Documentation.

    I wrote a handful of guides with images and gifs to serve as reference for those less used features. I would write them out as I finished a feature, then hop on a call with Jenn to talk walk down the guide. I would screen share my own computer working through an example so there was a clear picture of how it worked in Sanity, and how it would end up looking in our main application.

    Screenshot of a Notion Guide on Positioning and Phrases

    Communicating Trade Offs and Keeping an Eye on the Big Picture

    Towards the end of the project, feature creep continued. We had to continue discussing what an end product looked like.

    At the start of the project, I was here to serve. Anything we could dream up, I wanted to implement. I didn't mind diving deep into small bugs and rise with excellent solutions.

    Nearing the end of the project, I was excited to release, and saw the beginnings of perfection robbing us of a "good enough" application.

    My role towards the end of the project shifted towards a more informed leader in the technical domain. I saw myself switch from "I think we can do all of that!" to "Ok, let's talk about what value that feature would add to the game in conjunction with the time and trade offs adding that in presents."

    I was able to help us come to some hard calls, asking for creativity on Jenn's side to find story solutions to issues that were not so easily solved through the tech.

    Technical Debt

    In many ways, I'm happy with how the code has stayed modular and organized. Additions were easy to add in many portions of the code, including the schemas, inventory menus, and at a page level.

    And still, the project acquired it's fair share of technical debt.

    My time and resources for the project began to shrink as we neared the end, and I became less and less able to maintain it in the way that I would like. Getting the application done and out in the world took precedence over a pristine, DRY codebase.

    This has been a big lesson for me. Ultimately, I stand by the choice to focus on implementation over maintenance for this project — neither of us are planning to return to it. I also don't intend on releasing the code as any sort of basis for other people to use this as a game engine anytime soon.

    At the same time, I now deeply understand what technical debt looks and feels like, how it can come about gradually, and that for a project that's meant to be sustained and continually iterated on, it's invaluable to take the time to maintain, refactor, and plan for the future.

    Wrapping Up

    This was my greatest area of growth. I picked up some great technical skills, certainly. Actually managing the project, triaging feature requests, sticking to an MVP, and communicating to a non-technical collaborator — that's a whole suite of juicy soft skills.

    My experience with teaching music came in handy here! Lots of overlap from planning out the next step in a beginner saxophonist's education. And, some interesting new ways of adapting when it came to shipping an entire application!

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Iwata on Creative People

    Cover Image for Iwata on Creative People

    I grew up with a lot of the games and consoles developed by the late Satoru Iwata, Nintendo's former President. "Ask Iwata" is a sort of auto-biography, a collection of interviews Iwata gave for Shigesato Itoi's company / site with a few excerpts from the official Nintendo "Iwata Asks" interview series.

    The book came across as surprisingly personal for someone that's a sort of legend in the space. Some characters garner mysticism through the distance between them and their audience, but Iwata was a proponent of transparency and open communication, as is evident by all that's shared in these interviews.

    The war stories are impressive. His early experiences of being a tech wiz, saving a couple of games like Earthbound and Pokemon Gold and Silver from development hell, are a testament to him being a master developer.

    But it was his view on people that struck me.

    It's not what I expected going in, though he did spent more of his life in management. Iwata transferred much of his logical thinking and desire to delight others over into his leadership.

    Here's one example of this is his insight onto why creative folks can come into conflict on a shared project:

    Unless they have the self-confidence to announce, "I'm the best," engineers and artists never make any headway. Most programmers, too, believe their way of doing things to be superior. When these kinds of people join forces on a project, some conflict is unavoidable. Creativity, after all, is an expression of the ego.

    Really insightful to frame creativity as a means of putting a piece of ourselves out there.. Maybe not all forms of creativity, but certainly design centric, engineering type roles, take a bit more of that characteristic to it.

    Here's another one on respecting the talents of others:

    "There will always be people who see things differently than you. Perhaps to an unreasonable degree. Still, these people surely have their own reasons, their own history and values. Moreover, they're bound to be able to do things you can't do, and know things you don't know. This doesn't mean accepting everything they suggest, but respecting the fact that they have skills you lack, and are doing things that you can't do yourself. Whether or not you can maintain this respect will vastly influence how much fun and fulfillment you get out of a job."

    I plead guilty to the artistic trope of wanting to be good at it all! In an age (and, for me, a stage of life) that glorifies remarkable individuals, this was a refreshing passage to come across. It's that gradual opening up of accepting yourself for your limitations and celebrating the differences with others that brings buoyancy to work and opens up possibilities.


    Customizing Field State in React Final Form

    React Final Form is a delightful library. So much comes out of the box, and it's extensible enough to handle custom solutions.

    I've been working with it a fair amount recently. Here's what adding custom logic looks like:

    Scenario

    Say that you have data that needs to update two fields within your form. An example might be that you have an event form with the fields:

    • Name: Skate-a-thon 2022
    • Start Date: August 3rd
    • End Date: August 5th

    Let's also say that Skate-a-thon got rescheduled to another weekend.

    When going in to push back the start date, we want our form logic to automatically update the end date as well - to also go back 7 days.

    Simple Set up In Final Form

    I'll leave the initial set up to the React Final Form docs.

    Let's pick up with a component that looks like this within our form:

    <h2>Render Function as Children</h2>
    <Field name="name">
      {({ input, meta }) => (
        <div>
          <label>Name</label>
          <input type="text" {...input} placeholder="Name" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>
    
    <Field name="startDate">
      {({ input, meta }) => (
        <div>
          <label>Start Date</label>
          <input type="date" {...input} placeholder="Start Date" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>
    
    <Field name="endDate">
      {({ input, meta }) => (
        <div>
          <label>End Date</label>
          <input type="date" {...input} placeholder="End Date" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>

    We're assuming we already have our <Form> wrapper and submit button elsewhere.

    So far with this code, all of our fields will update independently.

    To tie together our endDate field with the startDate, we'll need a custom onChange method and a way to access the form API ourselves.

    Form Hook

    Two hooks come in handy here:

    • userForm(): Gives us all access to utility methods for our form. It can be called in any component within the <Form> wrapper.
    • useFormState(): As the name implies, gives us access to the current state, including values and meta data such as what fields have been "touched"

    We'll open up our component with these hooks:

    import React from 'react';
    import {Field, useForm, useFormState} from 'react-final-form';
    
    const FormComponent = () => {
      const formApi = useForm();
      const {values} = useFormState();
    
      return(
        ...
      )
    };

    And then use these in a custom onChange handler on our field

    <Field name="startDate">
      {({ input, meta }) => (
        <div>
          <label>Start Date</label>
          <input
            type="date"
            {...input}
            placeholder="Start Date"
            // Custom onChange below
            onChange={(e) => {
              const newStartDate = e.currentTarget.valueAsDate;
    
              // Lets assume I've written a COOL function that takes in the
              // Initial values for startDate and endDate, and calculates a
              // new endDate based on that
              const newValue = calculateNewDateBasedOnNewStartDate(newStartDate, values);
    
              // Update both values through the formApi
              formApi.change('startDate', newStartDate)
              formApi.change('endDate', newValue)
            }}
          />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>

    There you go! The endDate will update alongside the start date from here!


    Keeping it 200 — HTTP Status Codes

    I was getting a 503 waiting for my Gmail to load, so I decided to learn more about status codes while I waited!

    Response Classes

    1. 100-199: Informational Responses
    2. 200-299: Successful Responses
    3. 300-399: Redirection Messages
    4. 400-499: Client Error Responses
    5. 500-599: Server Error Responses

    Noteworthy Codes

    • 418: I'm a teapot Short and stout

    • 302: Found For temporary changes in URI, as opposed to the more permanent 301.

    • 300: Multiple Choices Where there's more than one possible redirect. The best way to handle this is to send an HTML page with possible choices for handling the response. But then, at that point, wouldn't you just sent a 200 status code with a landing page? I could see this being more useful with APIs than webpages.

    • 202: Accepted The request is received but not acted upon. Interestingly, HTTP doesn't have a way to handle an asynchronous response that ties itself to the delayed response to the request. I'm thinking of AWS batch uploading here - where the API accepts the data, but then needs to take however long to actually process the request.

    • 502: Bad Gateway Technically the appropriate response if the server gets an invalid response when waiting on an external request.

    • 404: Not Found Had to finish with my favorite.


    Cleaning Local Branches

    It's not spring, but it is time for cleaning! Here's a pretty simple way to clear up local git branches.

    $ git branch -d $(git branch | grep -v "develop\|master")

    Breaking It Down

    $ git branch -d <branch>

    This deletes the branch locally.

    If you wanted to delete remotely, this command does the trick (be mindful you're not deleting teammates branches, though):

    $ git push origin --delete <branch>

    To fill in multiple arguments, there's a few pieces of glue needed:

    • We'll use the $() command substitution syntax in Linux to fill in our list of branches
    • Within it, we'll pipe all the branch names from git branch
    • That will filter through our regex search through grep of all branches that aren't develop or master. (Inverted with the -v flag)
    • "develop|master" matches all branches that aren't develop or master.

    Testing the Regex

    To make sure the right branches are being grabbed, removing the outer delete command will return the list of branches:

    $ git branch | grep -v "develop\|master"

    Pruning Deleted Remote Branches

    Removed remote branches can be cleared from your local computer with this command:

    git fetch -p

    DIY Analytics & CORS

    I've been exploring analytics options. I have a use case for them, but we're more concerned with specific user behavior on this project. We want to know if they click a certain button, or make it to a certain page.

    There are some options. Google Analytics provides journeys and goals, though it's heavy handed for our use case. Other solutions like Fathom would keep track of individual page performance, but there are certain UI interactinos that we're interested in.

    So the need arose! I wrote a custom solution for our app.

    Overview & Stack

    We're using React on the client side. Since button interactions are our main metric, we essentially need something that can be integrated with our click handlers.

    Easy enough! We can fire off a POST request to an external API that records the interaction.

    For the API, I opted to spin up a Next.js API. A single serverless function may have been more appropriate, but I was short on time and know that I can deploy quickly with Next and Vercel.

    For storing data, I created a new Database with MongoDB Atlas. Similarly here, this may more than what we really need, but familiarity won out!

    Client Side

    In my React app, I'm adding this utility function that fires when ever I want to record an action:

    export const recordInteraction = (type) => {
      const data = { type };
    
      fetch('https://analytics-api.vercel.app/api/mycoolapihandler', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      })
        .then((response) => response.json())
        .then((data) => {
          console.log('Success:', data);
        })
        .catch((error) => {
          console.error('Error:', error);
        });
    };

    type here is simply a string for what event is recorded. It could be "Submits form", "turns on dark mode", or any unique way of identifying an action!

    The rest is your run of the mill fetch request handling.

    API handler

    On the other side of our endpoint is this handler:

    import { connectToDatabase } from '../../lib/mongodb';
    
    export default async function handler(req, res) {
      await runMiddleware(req, res, cors);
    
      if (req.method === 'POST') {
        const { client, db } = await connectToDatabase();
    
        const myObj = {
          date: new Date(),
          type: req.body.type,
        };
    
        const dbRes = await db
          .collection('acnm')
          .insertOne(myObj, async function (err, res) {
            if (err) throw err;
            console.log('1 document inserted');
            await client.close();
          });
    
        res.status(200).json({ message: 'Data recorded' });
      } else {
        res.status(200).json({ name: 'Ready to record' });
      }
    
      return res;
    }

    First, you'll notice runMiddleware(). I'll explain that in a second!

    If we receive a POST request, we'll do the following:

    • Connect to the DB (largely borrowing from Next.js's great example directory for setup)
    • Create myObj, a record of the current time of request and the type of request.
    • insert it into the database
    • return a success message

    CORS Middleware

    There are a few steps I'm skipping - validation, schema creation, sanitization. But, this api is only going to interact with my own application, so I'm not concerned about writing a very extensive request handler.

    The way I'm keeping it locked down, out of harms way from the world wide web, is through CORS.

    An incredible thorough look at CORS is available at MDN. For our purposes, we just need to know that this is how our API will whitelist only our application's url when receiving requests.

    Back to the runMiddleware() method! Here is the function declared in the same document:

    import Cors from 'cors';
    
    // Initializing the cors middleware
    const cors = Cors({
      methods: ['POST', 'GET', 'HEAD', 'OPTIONS'],
      origin: ['https://mycoolapp.netlify.app', 'https://mycoolapp.com'],
    });
    
    function runMiddleware(req, res, fn) {
      return new Promise((resolve, reject) => {
        fn(req, res, (result) => {
          if (result instanceof Error) {
            return reject(result);
          }
    
          return resolve(result);
        });
      });
    }

    The cors npm package is a great way of managing CORS without getting into manipulating the headers directly. In our instantiation, we're passing a few options for approved methods and the origins we want to white list. (NO trailing slash, FYI!)

    runMiddleware() is the simple wrapper function that handles us using the cors middleware with our request.

    Using the Data

    The nice thing about using Mongodb, or any full blown DB with a sophisticated querying language, is the ability to make use of your data! Our model only has a few simple terms:

    {
        _id,
        date: Date,
        type: String,
    }

    But, that's plenty for us to be able to answer questions such as "How many people submitted a form in the last month." A perfect solution for our case.


    Storing Keyword Arguments in Python Class Instantiation

    Here's the sitch: We recently had an all hands project in Python where we needed to create a pretty flexible internal package.

    Part of that flexibility means not always knowing all the arguments we're instantiating with, but knowing that if certain arguments are available, we want to use them in our methods.

    Here's what I mean:

    class PizzaOven(object):
        def __init__(self, **kwargs):
            # Take in toppings and store for later.
    
        def bake(number):
            for x in range(number):
                if self.pineapple and self.ham:
                    print('Here's one Hawaiian Pizza')
                else:
                    print('Here's your tasty pizza')

    We have a class that will receive toppings as keyword arguments, and our method later on may need to extract those arguments to know if it's a Hawaiian Pizza. In another case, if we're dealing with a third party api, we may need to plug those values into a method in a certain order, or only use them if they're provided, like an options dictionary.

    Static Dict Property

    When constructing classes, python stores it's writable attributes to the __dict__ class attribute.

    Doing this:

    class PizzaOven(object):
        def __init__(self, **kwargs):
            self.ham = kwargs.ham

    Is somewhat like doing this:

    class PizzaOven(object):
        def __init__(self, **kwargs):
            self.__dict__["ham"] = kwargs.ham

    The code above would error out since that's simply not how we do things around here! But we'll cover how this concept helps out with keyword arguments below.

    Updating Dict with Keyword Arguments

    By using the update dictionary method, we can take in an object that instantiates the class with our toppings and store them as instance variables.

    class PizzaOven(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)

    Then our toppings are made available later:

    class PizzaOven(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    
    dominos = PizzaOven(pepperoni=True, ham=True)
    
    dominos.bake(1) # "Here's one Hawaiian Pizza"

    Bon Appétit!


    Adding Background Music to Websites

    I'm working on a project where I'm adding my own music for each page in a React app.

    It has me nostalgic for the early internet. You would be cruising around, and all of a sudden, someone's Live Journal would have a charming MIDI of Enya's "Only Time" playing in the background.

    I'm definitely glad this isn't the norm anymore. I don't miss having to mute the obnoxious banner ads that included sound on Ask Jeaves. But now that the web is largely without sound as a background to pages, I've really enjoyed how it's bringing parts of this application to life!

    Quick Overview

    The project isn't out yet, so for the heck of it, let's call my React app "Music Box."

    The music is hosted on the Music Box's CDN (Sanity, in my case). The ideal format would be webm as it works across all modern browsers and is highly performant. For my use case, mp3's suited me just fine.

    In my codebase, I have a big ol' object that stores the track URL's and which page ID's they should play on. It looks something like this:

    const sounds = [
      {
          name: 'Bake Shop',
          src: 'https://cdn.sanity.io/files/qvonp967/production/f4163ffd79e09fdc32d028a1722ef8949fb31b85.mp3',
          conversationIDs: [
            '27f4be58-38f3-4321-bbc9-c76e0c675c36',
            'd008519f-16c0-4ef0-b790-f5eb0cb3b0b4',
          ],
          howl: null,
        },
        {
          name: 'Restaurant',
          src: 'https://cdn.sanity.io/files/qvonp967/production/4606e7ec6208df214d766776e3d5ed33408fe74d.mp3',
          conversationIDs: [
            'e1688c5f-218a-4656-ad96-df9a1c33b8f8',
            'a81fb6a7-d450-45e8-a942-e5c82fb1a812',
          ],
          howl: null,
        },
        ...
    ];

    You'll notice each object also has a howl property. Let's get into how I'm playing sound:

    Playing Audio with Howler.js

    Howler.js is a delightfully feature-full API for handling sound with JavaScript. The library is built on top of the Web Audio API and also uses HTML5 audio for certain use cases. While I could have interfaced with the Web Audio API directly, Howler has much nicer controls for using multiple sounds, interrupting them, and keeping separate sound instances contained in a single sound palette.

    For each page, we initiate the aproriate sound with this code:

      const initiateSound = (src) => {
        const sound = new Howl({
          src,
          loop: true,
        });
    
        return sound;
      };

    src here is derived from the url. The loop option is turned on so that we get continuous music.

    Changing Audio Page to Page

    This is all kept in a SoundController component at the top level of the react tree, above React Router.

    function App() {
    
        ...
        return (
        <>
          <SoundController />
          <Switch location={location} key={location.pathname}>
              <Route
                path="/testimony/:id"
                render={(props) => <Testimony match={props.match} />}
              ></Route>
              <Route path="/act-one">
                <ActOneTestimonySelect />
              </Route>
              ...
          </Switch>
        </>
        )
    };

    The main reason for this is so we have control over fading in and out between pages.

    The other reason is for caching. Remember the howl properties in the sound array? That array is going to be stored in a useRef() call in the SoundController component. Then we can save each instantiated sound with the appropriate element in the array for future reference.

    That's exactly what is happening here inside the useEffect. This code listens for a change in the currentTrackObj (triggered by a page change) and checks if we have a cached howler instance. The cache version is targeted if so, and a new one is played if not.

      useEffect(() => {
        if (currentTrackObj) {
          let howler;
    
          if(currentTrackObj.howl) {
            howler = currentTrackObj.howl;
          } else {
            howler = initiateSound(currentTrackObj.src);
          }
    
          currentTrackObj.howl = howler;
          howlerRef.current = currentTrackObj.howl;
          if (soundPlaying) {
            howlerRef.current.play();
          }
        }
    
        return () => {
          if (howlerRef.current && howlerRef.current.stop) {
            howlerRef.current.stop();
          }
        };
      }, [currentTrackObj]);

    Playing and Pausing

    The state for this is stored in redux as soundPlaying. When that's toggled, we can interface with howler to play and pause the track.

      useEffect(() => {
        if (!playedAudio) {
          dispatch(setPlayedAudio(true));
        }
    
        if (howlerRef.current && howlerRef.current.playing) {
          if (soundPlaying && !howlerRef.current.playing()) {
            howlerRef.current.play();
          } else {
            howlerRef.current.pause();
          }
        }
      }, [soundPlaying]);

    Then that's it! Musical bliss on every page!


    Error Tracking

    Solo deving a fairly large app has humbled me. Errors and bugs can sneak in. Even professional software ships with errors. I've had to accept that it's part of the feedback process of crafting software.

    A lot of the development process for me in this project has been as follows:

    1. Ship new feature
    2. Collaborator tests it and finds a bug
    3. Collaborator tells me something is broken, but with no code-related context they can share
    4. The hunt ensues

    Not ideal! I've been looking for ways to streamline bug squashing by logging pertinent information when errors occur.

    Swallowing pride and accepting a path towards sanity, I've integrated an APM with my app. Here are the details on it:

    Using Sentry

    I opted for Sentry. I did some research on Log Rocket and Exceptionless as well. All of them are fine pieces of software!

    Log Rocket includes a live action replay. Exceptionless provides real time monitoring. And Sentry is focused on capturing errors at the code level.

    For my needs, Sentry seemed to target exactly what I was experiencing — purely code-level issues.

    Integration

    Integrating is as simply as a few npm packages and adding this code to the index.js:

    import React from "react";
    import ReactDOM from "react-dom";
    import * as Sentry from "@sentry/react";
    import { Integrations } from "@sentry/tracing";
    import App from "./App";
    
    //Add these lines
    Sentry.init({
      dsn: "Your DSN here", //paste copied DSN value here
      integrations: [new Integrations.BrowserTracing()],
    
      tracesSampleRate: 1.0, //lower the value in production
    });
    
    ReactDOM.render(<App />, document.getElementById("root"));

    One tweak I had to make was to set Sentry to only run in production. (I'm fairly certain I'll see the errors in development, thank you!)

    if (process?.env.NODE_ENV === 'production') {
      Sentry.init({
        dsn: "Your DSN here", //paste copied DSN value here
        integrations: [new BrowserTracing()],
    
        tracesSampleRate: 1.0,
      });
    }

    With that bit of code, Sentry then comes with the stack trace, the OS and browser environment, potential git commits that caused the issue, and passed arguments. All sorts of goodies to help find the culprit!


    Analytics - Accuracy and Ethics

    I don't personally use analytics on this site. I'm not here to growth hack my occasional writing for ad space. But I am involved in a couple of projects where analytics is good feedback for what we're putting out. So I did a little bit of a deep dive.

    Accuracy is Suspect

    Uncle Dave Rupert and Jim Nielsen have striking comparisons between their different analytics services. The gist is that they are serving up WILDLY different data, telling different stories.

    It's not just that Netlify numbers are generally higher than Google Analytics, either. If you follow one service, the data could tell you that you had fewer visits this month, while the other claims you had more.

    Part of this is because of the difference between how the data is gathered.

    Server Side Analytics measures requests. Client Side loads a script on page load.

    There are pros and cons to both. Client side analytics can better map sources of leads and measure interactivity, but is prone to JS being turned off or plugins blocking their usage. Server Side is prone to inflated numbers due to bot traffic.

    So it seems like the best solution is to have multiple sources of information. Of course that extends to having more metrics than purely quantitative, as well.

    Privacy and Ethics

    Tangentially, there are some ethics around choosing how to track analytics and who to trust with this.

    It's an interesting space at the moment. Chris Coyier of CSS Tricks has written some thoughts on it.. I feel largely aligned. The gist is: aggregate, anonymous analytics is largely ok and needed in several use cases. Personally identifiable analytics are a no-no.

    But I understand that even this “anonymous” tracking is what is being questioned here. For example, just because what I send is anonymous, it doesn’t mean that attempts can’t be made to try to figure out exactly who is doing what by whoever has that data.

    This is key for me. History has told us that if we're not paying for a service, we are likely the product. And so, any analytics service that doesn't have a price tag on it to me is a bit suspect.

    I can't say I have any final conclusions on that matter. Nor any say that X is right and Y is wrong, I have no shade to throw. But as I step more and more into positions where I'm a decision maker when it comes to privacy, I'm working to be more and more informed, putting users best interests at the center.


    Git Hygiene

    My recent projects have involved a fair amount of disposable code. I'll write a component for an A/B test, and then it needs to be ripped out after the experiment closes.

    Git has simplified this process beautifully!

    I could manually handle the files, deleting line by line myself. But git makes it so that I can run a few commands in the CLI to revert everything.

    Here's my workflow for it:

    Modular Commits

    I've been guilty of mega commits that look something like this:

    git commit -m "render revenue data to pie chart AND Connect ID to Dashboard AND move tiers to constants file AND ..."

    I've recently made the switch to breaking out any instance where I would want to put an "and" in my explanation of the change into it's own commit. So now my commits will look more like this:

    $ git commit -m "render revenue data to pie chart"
    $ git commit -m "Connect ID to Dashboard"
    $ git commit -m "Move tiers to constants file"

    There are loads of benefits to this. To anyone reviewing my code, it's far easier to follow the story told by my commits. Isolating a breaking change is much easier.

    The best, though, is that it's WAY easier to isolate a commit or few that needs to be thrown out later.

    Revert Commits

    The word comes back from marketing: The first A/B test was a success, but the second needs taking out.

    If a single commit needs changing, it's as easy as this:

    $ git revert 9425e670e9425e66d61c8201...

    git revert will then create a commit with the inverse of those changes.

    Usually, I need to do this with multiple files. The workflow isn't too different:

    $ git revert --no-commit 820154...
    $ git revert --no-commit 425e66...
    $ git revert --no-commit 9425e6...
    $ git commit -m "the commit message for all of them"``

    Push and merge from there!


    Fluency

    I'm thinking a lot about this thread by multi-instrumentalist and composer Carlos Eiene

    For me, this is the key phrase:

    Where is the fluency line with an instrument? ... I think a closer answer is having the necessary abilities to effectively communicate in whatever situation you may be in. And if you're in a vacuum, learning an instrument by yourself without ever playing it for or with others... you don't get the chance to communicate musically.

    (Putting aside the whole argument for or against language as an analogy for music here.)

    In Music

    This is such a given in music school. You are jamming with musicians all the time, getting feedback, and performing alongside each other all the time.

    For me, it's been interesting transitioning musical communities.

    The main point of the thread is to deemphasize practicing for the sake of mastery alone. To focus on how you serve musically and how you can still effectively communicate with other musicians.

    I'm thinking a LOT about the inverse, though. How do you find that same community and immersion in a musical context that's a lot more individualist than, say, being in a concert band or jazz combo? Where does the feedback come from there?

    When it comes to writing music, I feel like it's much more in the vein of how I imagine authors write. Or Jazz musicians working on transcriptions, actually. You're not limited by time or space. You are communicating and riffing off of someone's ideas that could be from decades ago. I think a present, accessible community is of course important. But online communities are much more lightweight than when you're in a group that rehearses every week together. And so, filling in the gaps takes working with recordings and materials.

    Speaking as an ambivert, this way of connecting musically is pretty amorphous. The buzzword now is that many relationships online are "parasocial." And don't get me wrong, there's beauty to it, too. I love being able to transcribe a Japanese musician's X68000 chip music so easily and readily, there's an interesting kind of intimacy to that engagement with music. The feedback and communication is strange, though. It's not direct communication, and the community, again, is less tangible.

    Anyhow — sometimes I miss in person music making. Maybe I shouldn't expect writing music to be the same kind of fulfilling. For me, the lesson is that music is multifaceted. Different acts in music can balance each other out. We write to express individualism. We perform to connect with a larger community.

    In Code

    This got me thinking with code languages as well.

    There's a spectrum. Folks who are renaissance devs, those who have dipped their toes in many technologies, are fluent in multiple languages and frameworks, etc. And there are folks who are highly specialized.

    Namely, in web development, is it worth going broad or focusing in?

    (Short answer: go T Shaped)

    The answer comes from community, or maybe more importantly, what your problems are your clients grappling with?

    That, too, is a spectrum. If you're aiming for the big companies, python, data structures, and a CS degree in your back pocket helps. If you're doing client work, breadth wins out. If you're an application developer, it may be a more focused in set of JS centric technologies.

    Like music, the field is too large and varied to really say one size fits all.

    No matter what, though, mastery isn't necessarily the goal. Here, it is fluency.

    Some projects may require that intimate knowledge of JS runtime logic.

    Others may only need some familiarity with JQuery.

    The interesting thing about this field, in my mind, is that it's a lot less about working towards a specific target for fluency, but using the tools you have to solve a problem for your collaborators.

    Learning is a natural part of that process. So there is both a really tight feedback loop and there's natural growth and development built in.

    (Again, caveat here to say it's not an excuse to slack on developing your skills. But working towards fluency can keep it so that you are working to master relevant skills vs. simply being virtuosic in an irrelevant way.)

    Back to Music

    The difference here is that software solves a direct problem for someone else. It's creativity with a practical outcome. With music, there's more magic. ✨ The outcomes are less clear, the people you serve and communities you entangle with are less defined. The benefits, even, are vague at times.

    Except, y'know, your soul grows in the process. And simply being creative in the world and sharing that creativity can lead to inspiring others to do the same.