Christopher M. Boyer
  1. 2026-03-17

    Autonomous.ai released an AI product called Intern. It’s a packaged Open Claw instance that lives on your desk.

    I’m not a Claw person, so this product already isn’t for me, but I definitely don’t want this lol. What kind of person wants a sinister, glowing pyramid on their desk 24/7?

    The clearly AI generated product image feels so unnecessary. You have the product. There are product images on the page. It feels so unnecessary.

  2. 2023-07-24

    Over the last year I’ve been developing a design system / component library to use in my side-projects. At the base is a component called Block, upon which most of the styles get applied. The interface of Block focuses on configuration of atomic properties at the React component prop level. So for example, setting the padding and margin of a button is done like this:

    <Block padding="0.25" marginRight="1" tagName="button">
      Hello World
    </Block>
    

    I wanted the implementation to have a very strict, type-safe interface, but the repetitive nature of it was starting to weigh on me.

    interface BlockProps {
    	padding: string;
    	paddingTop: string;
    	paddingBottom: string;
    	paddingLeft: string;
    	paddingRight: string;
    	margin: string;
    	marginTop: string;
    	marginBottom: string;
    	marginLeft: string;
    	marginRight: string;
    }
    

    Maintaining this was time-consuming, and updating did not scale particularly well. Plus margin and padding props were basically the same except their name. I resolved to abuse every feature TypeScript afforded me to do this. At the center of it all is this type:

    export type Mapping<T extends string, R = string> = {
        [K in T]: R;
    };
    

    This is a mapped type. It allows me to define a type that has the keys R, and the type T for each of those keys (It was also brought to my attention that Mapping is basically just a backwards Record, so use that instead if you intend to walk this path). For padding, to have each directional prop and an overall padding, I’d define something like this:

    type PaddingProps = Mapping<string, 'padding' | 'paddingTop' | 'paddingBottom' | 'paddingLeft' | 'paddingRight';
    

    which is equivalent to

    interface BlockProps {
    	padding: string;
    	paddingTop: string;
    	paddingBottom: string;
    	paddingLeft: string;
    	paddingRight: string;
    }
    

    Looking at this, I wondered if I could decouple padding from Top, Bottom, Left, and Right, especially as I was adding more props that had directions variants. To do this, I created a generic type consisting of a union type of the type itself, and template literal types for each direction. Then I used that in the Mapping instead of an explicit union type.

    type DirectionOptions<T extends string> =
        | `${T}Top`
        | `${T}Bottom`
        | `${T}Left`
        | `${T}Right`
        | `${T}`;
    
    type PaddingProps = Mapping<string, DirectionOptions<'padding'>>;
    

    This also lets us easily define the directional props for margin.

    type MarginProps = Mapping<string, DirectionOptions<'margin'>>;
    

    Finally, I wanted to make it so that you could set the hover styles for properties. The way I wanted them to manifest was to have an additional field for each already existing field that appended the text Hover. paddingTop would have an associated paddingTopHover, paddingBottom and paddingBottomHover, etc. This combines the technique used for Mapping with the keyof operator. For each key in the supplied type, a new property is added using a template literal to expand its name to include Hover.

    export type Hoverable<Type> = {
        [Property in keyof Type as `${string & Property}Hover`]: Type[Property];
    };
    

    Applying Hoverable to BasePaddingProps below produces a type with the expected xxxHover, props:

    interface BasePaddingProps {
    	padding: string;
    	paddingTop: string;
    	paddingBottom: string;
    	paddingLeft: string;
    	paddingRight: string;
    }
    
    type PaddingProps = Hoverable<BasePaddingProps>;
    
    interface PaddingProps { // equivalent to above ^^^
    	padding: string;
    	paddingTop: string;
    	paddingBottom: string;
    	paddingLeft: string;
    	paddingRight: string;
    	paddingHover: string;
    	paddingTopHover: string;
    	paddingBottomHover: string;
    	paddingLeftHover: string;
    	paddingRightHover: string;
    }
    

    Combining it with our previous properties, we can easily generate hoverable padding and margin types:

    type PaddingProps = Hoverable<Mapping<string, DirectionOptions<'padding'>>>;
    type MarginProps = Hoverable<Mapping<string, DirectionOptions<'margin'>>>;
    

    This implementation is certainly a tradeoff. The interface it produces is really clean. It lets me move fast, and adding additions onto this Block infrastructure is simple. However, from an outsider’s perspective, as I found out sharing my journey piecemeal with my friends and peers, it seems kind of whack. There’s something about

    [Property in keyof Type as`${string & Property}Hover`]: Type[Property];
    

    that doesn’t quite roll off the tongue very well.

  3. 2023-07-12

    I've recently bought a nRF52850 DK from Nordic Semiconductor to try my hand at making Matter-based devices. I like it, and I've had little trouble building and running the provided samples. However, now that I've moved onto trying to do my own implementations, I've been running into issue after issue. Fortunately, the support teams at Nordic Semiconductor have been really helpful and committed to debugging these problems. That said, the solutions are disperate, and often hard to find or use. They're spread across the forums, private email threads, and are compounded by the fact that engineers are using the tools across Windows, Linux, and macOS.

    The first major place I've been having issues is with the ZAP tool. It's the tool used for adding Matter cluters to your application, at outlined in this tutorial. After following the instructions to install here, you'll get an instruction to add the ZAP locations to your path. It probably looks something like this:

    #############################################################################################
    # Please add the following location(s) to the system PATH:                                  #
    # /opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/MacOS/zap #
    # /opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac                            #
    #############################################################################################
    

    For me, this doesn't work. Upon repoenenig the terminal and running zap src/template.zap, it'll tell me the zap command is not found.

    command not found: zap
    

    I needed to cut the /zap of the end of the first path for it to be able to find the zap command. After that, the command will execute.

    Note: it doesn't say it explicity (or I didn't see it), but the steps assume you're starting from the root folder of the sdk. On my machine that's /opt/nordic/ncs/v2.4.0.

    After getting past the above issue, when trying to run ZAP I encountered an error message that says something like this:

    dyld[1660]: Library not loaded: @rpath/Electron Framework.framework/Electron Framework
      Referenced from: <4C4C44DA-5555-3144-A10D-1E87A10EA9F8> /opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/MacOS/zap
      Reason: tried: '/opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (not a mach-o file), '/opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (not a mach-o file), '/System/Volumes/Preboot/Cryptexes/OS@rpath/Electron Framework.framework/Electron Framework' (no such file), '/opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (not a mach-o file), '/opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (not a mach-o file), '/Library/Frameworks/Electron Framework.framework/Electron Framework' (no such file), '/System/Library/Frameworks/Electron Framework.framework/Electron Framework' (no such file, not in dyld cache)
    [1]    1660 abort      zap nrf/samples/matter/template/src/template.zap
    

    This is after following the instructions to install here to install the tool. A thread posted on the forums suggest to install the tool without using the provided script, using this instead:

    wget github.com/.../zap-mac.zip && unzip -d zap-mac zap-mac.zip
    

    I ran that from my root sdk folder, and ended up recreating the folder structure that the download script creates, and moving the unziped contents there:

    /opt/nordic/ncs/v2.4.0/modules/lib/matter/zap_location/zap-mac/zap.app/
    

    Again I tried running zap src/template.zap, now encountering a new error:

    Error while running sql statement:
    INSERT INTO
      ENUM (ENUM_ID, SIZE)
    VALUES (
      (SELECT DATA_TYPE_ID FROM DATA_TYPE WHERE PACKAGE_REF IN (2) AND NAME = ? AND DISCRIMINATOR_REF = ?),
      (SELECT
        CASE
          WHEN (
            (SELECT
              SIZE
             FROM
              ENUM
             INNER JOIN
              DATA_TYPE
             ON
              ENUM.ENUM_ID = DATA_TYPE.DATA_TYPE_ID
             WHERE
              DATA_TYPE.PACKAGE_REF IN (2)
              AND DATA_TYPE.NAME = ?
              AND DATA_TYPE.DISCRIMINATOR_REF = ?)
            IS NULL )
            THEN
              (SELECT
                SIZE
              FROM
                ENUM
              INNER JOIN
                DATA_TYPE
              ON
                ENUM.ENUM_ID = DATA_TYPE.DATA_TYPE_ID
              WHERE
                DATA_TYPE.PACKAGE_REF IN (2) AND DATA_TYPE.NAME = ?)
          ELSE
            (SELECT
              SIZE
             FROM
              ENUM
             INNER JOIN
              DATA_TYPE
             ON
              ENUM.ENUM_ID = DATA_TYPE.DATA_TYPE_ID
             WHERE
              DATA_TYPE.PACKAGE_REF IN (2)
              AND DATA_TYPE.NAME = ?
              AND DATA_TYPE.DISCRIMINATOR_REF = ?)
        END AS SIZE)), values: ProductFinishEnum,3,enum8,3,enum8,enum8,3, Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: ENUM.ENUM_ID
    

    As best I could understand from email threads with support, this is from trying to pull in the proper Matter cluster templates and definitions since they're not shipped with ZAP by default.

    I started looking for other solutions to the Electron Framework problem, which brought me to this thread on the Matter GitHub repository. Essentially the unzip functionality doesn't prefer symlinks. This time I installed the current released version from the repository releases (specifically v2023.05.04), placing it in the same spot like I did with the previous fix.

    This time, the magic happened! The ZAP tool opened with the expected Matter definitions.

    ZAP with template project zap file loaded

  4. 2023-07-08

    When setting up the chip tool for testing matter devices on macOS, you might run into issues with the build command

    ./scripts/examples/gn_build_example.sh examples/chip-tool BUILD_PATH
    

    For me, it reported a bunch of errors like these:

    ...
    
    The target:
      //third_party/connectedhomeip/third_party/libwebsockets:libwebsockets
    has a source file:
      //third_party/connectedhomeip/third_party/libwebsockets/repo/lib/system/system.c
    which was not found.
    ___________________
    ERROR at //third_party/connectedhomeip/third_party/libwebsockets/BUILD.gn:33:1: Source file not found.
    source_set("libwebsockets") {
    ^----------------------------
    The target:
      //third_party/connectedhomeip/third_party/libwebsockets:libwebsockets
    has a source file:
      //third_party/connectedhomeip/third_party/libwebsockets/repo/lib/roles/ws/ops-ws.c
    which was not found.
    ___________________
    ERROR at //third_party/connectedhomeip/third_party/jsoncpp/BUILD.gn:26:1: Source file not found.
    source_set("jsoncpp") {
    ^----------------------
    The target:
      //third_party/connectedhomeip/third_party/jsoncpp:jsoncpp
    has a source file:
      //third_party/connectedhomeip/third_party/jsoncpp/repo/include/json/allocator.h
    which was not found.
    ___________________
    ERROR at //third_party/connectedhomeip/third_party/editline/BUILD.gn:30:1: Source file not found.
    static_library("editline") {
    ^---------------------------
    

    The first step in Building the CHIP Tool says

    Install all required packages for Matter and prepare the source code and the build system. Read the Building Matter guide for instructions.

    I think incorreclty assumed that was completed when setting up the nRF SDK. You should have the Matter repository installed under /modules/lib/matter, but you still have to run git submodule update --init in that directory.

  5. 2021-09-26

    Seventy-nine days after packing up my things, trekking across the Appalachian Mountains, and settling in Manhattan’s Upper West Side, I’m turning around and reversing the process.

    When I took the job that would ultimately bring me here, I spoke with absolute confidence that New York City wasn’t just some place I thought I wanted to be. It was the place I needed to be. However, I’ve come to find that living here makes me absolutely miserable. How miserable? has been hard to universally impress upon people, so you’ll have to take my word for it when I say this is the saddest I’ve ever been.

    It’s been a lengthy journey of self-reflection as to how I could have made such a catastrophic miscalculation. Divulging every aspect would be difficult; partly because of how personal it is and partly because it’s kind of a long story. But I’m sure now that this isn’t the right place for me. Hours of advice from family, friends, and professionals made me realize that the value of new roots planted here aren’t worth the cost when compared to cultivating the ones I have deeper, elsewhere.

    That said, I feel the need to make one thing absolutely clear: don’t feel too bad for me. Yeah it sucks, but I truly have no regrets about the move to New York. I believed I needed to be here, so without coming I couldn’t have known the truth. I am incredibly grateful to have had the chance to get this life choice so wrong and learn something so valuable from it.

    Finally, What’s next? Everyone’s asked me this, and, honestly, you know about as much as I do. I have no real long-term plan at this point. I’m going home, I’m taking a break, and I’ll figure it out from there.

    Selfie on the Brooklyn Bridge with Manhattan in the background

  6. 2021-04-13

    Sometimes when I’m working on a particularly complicated feature I’ll amend onto my current commit in little working chunks. I generally recommend this practice, but I’ll occasionally run into an issue where I amended a commit with something that’s broken. When I want to cut off the most recent commit and start over. I’ll just git reset HEAD~1, but in a case like this there’s a lot of work in the current commit I’d like to keep.

    In cases like this, you can make use of reflog to see a more detailed history of you commits. git reflog will show you something like this:

    44e56e8 HEAD@{1}: commit (amend): Move all dialogs to same package
    d62a3e2 HEAD@{2}: commit (amend): Move all dialogs
    90654b0 HEAD@{6}: commit: Move all dialogs to same package
    d949095 HEAD@{7}: commit: Remove unused dialogs
    e8cac49 HEAD@{8}: commit: Implement new suprise dialog
    e9b67e2 HEAD@{9}: commit (initial): Refactor dialogs to share a base component
    

    The last two “commits” are both amends, adding onto the 90654b0.

    Now, let’s say I wanted to chop off my most recent amend. To do this, I can do a soft reset like so:

    git reset --soft @{1}
    

    Now doing a git status you should see your most recent changes upstaging and ready for modifying.

    The full details of a soft reset can be found here.

  7. 2021-03-30

    I’ve recently tried to take better care of my commits. In pull requests previously just appended feedback onto the end of the branch.

    3afac13 Fix typo in suprise dialog
    e8cac49 Implement new suprise dialog
    e9b67e2 Refactor dialogs to share a base component
    

    Commit 3afac13 is example of one such commit. The SupriseDialog.tsx added in e8cac49 included some typo, so I pushed a new commit to fix it.

    Now, if I wanted to fix this I could easily have just git commit --amend -—no-edit . But what if that typo is from a couple commits ago?

    90654b0 Move all dialogs to same package
    d949095 Remove unused dialogs
    e8cac49 Implement new suprise dialog
    e9b67e2 Refactor dialogs to share a base component
    

    Before, what I would have done is git reset HEAD~1 to undo the commit at HEAD and then git stash to save it for each commit until I got to the one I needed to fix. This is both annoying and time-consuming.

    A better way is git’s interactive rebase. Instead I could have done git rebase -i HEAD~3 . The -i flag says to make the rebase interactive, and HEAD~3 says to go back three commits from the current HEAD.

    A view like this will then appear:

    pick e8cac49 Implement new suprise dialog
    pick d949095 Remove unused dialogs
    pick 90654b0 Move all dialogs to same package
    
    # Rebase e9b67e2..90654b0 onto e9b67e2 (3 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup <commit> = like "squash", but discard this commit's log message
    # x, exec <command> = run command (the rest of the line) using shell
    # b, break = stop here (continue rebase later with 'git rebase --continue')
    # d, drop <commit> = remove commit
    # l, label <label> = label current HEAD with a name
    # t, reset <label> = reset HEAD to a label
    # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
    # .       create a merge commit using the original merge commit's
    # .       message (or the oneline, if no original merge commit was
    # .       specified). Use -c <commit> to reword the commit message.
    #
    # These lines can be re-ordered; they are executed from top to bottom.
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    #
    # However, if you remove everything, the rebase will be aborted.
    #
    # Note that empty commits are commented out
    
    

    This gives a ton of options. For this example, edit will let me amend onto the commit like I want, but if I just wanted to change the commit message I could use reword, or I could combine commits with squash. To use a command on a commit, change the work pick before the commit message at the top.

    edit e8cac49 Implement new suprise dialog
    pick d949095 Remove unused dialogs
    pick 90654b0 Move all dialogs to same package
    

    Then a message will appear telling me I’ve stopped at the appropriate commit.

    Stopped at e8cac49...  Implement new suprise dialog
    You can amend the commit now, with
    
      git commit --amend
    
    Once you are satisfied with your changes, run
    
      git rebase --continue
    

    I’ve done my best to become an interactive rebase wizard as it lets me easily produce a cleaner git history. You can find the documentation for the interactive rebase here!

  8. 2021-03-16

    Effective Python has a bunch of fun factoids in it, and one in particular I I like is Item 17: Prefer defaultdict Over setdefault to Handle Missing Items in Internal State. This section introduces the defaultdict from the collections module, which is something I’ve started to make regular use of.

    Consider a dictionary where you keep track of your test scores for all your classes. It might look something like this:

    class_grades = {
        'CSC-110': [ 'B' ],
        'BIO-161': [ 'C' ]
    }
    

    The code for handling missing classes when logging a grade can be centralized to an add_grade method like so:

    def add_grade(class_id: str, grade: str):
        found = class_grades.get(class_id)
        if found is None:
            found = []
            class_grades[class_id] = found
        found.append(grade)
    
    
    add_grade('CSC-110', 'A-')
    add_grade('SWE-200', 'A')
    print(class_grades)
    >>>
    {'CSC-110': ['B', 'A-'], 'BIO-161': ['C'], 'SWE-200': ['A']}
    
    

    The abstraction of add_grade will hide the complexity of managing a missing class_id from users of class_grades. While this is fine, defaultdict can handle all of this for us.

    Basically, defaultdict allows us to define what should be added to a dictionary in the case of a missing key. For class_grades that’s an empty list.

    The new definition of class_grades looks like this:

    from collections import defaultdict
    
    class_grades = defaultdict([])
    

    Now add_grade can be eliminated entirely, and in its place we can just call get and append directly.

    class_grades.get('CSC-110').append('A-')
    class_grades.get('SWE-200').append('A')
    print(class_grades)
    >>>
    {'CSC-110': ['B', 'A-'], 'BIO-161': ['C'], 'SWE-200': ['A']}
    

    This leads to an overall cleaner interface because we don’t have to program defensively around a possible None value. We can always assume class_grades.get is going to return a valid response, and just call append.

    You can find more tidbits like these in Effective Python .

  9. 2021-03-02

    Something important about components that pop up on a user action is detecting when someone clicks outside of them so you can stop showing it. Anytime I’ve done this by hand it usually involves making some background container with some less than simple styles that takes up the entire page, is transparent to the user, and registers an on-click callback which handles the component’s visibility.

    <div className="modal-background" onClick={() => setShowing(false)}>
        {showing && <div>The rest of the owl</div>}
    </div>
    

    This is not only annoying, but something you don’t have to do when you’re using MaterialUI. The ClickAwayListener can arbitrarily wrap a component to manage when a user clicks outside of it.

    <ClickAwayListener onClickAway={handleClickAway}>
        {showing && <div>The rest of the owl</div>}
    </ClickAwayListener>
    

    The full documentation can be found here.

  10. 2021-02-16

    I like automated testing. It gives me the confidence my code works as I’m writing, and it makes sure I don’t introduce a regression. My comfort with working in any particular language or framework often comes from the quality of the testing tools in their ecosystem.

    A recently project forced me to come back to an old friend: C. Unit testing in C has always been a bit daunting. Frameworks exist (Check and CUnit for example), but any forays into integrating them with my projects has proved fruitless. Being primarily a web developer, my outsider’s perspective on build systems in C’s ecosystem is one of bewilderment. It’s not to say they’re bad or wrong, just not something I can wrap my head around.

    Enter ThrowTheSwitch.com. This site is a cornucopia of C programming resources. They have libraries for exceptions, mocking, and the ones I had come searching for: unit testing and a build system. Unity — not to be confused with the game engine — comes with many of the basic features one would expect from a testing library. Additionally, their Ceedling project makes setting up projects integrated with Unity a breeze. Running the command ceedling new fibbonacci creates a project with a starter source and test directory set up and ready to go.

    Bringing it all together, I can write a simple test for a Fibonacci function like this:

    // test/test_fibonacci.c
    
    #include "unity.h"
    
    #include "fibonacci.h"
    
    void test_fibonacci(void) {
        TEST_ASSERT_EQUAL_INT(1, fibbonacci(1));
        TEST_ASSERT_EQUAL_INT(1, fibbonacci(2));
        TEST_ASSERT_EQUAL_INT(2, fibbonacci(3));
        TEST_ASSERT_EQUAL_INT(55, fibbonacci(10));
    }
    

    In my header file I include the method signature and a blank implementation in my source file so my test will compile and run.

    // src/fibonacci.h
    
    #ifndef FIBONACCI_H
    #define FIBONACCI_H
    
    int fibbonacci(int i);
    
    #endif // FIBONACCI_H
    
    // src/fibonacci.c
    
    #include "fibonacci.h"
    
    int fibbonacci(int i) {
    	return 0;
    }
    

    Now I have a failing test to start my TDD cycle.

    ⇒  ceedling test
    
    
    Test 'test_fibonacci.c'
    -----------------------
    Compiling fibonacci.c...
    Linking test_fibonacci.out...
    Running test_fibonacci.out...
    
    -------------------
    FAILED TEST SUMMARY
    -------------------
    [test_fibonacci.c]
      Test: test_fibonacci
      At line (7): "Expected 1 Was 0"
    
    --------------------
    OVERALL TEST SUMMARY
    --------------------
    TESTED:  1
    PASSED:  0
    FAILED:  1
    IGNORED: 0
    
    ---------------------
    BUILD FAILURE SUMMARY
    ---------------------
    Unit test failures.
    

    I chose to implement my fibonacci with recursion:

    // src/fibonacci.c
    
    #include "fibonacci.h"
    
    int fibbonacci(int i) {
      if (i <= 1) {
        return i;
      } else {
        return fibbonacci(i - 1) + fibbonacci(i - 2);
      }
    }
    
    

    After running the test again I can see it passes:

    ⇒  ceedling test
    
    
    Test 'test_fibonacci.c'
    -----------------------
    Generating runner for test_fibonacci.c...
    Compiling test_fibonacci_runner.c...
    Compiling test_fibonacci.c...
    Linking test_fibonacci.out...
    Running test_fibonacci.out...
    
    --------------------
    OVERALL TEST SUMMARY
    --------------------
    TESTED:  1
    PASSED:  1
    FAILED:  0
    IGNORED: 0
    

    For more on Ceedling check out their extensive github docs. Likewise, you can find more about Unity on GitHub, including a complete list of available assertions.