The blog of dlaa.me

Posts tagged "Miscellaneous"

"The unexamined life is not worth living" [Thoughts on writing an effective performance review at Microsoft (or elsewhere)]

It's "Connect season" again at Microsoft and I thought it might be useful to address something managers see pretty regularly: reluctance by their direct reports to fill out the required "paperwork".

To start, it's important to understand what a Connect is. A Connect is a structured document that Microsoft employees are required to fill out every few months, submit to their manager for comment, then use as a basis for a career/performance discussion. The Connect was introduced as a way to provide employees with regular feedback on a flexible basis to accommodate each organization's timelines. Before Connects, people went through a similar process every six months on a rigid schedule; in practice, Connects happen twice a year on the same schedule as before. (Shrug.)

There are four sections in a Connect document, all open-ended text boxes. Each part has an area for the direct report which is filled out first and an area for the manager which is filled out second, often referencing the direct report's comments.

  1. The first section is for writing about what the person accomplished since last time. Depending on the level of detail, this section can get long. (Very long.) It's usually the biggest part because people have a chance to talk about all the great things they did. Ironically, if the individual is extremely thorough, their manager may not have much to add beyond "Yup, they did all that".
  2. The second section is about things that could have gone better since last time. Most people don't like to focus on negatives and this is often a short section. Managers usually have more to say and may go into depth depending on what happened and the opportunities they see (and how many hours they've already spent writing feedback that day).
  3. The third section is about what the person will do in the upcoming months. This is ostensibly an opportunity to set goals and provide clarity about future tasks. Unfortunately, planning hasn't usually been done at this point and, besides, good luck trying to define a schedule six months in advance for a dynamic environment. In my experience, managers don't hold people to commitments made here.
  4. The fourth section is about what the person will do to learn and grow in the coming months. This can be things like training, coursework, reading books, etc., and is driven by the direct report. The manager may have suggestions, but this section is about how the person is looking to improve themselves.

Some people seem to really struggle with the process of filling out a Connect. To be sure, it can be hard to write about yourself and some people aren't used to extolling their virtues. (While other people are comfortable doing so at length!) In my opinion, the greatest value in this process is for the individual, not the manager. Managers might be reminded of a few things that got done and it's a pleasure to share positive feedback, but managers probably are not learning a lot from the writing process nor getting much actionable feedback from the document. (The ensuing discussion is a more balanced exchange of ideas, but people don't seem to dread it as much.)

Some key benefits for the individual (who I'll refer to as "you" below):

  • This process contributes directly to a discussion between you and your manager. Regular one-on-one's are great for keeping up with daily and weekly progress, but they often don't get into deeper career-oriented topics without one of you making a specific effort to do so. Connects provide that opportunity and are a good forcing function.
  • Another benefit of Connects is documenting your accomplishments for future reference. There shouldn't be a lot your manager wasn't already aware of (or else you're not communicating effectively in email and 1:1's), but Connects are something future managers will look at as part of the hiring process. Being comprehensive about listing accomplishments is a good way to capture the things you've done in a place that's easy to review and gives a complete picture of your impact. It's also a great reference when updating your resume. (You do update your resume periodically, right?)
  • Connects are a great opportunity for self-reflection and feedback and this is where you skimping on the process is counter-productive. It may feel good to leave the "what could have gone better" section blank, but that's probably not honest and it puts the onus on your manager to do all the heavy lifting. Yes, it's their job to help you improve, but that's much more productive when you take an active role and provide a good starting point for the discussion. A common phrase at Microsoft is "growth mindset" and that requires acknowledging and identifying areas for growth. Your manager probably has some ideas about ways you could do things differently and it's much easier when they see that you acknowledge this also. That means they don't need spend time pointing out issues and can think more about coaching you through them.

In my opinion, Connects are very much a "the more you put into it, the more you get out of it" situation. But this is not to say you should write long multi-page essays! (Please don't.) Writing a Connect is about communicating concepts clearly and concisely. My preference is to use bulleted lists and sentence fragments, but short paragraphs can also work well if that's your style. Make your point, provide supporting evidence, then move on. Your manager may have a dozen Connects to read and respond to and will appreciate being able to focus on the content.

In summary, the Connect process is a valuable opportunity for you to think back on your accomplishments as well as ways to improve how you do things. Your manager will try their best to provide constructive, actionable feedback regardless of how deeply you invest, but the experience will be more rewarding if you take the time to reflect, are candid about where things could have gone better, and are open to feedback about what to do differently next time.

You may never love the act of self-analysis, but writing a performance review shouldn't be a source of fear or anxiety. I hope this post makes your next Connect a more positive experience!

"If you want to see the sunshine, you have to weather the storm." [A simple AppleScript script to backup Notes in iCloud]

As someone who likes to make backups of all my data, storing things "in the cloud" is a little concerning because I have no idea how well that data is backed up. So I try to periodically save copies of cloud data locally just to be safe. This can be easy or hard depending on how that data is represented and how amenable the provider is to supporting this workflow. In the case of Apple Notes, it's easy to find questionable or for-pay solutions, but there's a lot out there I don't really trust. You might think Apple's own suggestion would be the best, but you might be wrong because it's rather impractical for someone with more than a handful of notes.

As a new macOS user, there's a lot I'm still discovering about the platform, but something that seems well suited for this problem is AppleScript and the AppleScript Editor, both of which are part of the OS and enable the creation of scripts that interact with applications and data. This Gist by James Thigpen proves the concept of using AppleScript to backup Notes, but of course I wanted to do some things a little differently and so I made my own script.

Notes:

  • This script enumerates all notes and appends them to a single HTML document that it puts on the clipboard for you to paste into the editor of your choice and save somewhere safe, email to yourself, or whatever.
  • The script does very little formatting other than separating notes from each other with a horizontal rule and adding a heading for each folder name. The output should be legible in text form and look reasonable in a web browser, but it won't win any design awards.
  • The contents of each note are apparently stored by Notes as HTML; this script adds the fewest number of additional tags necessary for everything to render properly in the browser.
  • If your OS language is set to something other than English, you may need to customize the hardcoded folder name, "Recently Deleted". (Or remove that conditional and you can backup deleted notes, too!)
  • This was my first experience with AppleScript and I'm keeping an open mind, but I will say that I did not immediately fall in love with it.
set result to "<html><head><meta charset='utf-8'/></head><body>" & linefeed & linefeed
tell application "Notes"
  tell account "iCloud"
    repeat with myFolder in folders
      if name of myFolder is not in ("Recently Deleted") then
        set result to result & "<h1>" & name of myFolder & "</h1>"
        set result to result & "<hr/>" & linefeed & linefeed
        repeat with myNote in notes in myFolder
          set result to result & body of myNote
          set result to result & linefeed & "<hr/>" & linefeed & linefeed
        end repeat
      end if
    end repeat
  end tell
end tell
set result to result & "</body></html>"
set the clipboard to result

Can you use it in a sentence? [An example of solving the New York Times Spelling Bee puzzle with JavaScript]

The New York Times Crossword app includes a daily puzzle called "Spelling Bee". The challenge is to make as many four-or-more letter words from a set of seven letters as you can. Each word must contain only those letters (using a letter multiple times is okay) and all of the words must include a specific letter (highlighted as part of the puzzle). It's pretty straightforward and the goal is to find a bunch of valid words with extra credit being given for long words and extra-extra credit for a word that uses all of the day's letters (known as a "pangram"). It's fun and you can spend as little or as much time as you want.

The app lets you see a list of all the previous day's words, but maybe you're stuck and want some ideas today. Or maybe you're bored and wonder what it would look like to solve this in code. Or both. I won't judge.

As usual, there are various ways to solve this problem. This one is mine. On iOS, my go-to application for writing JavaScript is Scriptable. It offers a modern JavaScript environment with handy helper functions and is pleasant to use on both iPhone and iPad. In this case, the Request.loadJSON method provides a concise way to load a list of popular English words from the Internet. The source I chose is dictionary.json from this GitHub repository. In addition to being a fairly comprehensive list of English words, this file is in JSON format which is automatically parsed by the API above.

The algorithm I use is fairly straightforward: download the list of words and loop through them looking for valid ones. Once found, check if the word is a pangram and write it to the console (red if so, white otherwise). I use a RegExp for the validity check and augment it with a function call to ensure the required letter is present. (I don't think there's an elegant way to do everything with a single regular expression, but I'd be happy to learn one! And I'm not interested in clunky ways because I already know a few of those.) The pangram check is basically also a loop, though it uses Array's reduce for conciseness. There are a couple of less-common practices to keep things interesting, but otherwise the code speaks for itself.

I didn't want to "spoil" a previously published puzzle, so the code below uses the seven unique letters of my full name with "d" as the required letter. Running the sample code with this set of letters doesn't output a pangram for any dictionary I tried, so it's unlikely to ever be a real Spelling Bee puzzle! (Fun fact: the longest valid word for this input seems to be "nondivision".)

// Puzzle letters; the first is required
const letters = "davinso";

// Source of word list JSON dictionary
const req = new Request("https://github.com/adambom/dictionary/blob/master/dictionary.json?raw=true");

// Output valid words to console log/error
const valid = new RegExp("^[" + letters + "]{4,}$");
const required = letters[0];
const optionals = letters.slice(1).split("");
req.loadJSON().then((dictionary) => {
  const sorted = Object.keys(dictionary).map((word) => word.toLowerCase());
  sorted.sort();
  for (const word of sorted) {
    if (valid.test(word) && word.includes(required)) {
      const pangram = optionals.reduce(
        (prev, curr) => prev && word.includes(curr),
        true
      );
      console[pangram ? "error" : "log"](word);
    }
  }
}).catch(console.error);

"We must never become too busy sawing to take time to sharpen the saw." [Two solutions to a programming challenge]

I stumbled across a programming challenge while looking for info on UglifyJS. It's called A little JavaScript problem, though I you can do it in any language. I will summarize the problem here, but please visit that page for the details:

The problem: define functions range, map, reverse and foreach, obeying the restrictions below, such that the following program works properly. It prints the squares of numbers from 1 to 10, in reverse order.

var numbers = range(1, 10);
numbers = map(numbers, function (n) { return n * n });
numbers = reverse(numbers);
foreach(numbers, console.log);

This wouldn't be too hard, except the restrictions say you are not allowed to use Array or Object (i.e., no storing state in a lookup). So, yeah, it's a good challenge!

If you're interested, please try to do it before reading further - spoilers follow...

The first solution

My first thought was to use something like IEnumerable in .NET where range would generate the sequence of numbers and the other functions would consume that sequence and output a modified sequence in turn until foreach wrote each item to the console. I thought of this as a "generator" model (yes, I know JavaScript has generator functions, that's next...) and it's easy to define range as returning a function that returns one number each time it's called and a falsy value to signal the end.

function range (a, b) {
  return () => (a <= b) ? a++ : null;
}

That done, it's pretty straightforward to define foreach as a function that takes one of these "generators" and keeps calling it and processing results until they run out. (Yes, this has a bug if the range includes the value 0, but we could use a unique sentinel value to fix that.)

function foreach (g, func) {
  while (v = g()) {
    func(v);
  }
}

It's also easy to define map as taking one of these generator functions and returning another that returns the result of calling the provided function for each of the generated values in turn.

function map (g, func) {
  return () => func(g());
}

But reverse is more challenging! The only way to return the last item first is to traverse the entire list, but once that's done it can't be done again, so all the preceding elements need to be saved somewhere to be able to return them in backwards order. But recall that the code is not allowed to use arrays or objects, so typical data structures like Stack are not available. The approach I came up with was to create breadcrumbs out of closures. There might be a more elegant way to express this, but I did so with a local variable and helper methods push and pop. For each element in the list, a new closure is created that captures the previous closure and the value of the element. Returning values in reverse is then just a matter of looking into the current closure, replacing it with the previous closure, and returning the current closure's value. It's probably not immediately obvious what's going on with this code and it took a little while to come up with the right approach even after I had the design in my head.

function reverse (g) {
  let pop = () => null;
  const push = (v, pre) =>
    pop = () => (pop = pre) && v;
  while (v = g()) {
    push(v, pop);
  }
  return () => pop();
}

"She may not look like much, but she's got it where it counts, kid." But we can do better! I hadn't previously worked with JavaScript generators, and this seemed like a great opportunity to sharpen the saw...

The second solution

You're almost always better off using the right tool for the job; in this case generators/iterators fit nicely. As before, range is easy to get started with - just return the numbers in order and stop after the last one. In this case, the generator automatically indicates completion, so there is no confusion or ambiguity around returning a falsy value to signal the end.

function* range (a, b) {
  while (a <= b) {
    yield a++;
  }
}

The implementation of foreach is almost identical to before.

function foreach (g, func) {
  for (let v of g) {
    func(v);
  }
}

The code for map is very slightly longer than before, but it's completely obvious what it's doing and it's easy to write. (It looks a lot like foreach which is a win from an consistency point of view.)

function* map (g, func) {
  for (let v of g) {
    yield func(v);
  }
}

That brings us to reverse and that is where the real payoff happens! The generator-based version of this code also builds up state in the call chain (known as a "call stack" for a reason), but the way it does so is clear and closely relates to how one would describe this function in words: "until you're done, get the first value, reverse the rest of the list, and return the value (i.e., after the other values)".

function* reverse (g) {
  const { done, value } = g.next();
  if (!done) {
    yield* reverse(g);
    yield value;
  }
}

I'm much happier with this second solution because the intent is so much clearer. Also, it took me less time to write! :)

And there you have it: two solutions to the same problem. The first: functional but hard to follow; the second: clear and easy to understand (also more versatile). Sometimes, the right algorithm or approach can make a world of difference when it comes to programming.

Make the Pi Higher [Configuring a Raspberry Pi for Node.js and web development]

Since the beginning of the year, I've been doing all my OSS project development on a Raspberry Pi 4 running Raspberry Pi OS (née Raspbian). It's not as powerful a machine as my Intel-based desktop PC running Windows, but it's much smaller, it uses much less power, it's completely silent, and it cost about 5% of the price. To be clear, I still do photo editing on Windows and the extra speed of the PC is nice for running unit tests - but most of the time I'm perfectly happy using the Pi.

There are some great resources for Setting up your Raspberry Pi. If you want to configure it for development work like I did, here are steps that worked for me. (They assume some familiarity with Linux, but not much.)

  1. Install Raspberry Pi OS (32-bit) with desktop
  2. sudo apt update
  3. sudo apt upgrade
  4. If you want to use the Xfce desktop environment:
    1. sudo apt install xfce4 xfce4-terminal
    2. sudo update-alternatives --config x-session-manager
    3. Choose startxfce4
  5. Install the Git UI tools and configure Git
    1. sudo apt install git-gui gitk
    2. Set your username in Git
    3. Set your commit email address in Git
    4. Connect to GitHub with SSH
  6. Install a current version of Node.js
  7. Install a community build of Visual Studio Code
    1. Trust the corresponding GPG key: wget -qO - https://packagecloud.io/headmelted/codebuilds/gpgkey | sudo apt-key add -
  8. Install the ARM build of Visual Studio Code
  9. Install Visual Studio Code via apt install code

Note: The community build of VS Code is the only part of this process that uses an unofficial release. I'd love for VS Code to support the Pi officially, but this issue from 2016 has not been addressed. Everything is official now that VS Code supports Raspberry Pi!

That's pretty much it! I really enjoy this minimalist configuration and find there are very few compromises with the Pi relative to a "real" computer. Plus, it's easy to swap drives (SD cards) and keep a set of different OS'es / configurations handy for experimenting and trying new things.

If you thought the Pi was just a toy, maybe it's time for another look!

Updated 2020-11-22 to link to official builds of Visual Studio Code for ARM.

Without geometry, life is pointless [Another interesting programming puzzle worked through]

I happened across another coding puzzle recently that caught my interest. (If you're curious, here is a link to the puzzle I discussed a few weeks ago about implementing a weird kind of addition.) The problem this time is as follows:

You are given an array of coordinates, coordinates[i] = [x,y], where [x,y] represents the coordinate of a point. Check if these points make a straight line in the XY plane.

I saw an approach to solving this that didn't thrill me, so I wanted to solve it for myself.

If you're going to do the same, stop reading now because there are spoilers ahead!

As an aside, one thing I don't love about this problem is that it requires a certain amount of geometry knowledge to solve and furthermore the solution I use benefits from experience with trigonometric functions. However, one can be a perfectly good programmer without knowing either of these topics and they're not likely to come up in most roles. I try to avoid questions like this when I'm interviewing because they're likely to favor some backgrounds over others. That said, I didn't come up with this scenario and it's still interesting to think about, so let's go.

The first thing is to figure out how to approach the problem. If you remember much about linear equations, you might think to use the equation for a line to solve this: y = mx + b. If the slope, m, and the y-intercept, b, are the same for all pairs of points, then they all lie on the same line. This is helpful, but the equation starts to fall apart for points that lie on the same vertical line (for example, along the y-axis) because the slope there is infinite. Now, maybe you are comfortable dealing with infinity or risking division by zero, but I am not. That makes the vertical line scenario a special case and "special case" is often another phrase for "bug farm".

We don't want to use the slope equation, but we're in luck because there's something similar that's easier to work with: angle. Specifically:

The Math.atan2() function returns the angle in the plane (in radians) between the positive x-axis and the ray from (0,0) to the point (x,y), for Math.atan2(y,x).

That's not a solution as-is, but if we choose two points from the input and subtract the corresponding x and y coordinates, we get coordinates that define a ray in the manner described above. Now, two sets of points on line segments sharing the same angle (as calculated above) will be parallel, but not necessarily collinear. However, if we use the same point to calculate the angle to every other point of the input, that should be sufficient to determine collinearity of the set. In words:

If the angle of the line segment made up by pairing the first point of the input array with every other point of the array is the same, then we know all those rays point in the same direction and all originate at the same point (the first point) and therefore all the points are on the same line!

We're nearly there, but there's one more observation: two rays that go in exactly opposite directions from the same point are also on the same line. For example, the line segment defined by two points on a ray with an angle of 10° to the x-axis are parallel to the segment corresponding to two points on a ray with an angle of 190° to the x-axis. So instead of worrying about all 360°, we can focus on just the first 180°. The modulo operator provides an easy way to map angles between 180° and 360° into the range of 0° to 180°. (Except that the atan2 function returns values in radians instead of degrees because "Why not?" so we're going to be dealing with π.)

With all that behind us, the following algorithm should make sense. There are a few subtleties so it correctly handles empty lists as well as lists with just one point and also lists where the same point appears multiple times. In addition, it bails out as soon as it finds a non-collinear point for efficiency. Here's the code - as is the custom with JavaScript, there's no parameter validation, but this is intended to work for all valid inputs:

function arePointsCollinear(points) {
  let angle = null;
  const first = points[0];
  for (let i = 1; i < points.length; i++) {
    const current = points[i];
    const deltaX = current[0] - first[0];
    const deltaY = current[1] - first[1];
    if (deltaX || deltaY) {
      const theta = (Math.atan2(deltaY, deltaX) + Math.PI) % Math.PI;
      if (angle === null) {
        angle = theta;
      } else if (angle !== theta) {
        return false;
      }
    }
  }
  return true;
}

Interviewing for Fun and Profit [An unreasonably detailed consideration of a practical programming puzzle]

A few days ago, Brent Simmons published a thoughtful blog post about one aspect of programming interviews. As someone who conducts interviews regularly for work, the topic is of interest to me. Rather than summarizing the post, I encourage you to go read Practicing the Coding Challenges now. (You might also enjoy some of the replies to Brent's tweet linking to it.)

After reading the post, I tweeted a response:

I agree with the spirit of this post - that clarity beats premature optimization - but feel the conclusion skips an important point. The "efficient" solution is correct for arbitrarily large numbers while the "clear" one fails after MAX_INT (or similar).

Brent replied:

I thought about discussing that, but felt like I'd be muddying up the post too much.

And I responded:

Understood, thanks! I don't know how it was posed on LeetCode, so that difference may not even be relevant. But if I were discussing this with a candidate during an interview, it's something I'd want them to raise for discussion as an indication they're thinking about edge cases.

And I kept thinking about this problem in the context of a programming interview. When I saw Olof Hellman's well-reasoned follow-up post Practicing the Coding Challenges, I realized I wasn't the only one.

I was curious what the "clear" and "efficient" solutions might look like in practice, so I decided to write them and see. I used JavaScript because it's my "go to" language. Things will look a little different in other languages, but I had to pick one and I chose something popular. I'm not including comments in the code below because they'll distract from it and I'm not validating parameters (for null-ness or range) per typical JavaScript practice.

As a reminder, here's the problem definition:

You need to add two numbers and get a sum. But, importantly, the digits of those numbers are stored in arrays, and they're backwards.

The return value also needs to be in a backwards array.

If inputs are [4,2,6] and [3,8,0,8], the function should return [7,0,7,8] - because 624 + 8083 == 8707.

Let's start with the "clear" approach. The point here is to be obvious and straightforward - like you normally want your code to be. Recall Brent's pseudocode proposal:

let num1 = number(from: array1)
let num2 = number(from: array2)
let sum = num1 + num2
return array(from: sum)

Now, in order to run that, we need to implement those two conversion functions. As John Siracusa pointed out, we can do anything we want inside there and because we want to handle arbitrarily large inputs, we'll use JavaScript's BigInt built-in.

As an aside, BigInt does not have widespread support across browsers. In fact, none of the three JavaScript tools I use on iOS support it and I had to switch to a "real" computer to finish this post.

Okay, here's what I came up with:

function clear(array1, array2) {
  const num1 = toBigInt(array1);
  const num2 = toBigInt(array2);
  const sum = num1 + num2;
  return toArray(sum);
}
function toBigInt(arr) {
  const copy = [...arr];
  copy.reverse();
  const numberAsString = copy.join("");
  return BigInt(numberAsString);
}
function toArray(num) {
  const numString = num.toString();
  const charArray = numString.split("");
  const digitArray = charArray.map(Number);
  digitArray.reverse();
  return digitArray;
}

Because this is the "clear" implementation, each line does a single thing and I've mostly avoided idiomatic JavaScript. You could argue map(Number) is a bit subtle, but it should be familiar to most JavaScript programmers. The BigInt constructor takes a Number or a String, so we need to bounce the input through a String on the way into/out of BigInt. We copy the input array to avoid mutating data we don't own.

Okay, so what does the "efficient" code look like? Here's what I came up with:

function efficient(array1, array2) {
  const length = Math.max(array1.length, array2.length);
  const res = new Array(length);
  let carry = 0;
  for (let i = 0; (i < length) || carry; i++) {
    const sum = (array1[i] || 0) + (array2[i] || 0) + carry;
    carry = (sum >= 10) ? 1 : 0;
    res[i] = sum % 10;
  }
  return res;
}

Because this isn't the "clear" solution, I took some liberties with this implementation in the interest of compactness and efficiency. The output array is pre-allocated (though it may need to grow by one) and there is a single loop over the input with no redundancy and fairly little overhead I see. I'll acknowledge some folks might be uncomfortable with the (deliberate) ignorance of array bounds and that the use of carry in the loop condition may be too clever by half. (It can be removed at the cost of a single additional line of code, but I like how this approach reuses the core logic for the final overflow.) Otherwise, I tried not to be unnecessarily obscure.

Conclusions?

The "clear" code is definitely clearer, but it's not as simple as it originally seemed to be. The need to implement helper functions basically tripled the amount of code that needed to be written. The "efficient" code is smaller and ought to be quite a bit cheaper to run considering the algorithmic efficiency and the fact that it doesn't require JIT-ing BigInt or the need for String parsing. In fact, by eschewing those dependencies, it's able to run in many environments (like iOS) where BigInt doesn't exist - so it's also the more "flexible" implementation!

Of course, there's no "right" answer here - just inspiration and food for thought. My thanks go out to everyone who contributed to the discussion and especially Brent, whose NetNewsWire app is my favorite RSS reader!

Let's go to the video tape! [tape-player is a simple, terse, in-process reporter for the tape test harness for Node.js]

I've been a happy user of the nodeunit test harness for a long time, but it was deprecated a few years ago. Recently, I went looking for a similar Node.js test harness to replace it. I prefer small, simple packages and settled on the tape test harness. I enjoy nearly everything about it, but didn't like having to pipe output to a formatter (more on this below). So I wrote a quick bit of code to create an in-process reporter. Then I realized what I'd done could have broader applicability (in my own projects, if nowhere else!) and published a reusable package after adding scenario tests to ensure formatted output for all of the the tape primitives is reasonable.

If this seems interesting, the README goes into more detail:

The Test Anything Protocol (TAP) used by many test harnesses is versatile, but it's not much to look at - or rather, it's too much to look at. There are many custom formatters that work with the tape test harness, but most work by piping process output. This is a useful technique, but interferes with the exit status of the test harness which is a problem in scripts that are meant to fail when tests fail (like npm test). (Though there are workarounds for this, they are shell- and platform-specific.)

Fortunately, tape offers an alternative logging mechanism via its createStream API. This technique is easy to use and runs in-process so it doesn't interfere with the exit status of the test harness. tape-player takes advantage of this to produce a concise test log that's easy to enable.

You can find directions to install and enable tape-player on the GitHub project for tape-player.

Good, cheap, fast: you only get one and you don't get to pick [CoLR, the Camera of Last Resort]

One way I learn about new technologies is by using them. Sometimes I have a compelling scenario that yearns for a creative solution. Other times I don't. This was one of those times... So I created what's probably the worst camera app ever as a way of experimenting with four new (to me) technologies.

Excerpting from the README:

  • CSS Grid Layout - A modern layout system based on grid cells with the great working reference A Complete Guide to Grid
  • MediaDevices.getUserMedia() API - The part of the WebRTC Media Streams API that allows websites to access the device camera for video and image capture
    • Verdict: A somewhat cumbersome API that's nonetheless quite powerful
  • Preact - Exactly what it says on the box, a "Fast 3kB alternative to React with the same modern API"
    • Verdict: Works like a charm, easy for someone with React.js experience
  • Dexie.js - More truth in advertising, a "Minimalistic Wrapper for IndexedDB", where IndexedDB is "a JavaScript-based object-oriented database" that runs in the browser
    • Verdict: Easy to use, even for someone with no prior IndexedDB experience

If you want to learn more, please visit the CoLR project on GitHub. There, you'll find complete source code, instructions, links to resources, and some FAQ's.

If you just want to see how bad a camera app can be (and your device has a camera and your browser supports ECMAScript 2015), please open the CoLR web app in your browser.

Oops, I did it again [A rewrite of my website/blog platform - now public, open-source, and on GitHub as simple-website-with-blog]

Almost 5 years ago, I moved my blog (and website) from a hosted environment to a Node.js implementation of my own creation. At the time, I was new to Node and this was a great way to learn. That code has served me well, but over time I've found a few things I wanted to change - in part due to the rapid growth of the Node platform. Some of the changes were foundational, so I chose to do a rewrite instead of multiple revisions.

The rewrite ended up taking about twice as long as I anticipated, but I'm glad I did it. The new architecture is similar to what it was before - and the user interface almost identical - but there are some nice improvements and modernizations under the hood. Of note, the rendering was separated so it's easy to customize (there are samples for a text blog and a photo blog; the unit tests are implemented as a blog, too), search is much more powerful (with inclusion, exclusion, partial matches, etc.), and the code is simplified by the removal of some undue complexity (mostly features I thought were neat but that added little). Finally, because the new project was written to be reused for other purposes and by other people, I'm able to share it.

For context, the project goals are:

  • An easy way to create a simple, secure website with a blog
  • Support for text-based and photo-based blog formats
  • Easy authoring in HTML, Markdown (with code formatting), or JSON
  • Ordering of posts by publish date or content date
  • Easy customization of site layout and formatting
  • High resolution (2x) support for photo blog images
  • Support for Windows and Linux hosting with Node.js
  • Simple post format that separates content and metadata
  • Ability to author hidden posts and schedule a publish date
  • Ability to create posts that never show up in the timeline
  • Support for archive links and tagging of posts by category
  • Quick search of post content, including simple search queries
  • Automatic Twitter and Open Graph metadata for social media
  • Automatic cross-linking of related posts
  • No JavaScript requirement for client browsers

To learn more about the code, its dependencies, and how to use it, please visit: simple-website-with-blog on GitHub

To see it in action, just browse this blog and site. :)