Showing posts with label Meta. Show all posts

20 Years of Blogging #

The first proper blog post on this site was 20 years ago today. It lived under a different URL, I didn’t register persistent.info until later (when .info was a new-and-shiny top-level domain). It also looked different (I don’t have a screenshot of the first version, but I switched themes a few months later) and was published by very different software (Movable Type 2.64), but there is continuity from then to today.

"10 years, man!" scene from Gross Pointe Blank with the 10 crossed out and replaced by 20

Later that day I imported a bunch of older programming journals, thus the entries go back almost 25 years. The first few years are not really blog posts, but it’s been nice to have snapshots of early work, even if they’re not all gems. I’ve been backfilling a projects page, and the posts also serve as tangible artifacts to link to, even if the project itself is long-dead.

Year 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023
Posts 13 3 4 2 4 1 2 5 7 12

The trend for posts per year is looking encouraging when looking at the past decade, here’s to a few more (while computers still work).

Places Mihai Has Committed Code From #

I've been running lolcommits for 10 years, and it's captured some interesting moments from my time at Quip and Tailscale.

I've put together a gallery, it was a fun nostalgia trip to revisit so many places and coworkers.

A-12 Software Development Parallels #

I recently finished reading From RAINBOW to GUSTO which describes the development of the A-12 high-speed reconnaisance plane (the predecessor to/basis for the somewhat better known SR-71 Blackbird). Though a bit different from the software history/memoirs that I've also enjoyed, I did find some parallels.

Early on in the book, when Edwin Land (founder of Polaroid) is asked to put together a team to research ways of improving the US’s intelligence gathering capabilities, there's the mid-century analog of the two-pizza team:

Following Land’s “taxicab rule” — that to be effective a working group had to be small enough to fit in a taxi — there were only five members.

It turns out that cabs in the 1940s had to seat 5 in the back seat – I suppose the modern equivalent would be the "Uber XL rule".

Much later in the book, following the A-1 to A-11 design explorations, there was an excerpt from Kelly Johnson’s diary when full A-12 development had started:

Spending a great deal of time myself going over all aircraft systems, trying to add some simplicity and reliability.

That reminded me of design, architecture and production reviews, and how the simplification of implementations is one of the more important pieces of feedback that can be given. Curious to find more of Johnson's log, I found that another book has an abridged copy. I've OCRed and cleaned it up and put it online: A-12 Log by Kelly Johnson.

It's a snippets-like approximation of the entire A-12 project, and chronicles the highs and lows of the project. I highlighted the parts that particularly resonated with me, whether it was Johnson's healthy ego, delays and complications generated by vendors, project cancelations, bureaucracy and process overhead, or customers changing their minds.

HTML Munging My Way To a React.js Conf Ticket #

Like many others, I was excited to see that Facebook is putting on a conference for the React community. Tickets were being released in three waves, and so for the last three Fridays I have been trying to get one. The first Friday I did not even manage to see an order form. The next week I got as far as choosing a quantity, before being told that tickets were sold out when pushing the “Submit” button.

Today was going to be my last chance, so I enlisted some coworkers to the cause — if any of them managed to get an order form the plan was that I would come by their desk and fill it out with my details. At 12 o'clock I struck out, but Bret and Casey both managed to run the gauntlet and make it to the order screen. However, once I tried to submit I was greeted with:

React.js Conf Order Failure

Based on Twitter, I was not the only one. Looking at the Dev Tools console showed that a bunch of URLs were failing to load from CloudFront, with pathnames like custom-tickets.js and custom-tickets.css. I assumed that some supporting resources were missing, hence the form was not entirely populated¹. After checking that those URL didn't load while tethered to my phone (in case our office network was banned for DDoS-like behavior), I decided to spelunk through the code and see if I could inject the missing form fields by hand. I found some promising-looking JavaScript of the form:

submitPaymentForm({
    number: $('.card-number').val(),
    cvc: $('.card-cvc').val(),
    exp_month: $('.card-expiry-month').val(),
    exp_year: $('.card-expiry-year').val(),
    name: $('.cardholder-name').val(),
    address_zip: $('.card-zipcode').val()
});

I therefore injected some DOM nodes with the appropriate class names and values and tried resubmitting. Unfortunately, I got the same error message. When I looked at the submitPaymentForm implementation, I could see that the input parameter was not actually used:

function submitPaymentForm(fields) {
    var $form = $("#billing-info-form");
    warnLeave = false;
    $form.get(0).submit();
}

I looked at the form fields that had loaded, and they had complex names like order[TicketOrder][email]. It seemed like it would be difficult to guess the names of the missing ones (I checked the network request and they were not being submitted at all). I then had the idea of finding another Splash event order form, and seeing if I could get the valid form fields from there. I eventually ended up on the ticket page for a music video release party that had a working credit card form. Excited, I copied the form fields into the React order page that I still had up, filled them out, and pressed “Submit”. There was a small bump where it thought that the expiration date field was required and not provided, but I bypassed that client-side check and got a promising-looking spinner that indicated that the order was being processed.

I was all set to be done, when the confirmation page finally loaded, and instead of being for the React conference, it was for the video release party. Evidently starting the order in a second tab had clobbered the some kind of cookie or other client-side state from the React tab. I had thus burned the order page on Bret's computer. However, Case had been dutifully keeping the session on his computer alive, so I switched to that one. There I went through the same process, but opened the “donor” order page in a Chrome incognito window. With bated breath I pressed the order button, and was relieved to see a confirmation page for React, and this email to arrive shortly after:

React.js Conf Order Success?

Possibly not quite as epic was my last attempt at working around broken service providers, but it still made for an exciting excuse to be late for lunch. And if anyone wants to go a music video release party tonight in Brooklyn, I have a ticket².

  1. I now see that other Splash order forms have the same network error, so it may have been a red herring.
  2. Amusingly enough, I appeared to have bought the last pre-sale ticket there — they are sold out too.

Two Hard Things #

Inspired by Phil Karlton's (possibly apocryphal) Two Hard Things, here's what I struggle with on a regular basis:

  1. Getting information X from system A to system B without violating the N layers of abstraction in between.
  2. Naming X such that the name is concise, unique (i.e. greppable) and has the right connotations to someone new to the codebase (or myself, a year later).

Saving The Day For (A Few) Veronica Mars Fans #

Yesterday was the release day of the Veronica Mars movie. As a Kickstarter backer, Ann got a digital copy of the movie. For reasons that I'm sure were not entirely technical, it was only available via Flixster/UltraViolet¹, so getting access to it involved registering for a new account and jumping through some hoops.

To actually download the movie for offline viewing, Flixster said it needed a “Flixster Desktop” client app. It was served as a ~29 MB .zip file, so it seemed like a straightforward download. I noticed that I was only getting ~ 30K/second download speeds, but I wasn't in a hurry, so I let it run. The download finished, but with only a ~21MB file that was malformed when I tried to expand it. I figured the WiFi of the hotel that we were staying at was somehow messing with the connection, so I tried again while tethered to my phone. I was still getting similarly slow download speeds, and the “completed” download was still too small. Since 30K/second was definitely under the expected tethered LTE throughput, I began to suspect Flixster's servers as the root cause. It certainly seemed plausible given that the file was served from desktop.flixster.com, which did not seem like a CDN domain². I guess ~60,000 fans were enough to DDoS it; from reading a Reddit thread, it seemed like I was not the only one.

The truncated downloads were of slightly different sizes, but it seemed like they finished in similar amounts of time, so I decided to be more scientific and timed the next attempt. It finished in exactly 10 minutes. My hypothesis was now that Flixster's server (or some intermediary) was terminating connections after 10 minutes, regardless of what was being transferred or what state it was in.

Chrome's download manager has a Pause/Resume link, so my next thought was to use it to break up the download into two smaller chunks. After getting the first 10 MB, I paused the download, disconnected the laptop from WiFi (to make sure the connection would not be reused) and then reconnected and tried resuming. Unfortunately, the download never restarted. I did a HEAD request on the file, and since the response headers did not include an Accept-Ranges header, I assumed that the server just didn't support resumable downloads, and that this path was a dead end.

After spending a few minutes trying to find a mirror of the app on sketchy download sites, a vague memory of Chrome's download manager not actually supporting HTTP range requests came to me. I did some quick tests with curl and saw that if I issued requests with --range parameters I got different results back. So it seemed like despite the lack of Accept-Ranges headers, the server (Apache fronted by Varnish) did in fact support range requests³.

I therefore downloaded the file in two chunks by using --range 0-10000000 and --range 10000000- and concatenated them with cat. Somewhat surprisingly, the resulting zip file was well-formed and expanded correctly. I put a copy of the file in my Dropbox account and shared it on the Reddit thread, it seemed to have helped a few others.

Of course, by the end of all this, I was more excited about having successfully downloaded the client app than getting or watching the movie itself⁴.

  1. As opposed to say, iTunes or Amazon.
  2. Now that I check the download page a day later, it seems to be served from static.flixstercdn.com, so I guess Flixster realized what was going on and fixed the problem.
  3. A closer reading of section 14.5 of the HTTP 1.1 spec showed that servers MAY respond with Accept-Ranges: bytes, but are not required to.
  4. Downloading the actual movie worked fine, I assume it was distributed over a CDN and thus wasn't quite so easily overwhelmed.

Quip: Back to app development #

Though I've mentioned it off-hand a couple of times, I haven't explicitly blogged that I left Google towards the end of 2012¹. I joined Quip, a small start-up in San Francisco that's trying to re-think word processing.

My ​mid-2010 switch to the Chrome team was prompted by a desire to understand “the stack” better, and also a slight fear of being typecast as a frontend guy and thus narrowing my options of projects. However, after several months of working on the rendering engine, I realized that I missed the “we've shipped” feeling created by being on a smaller, focused project with distinct milestones (as opposed to continuously improving the web platform, though I certainly appreciate that as a platform client). I thus switched to the Chrome Apps team, co-leading the effort to build the “packaged apps” platform. We shipped the developer preview at Google I/O 2012, and I'm happy to see that these apps are now available to everyone.

As we were building the platform, I got more and more jealous of the people building apps on top of it. Making samples scratched that itch a bit, but there's still a big gap between a toy app that's a proof of concept and something that helps people get their job done. I also found it hard to cope with the longer timelines that are involved in platform building — it takes a while to get developers to try APIs, making changes requires preserving backwards compatibility or having a migration strategy, etc. In the end, I realized that although I enjoy understanding how the platform works, I don't feel the need to build it myself.

When Bret approached me about Quip in the fall of 2012, a lot of the above clicked together for the first time. It was also very appealing to get to work (with a small yet very capable team) on something that I would be using every day².

My fears of becoming overly specialized were assuaged by being at a startup, where we're always resource constrained and every engineer has to wear multiple hats. Depending on the day, I may be working in C++, Objective-C, Java, JavaScript, or Python. The changes can range from fixing a bug in protocol buffer code generation to figuring out how to mimic iOS 7's translucent keyboard background color³.

My time working on Chrome also turned out to be well spent. Even on my first day I had to dig through the source to understand some puzzling behavior⁴. It's also been interesting to see how quickly my perspective on certain platform (anti-)patterns has changed. When I was working on Chrome, slow-running unload event handlers and (worse yet) synchronous XMLHttpRequests were obviously abominations that prevented a fluid user experience. Now that I work on a product that involves user data, those are the exact mechanisms that I need to use to make sure it's not lost⁵.

I also view browser bugs differently now. Before I took them as an immutable fact of life, just something to be worked around. Now I still do the workaround, but I also report them. This is the biggest change (and improvement) between the last time I did serious web development (early 2010) and now — more and more browsers are evergreen, so I can rely on bug fixes actually reaching users. This doesn't mean that all bugs get fixed immediately; some are dealt with more promptly than others (and don't get me started on contentEditable bugs).

The same benefit applies not just to bugs but also to features. I've been wishing for better exception reporting via the global onerror handler, and now that it's finally happening, we get to use it pretty quickly. It's not just Chrome or WebKit — when we started to look into Firefox support it didn't render Mac OS X Lion-style scrollbars, but shortly after our launch that shipped in Firefox 23, and we didn't have to implement any crazy hacks⁶.

I hope to write more about the technical puzzlers that I've run into, since that's another way in which this experience has been very different from Google. At a big company there are mailing lists with hundreds of engineers that can answer your questions. Now that blogs (and Stack Overflow) fulfill the same need, I'd like to return the favor.

  1. This post a bit narcissistic. The primary audience is myself, 20 years from now.
  2. I'm guessing that Quip has more design docs written than the average year-old startup, since creating them is a chance to do more dogfooding.
  3. And on some days, I fix the espresso machine.
  4. If your HTTP response has a Last-Modified header and no other caching directives, Chrome will cache it for 10% of the time delta between the current time and the last modified time.
  5. Quip auto-saves frequently, but there's still a small window between when a change is made and when a tab could be closed during which data could be lost.
  6. Not knocking the hacks, we have our own share of crazy code, but the less code that we ship to the users, the better.

Internet Memories #

An Accidental Tech Podcast episode from a few months ago had some reminiscing of the ways to get online in the mid-90s (most notably, the X2 vs. K56flex debate). I thought I would write down some of my earliest recollections of Internet access¹.

The first (indirect) Internet access that I recall having was in the spring of 1995². My dad had access to an FTP-to-email gateway³ at work, and had discovered an archive that had various space pictures and renderings. He would print out directory listings and bring them home. We would go over them, and highlight the ones that seemed interesting. The next day he would send the fetch requests, the files would be emailed to him, and he would bring them home on a floppy disk. In this way I acquired an early International Space Station rendering and a painting of Galileo over Io. I recall using the latter picture as my startup screen, back in the day when having so many extensions that they wrapped onto a second row was a point of pride, and the associated multi-minute startup time necessitated something pretty to look at.

A little while later, my dad found an archive with Mac software (most likely Info-Mac or WUArchive). This was very exciting, but all of the files had .sit.hqx extensions, which we hadn't encountered before (uuencoding was the primary encoding that was used in the email gateway). .hqx to turned out to refer to BinHex, which Compact Pro (the sole compression utility that I had access to⁴) could understand. A bit more digging turned up that .sit referred to StuffIt archives, and the recently released (and free) StuffIt Expander could expand them. We somehow managed to find a copy of StuffIt that was either self-expanding or compressed in a format CompactPro understood, and from that point I was set.

In the early summer of 1995, almost at the end of the school year, my school got Internet access through the 100 Schools project⁵. It would take until the fall for the lab to be fully set up, but as I recall there was a server/gateway (something in the NEC PC-98 family running PC-UX) connected to 3 or 4 LC 575s (see the last picture on this page). Also at the end of the school year a student who had graduated the year before came by, and told wondrous tales of high-speed in-dorm Internet access. At this point I still didn't have Internet access at home, but I did have a copy of the recently released version 1.1 of Netscape. I learned a bunch of HTML from the browser's about page​⁶, since it was the only page that I had local access to.

Sometime in the fall, we got a Telebit Trailblazer modem. The modem only had a DB-25 connector, so my dad soldered together an adapter cable that would enable it work with the Mac's mini-DIN8 “modem” port. This was used to dial into local and U.S.-based BBSes, at first with ZTerm and later with FirstClass

A little while later, we finally got proper Internet access at home. I had read Neuromancer over the summer, and it left a strong impression. Before dialing up to the ISP I would declare that I was going to “jack in” and demand that the lights be left off and that I not be disturbed, all for a more immersive experience.

In the spring of 1996 we upgraded to a Global Village⁷ Teleport Platinum, going from 9600 baud to 28.8Kbps⁸. Over the summer, the modem developed an annoying tendency to drop the connection without any warning, with no obvious trigger. It eventually became apparent that this was correlated with the air conditioning kicking in. The modem was a U.S. model, designed for 120V. Japan runs at 100V, and though most U.S. electronics worked at the lower voltage, the extra load caused by the air conditioning (on the same circuit) turning on was enough to briefly reset the modem. The problem was solved by a (surprisingly large) 100V-to-120V step up transformer.

Over the next couple of years there was my first domain name, an upgrade to ISDN (a blazing fast 128Kbps when going dual-channel!), a Hotline phase, etc. However, this part is less interesting, both because it became increasingly common to have Internet access, and thus the stories are more alike, and because it's better documented.

I don't expect the contents of this post to have been all that interesting to anyone else. It was still an interesting exercise, both in terms how much I could remember and how much I could reconstruct given my personal archive and what's on the Internet. Given the advantages of digital storage that I had, I'm that much more impressed that memoirs manage to achieve any reasonable level of accuracy.

  1. Also as a way of testing Quip's new link inspector/editor.
  2. To give a bit of context, I was in middle school and living in Japan at the time. The Japan bit is relevant given its lag in Internet adoption.
  3. Some people use the web in this fashion even today.
  4. Despite using it for years, I dutifully clicked “Not Yet” in the shareware startup nag screen every time. Sorry Bill Goodman!
  5. Based on this list of domain names the smis.setagaya.tokyo.jp domain name was activated on May 26, 1995.
  6. As I recall, it was more than just a wall of text, it used both the <center> tag and tables.
  7. For reasons that I still don't understand, Global Village initially had the domain name globalvillag.com.
  8. Later firmware-flashed to 33.6K

How I Consume Twitter #

In light of the Twitter API 1.1 announcement and the surrounding brouhaha, I thought I would take a moment to document how I read Twitter, since it might all have to change1.

It shouldn't be surprising at all that I consume Twitter in Google Reader. I do this with the aid of two tools that I've written, Bird Feeder2 and Tweet Digest3.

Bird Feeder in Google Reader screenshotBird Feeder lets you sign in with Twitter, and from that generates a "private" Atom feed out of your "with friends" timeline. It tries to be reasonably clever about inlining thumbnails and unpacking URLs, but is otherwise a very basic client. The only distinctive thing about it is that it uses a variant of my PubSubHubbub bridge prototype to make the feed update in near-realtime4. What makes it my ideal client is that it leverages all of Reader's capabilities: read state, tagging, search (and back in the day, sharing). Most importantly, it means I don't need to add yet another site/app to my daily routine.

In terms of the display guidelinesrequirements, Bird Feeder runs afoul of a bunch of the cosmetic rules (e.g. names aren't displayed, just usernames), but those could easily be fixed. The rule that's more interesting is 5a: "Tweets that are grouped together into a timeline should not be rendered with non-Twitter content. e.g. comments, updates from other networks." Bird Feeder itself doesn't include non-Twitter content, it outputs a straight representation of the timeline, as served by Twitter. However, when displayed in Reader, the items representing tweets can end up intermixed with all sorts of other content.

Bird Feeder lets me (barely) keep up with the 170 accounts that I follow, which generate an average of 82 tweets per day (it's my highest volume Reader subscription). However, there are other accounts that I'm interested in which are too high-volume to follow directly. For those I use Tweet Digest, which batches up their updates into a once-a-day post. I group accounts into digests by theme using Twitter lists (so that I can add/remove accounts without having to resubscribe to the feeds). It adds up to 54 accounts posting an average of 112 tweets per day.

This approach to Twitter consumption is very bespoke, and caters to my completionist tendencies. I don't expect Twitter's official clients to ever go in this direction, so I'm glad that the API is flexible enough to allow it to work, and hopefully that will continue to be the case.

  1. Though I'm hoping that I'm such an edge case that Twitter's Brand Enforcement team won't come after me.
  2. Bird Feeder is whitelisted for Ann's and my use only. This is partly because I don't want it to attract Twitter's attention, and partly because I don't need yet another hobby project ballooning into an App Engine budget hog. However, you're more than welcome to fork the code and run your own instance.
  3. It's amazing that Tweet Digest is almost 5 years old. Time flies.
  4. This was a good excuse to learn Go. Unfortunatley, though I liked what I saw, I haven't touched that code (or any other Go code) in 6 months, so nothing has stuck.

Protecting users from malware via (strict) default settings #

One of the features in Mountain Lion, Apple's newest OS X release, that has gotten quite a bit of attention is Gatekeeper. It's a security measure that, in its default configuration, allows only apps downloaded from the Mac App Store or signed with an Apple-provided (per-developer) certificate to run. This a good security move that makes a bunch of people happy. The assumption is that, though Gatekeeper can be turned off, it's on by default, so it will be a great deterrent for malware authors. For example, here's an excerpt from John Siracusa's Mountain Lion review:

All three of these procedures—changing a security setting in System Preferences, right-clicking to open an application, and running a command-line tool—are extremely unlikely to ever be performed by most Mac users. This is why the choice of the default Gatekeeper setting is so important.

However, a cautionary tale comes from the web security world. The same-origin policy is an inherent1 property of the web. This means that, barring bugs, it shouldn't be possible to have cross-site scripting (XSS) not allowed by the host site. But at the same time that scripting ability was added to browsers, the javascript: URL scheme was introduced, which allowed snippets of JavaScript to be run in the context of the current page. These could be used anywhere URLs were accepted (leading to bookmarklets), including the browser's location bar.

In theory, this feature meant that users could XSS themselves by entering and running a javascript: URL provided by an attacker. But surely no one would just enter arbitrary code given to them by a disreputable-looking site? As it turns out, enough of people do. There is a class of Facebook worms that spread via javascript: URLs. They entice the user with a desired Facebook feature (e.g. get a "dislike" button) and say "all you have to do to get it is copy and paste this code into your address bar and press enter."2 Once the user follows the instructions, the attacker is able to impersonate the user on Facebook.

If the target population is big enough, it doesn't matter what the default setting is, or how convoluted the steps are to bypass it. 0.1% of Facebook's ~1 billion users is still 1 million users. In this particular case, browser vendors are able to mitigate the attack. Chrome will strip a javascript: prefix from strings pasted into the omnibox, and I believe other modern browsers have similar protections. For the attacker's perspective, working around this involves making the "instructions" even more complicated, leading to hopefully a large drop-off in the infection success rate, and perhaps the dropping of the attempt altogether.

This isn't to say that Gatekeeper as deployed today will not work. It's just that it'll take some time before the ease-of-use/configuration and security trade-offs can be evaluated. After all, javascript: URLs were introduced in 1995, and weren't exploited until 2011.

  1. So inherent that it was taken for granted and not standardized until the end of 2011.
  2. I'm guessing that it's not helpful that legitimate sites occasionally instruct users to do the same thing.

My Chrome Apps Journey #

A couple of years ago, I switched from the Google Reader team to the Chrome team1. I focused on the WebKit side, working a bunch on layout tests and associated tooling. I became a reviewer, messed around with events and generally scratched my "move down the stack" itch. However, I realized I missed a few things: Spec compliance and tooling are very important, but they don't necessarily trigger that awesome "we've shipped!" feeling (cf. HTML5 being a living document). I also missed having something concrete to put in front of users (or developers).

Those thoughts were beginning to coalesce when, fortuitously, Aaron approached me in the spring of 2011 about joining Chrome's "Apps and Extensions" team. I'd been a fan of Aaron's work since my Greasemonkey days, and the Chrome extension system had impressed me with its solid foundations. After spending some time getting to know life outside of src/third_party/WebKit, I ended up focusing more on the apps side of things, implementing things like inline installation and tweaking app processes.

Historically, apps and extensions were handled by the same team since Chrome apps rose out of the extension system. They share the manifest format, the packaging mechanism, the infrastructure to define their APIs, etc. The lines were further blurred since apps could use extension APIs to affect the rest of the browser.

As we were discussing in the fall of 2011 where extensions and apps were heading, it became apparent that both areas had big enough goals2 that having doing them all with one team would result in a lack of focus and/or an unwieldy group with lots of overhead. Instead, there was a (soft) split: Aaron remained as tech lead of extensions, and I took on apps.

We've now had a few months to prototype, iterate, and start to ship code (in the canary channel for now). Erik and I gave an overview of what we've been up to with Chrome apps at last week's Google I/O:


(DailyJS has a very good summary, and the presentation is available as an app in our samples repository.)

It's still the early days for these "evolved" Chrome packaged apps. We're pretty confident about some things, like the app programming model with background pages that receive events. We know we have a bunch of work in other areas like OS integration, bugs and missing features. There are also some things we haven't quite figured out yet (known unknowns if you will), like cross-app communication and embedding (though I'm getting pretty good at waving my hands and saying "web intents").

What makes all this less daunting is that we get to build on the web platform, so we get a lot of things for "free". Chrome apps are an especially good opportunity to play with the web's bleeding edge3 and even the future4.

Going back to the impetus for the switch that I described at the start of this post, doing the I/O presentation definitely triggered that "We've launched!" feeling that I was looking for. However, it's still the early days. To extend the "launch" analogy, we've been successful at building sounding rockets, but the long term goal is the moon. We're currently at the orbital launch stage, and hopefully it'll go more like Explorer 1 and less like Vanguard.

I'm planning on blogging more about apps. In the meantime, if your curiosity is piqued, you can watch the presentation, dig around the samples repository, or peruse the docs. If you'd like to get in touch, try the chromium-apps@chromium.org mailing list or #chromium-apps on freenode.

  1. Though I have trouble letting go sometimes.
  2. The extensions team had its own talk at I/O about their evolution. Highly recommended viewing, since the same factors influenced our thinking about apps.
  3. See Eric's The Web Can Do That!? presentation.
  4. See Dimitri's and Alex's Web Components presentation. They can be used by apps (and extensions) that add the "experimental" permission.

Life Imitates Art #

Cryptonomicon (1999):

[...] guerilla mechanic teams had been surveilling Randy’s grandmother ever since and occasionally swiping her Lincoln from the church parking lot on Sunday mornings and taking it down to Patterson’s for sub rosa oil changes. The ability of the Lincoln to run flawlessly for a quarter of a century without maintenance — without even putting gasoline in the tank — had only confirmed Grandmother’s opinions about the amusing superfluity of male pursuits.

Cherry (2012):

Cherry is the carwash that comes to you. Park anywhere, check in online, and we'll wash your car right where you left it. Only $29.99 per wash, which includes exterior, interior, vacuum, tires, rims, and an air freshener of your choice.

Being a new parent as told through Reader trends #

Paternity leave has meant lots of 10-20 minute gaps in the day that can filled with Reader catchup:

Google Reader Trends 30 day chart

Even when trying to put the baby to sleep at 1am or 5am, thanks to 1-handed keyboard shortcuts:

Google Reader Trends hour of day chart

Stack Overflow Musings #

I recently spent an enjoyable Sunday morning tracking down a Chrome extension-related bug. Though not as epic as some past bugs, it was still interesting, since it involved the interaction of four distinct codebases (wix.com's, Facebook Connect's, the extension, and Chromium's).

The reason why I'm writing about it here (or rather, just pointing it out), is because it seemed like a bit of waste to have that experience live only on Stack Overflow. It's not that I don't trust Stack Overflow (they seem to have good intentions and deeds). However, I'm no Jon Skeet, Stack Overflow isn't a big enough part of my online life that I feel like I have an enduring presence there. The test that I apply is "If I vaguely recall this piece of content in 10 years, will I be able to remember what site it was on? Will that site still be around/indexed?" The answer to the latter is most likely yes, but to the former, I'm not so sure (I already have a hard time searching across the mailing lists, bug tracker and Stack Overflow silos).

On the other hand, a blog post is too heavyweight for every piece of (notable) content. The lifestreaming fad of yesteryear also doesn't seem right, I don't want this aggregation to be necessarily public or a destination site. ThinkUp (and a plugin) seems like what I want, if only I could get over the PHP hurdle.

My earlier stance on Stack Overflow was based on being a pretty casual user (answering the occasional Reader question, using it as a reference when it turned up in search results). Now that it's an official support channel, I've been using it more, and the Kool Aid has started to wear off. For every interesting question, there are repeated ones that end up being about the same topic. For Chrome extension development, a recurring issue is not understanding that (nearly) all APIs are asynchronous.

Is the right thing there to mark them as duplicates of each other, since they have the same underlying cause? I tried that recently, and the moderator did not agree (relatedly, I'm amused that Eric Lippert's epic answer about local variables is on a question that was later closed as a duplicate). Even if closing as dupes were OK, what should the canonical answer look like? Presumably it would have to be something generic, by which point it's not all that different from the section in the official documentation that explains asynchronous functions. Is it just a matter of people not knowing the right terminology, so they will never search for [chrome extension api asynchronous]? Is the optimal end state a page/question per API function asking "Why does chrome.<API name here>() return undefined?" with a pointer to the documentation?

Adventures in Retro Computing #

One of the big assignments in my 7th English class was to write an autobiographical composition entitled "Me, Myself & I". This being 1994, "multimedia" was a buzzword, so students were also given the option of doing the assignment as an "interactive" experience instead*. I had been playing around with HyperCard, so I chose that option (it also meant extra computer time while the rest of the class was writing things out long-hand). I recall the resulting HyperCard stack being fun to work on, and it featured such cutting-edge things as startup 3D animation rendered with Infini-D (albeit, with the trial version that was limited to 30 frames only).

I'm a bit of a digital packrat, so I still have the HyperCard stack that I made 16 years ago. I recently remembered this and wanted to have a look, but lacked a direct way to view it. Thankfully, there are many options for emulating mid-90s 68K Macs. Between Basilisk II, a Quadra 650 ROM, Apple distributing System 7.5.3 for free, and a copy of HyperCard 2.4, I was all set. I was expecting to have more trouble getting things running, but this appears to be a pretty well-trodden path.

Me, Myself & I Screenshot I was pleasantly surprised that the full stack worked, including bits that relied on XCMDs to play back movies and show custom dialogs. The contents are a bit too embarrassing personal to share, but it contains surprisingly prescient phrases like "I will move to California and work for a computer company".

This stack also represents one of my earliest coding artifacts (outside of Logo programs that unfortunately got lost at some point), so I was also curious to look at the code. Unfortunately whenever that stack was loaded, all of the development-related menu commands disappeared. I remembered that stacks have user levels, and that lower ones are effectively read-only. I tried changing the user level in the Home stack, but to no effect: as soon as my stack was brought to the foreground, it was reset back to the lowest level. Hoping to disable script execution, I engaged in a bit of button mashing. Instead I rediscovered that holding down command and option shows all field outlines, including invisible fields. 13-year-old me was clever enough to include a backdoor – a hidden button in the lower right of all cards that when pressed reset the user level back to the development one.

Code-wise, 13-year-old me did not impress too much. There was a custom slider that moved between different events in my life, showing and hiding text in a main viewing area that was awfully repetitive:

on mouseDown
  repeat while the mouse is down
    set location of me to 118, mouseV()
    if mouseV() < 91 then
      set location of me to 118, 91
      walkfield
      exit mouseDown
    end if
    if mouseV() > 238 then
      set location of me to 118, 238
      stmarysfield
      exit mouseDown
    end if
  end repeat
  if mouseV() >= 91 and mouseV() <= 103 then
    set location of me to 118, 91
    walkfield
  end if
  if mouseV() > 103 and mouseV() <= 127 then
    set location of me to 118, 115
    talkfield
  end if
  ...and so on
end mouseDown

on walkfield
  play "Click"
  show card field walk
  hide card field talk
  hide card field beach
  hide card field garden
  hide card field school
  hide card field japan
  hide card field stmarys
end walkfield

on talkfield
  play "Click"
  hide card field walk
  show card field talk
  hide card field beach
  hide card field garden
  hide card field school
  hide card field japan
  hide card field stmarys
end walkfield

With Rosetta being removed from Lion, PowerPC-only Mac OS X software is next on the list of personally-relevant software to become obsolete (Iconographer in this case). Thankfully, it looks like PearPC is around in case I get nostalgic about 18-year-old me's output.

* I was initially going to have a snarky comment about the teacher** not realizing that the web was the way of the future, but after thinking about it more, having this level of flexibility was great, regardless of the specific technologies involved.

** Hi Mr. Warfield! Good luck with whatever is next!

In Praise of Incrementalism #

Pinky: "Gee, Brain, what do you want to do tonight?"
The Brain: "The same thing we do every night, Pinky — try to take over the world!"

My memory is a bit fuzzy, but from what I remember, if the Brain had set his sights slightly lower, he definitely could have taken over a city, or perhaps a small state as the first step in one night, and left the rest of the world to following nights.

Along these lines, I was talking with Dan about why I thought of Stack Overflow/Exchange as being significantly more successful than Quora. I wouldn't be surprised to find out that they have comparable traffic, users, or other metrics. However, from an outsider's perspective, Stack Overflow made fast progress on its initial goal of being a good programming Q&A site. There was never a clear mission accomplished moment, but at this point its success does not feel in doubt. There were follow-on steps, some more successful than others, and a general upward-and-onward feeling.

On the other hand, Quora's goals from the start were outrageous (in a good way): “Imagine a world where I knew everything that I wanted to know, as long as someone else in the world knew it.” I'm sure that having J.J. Abrams give his thoughts on monster/action scenes is a milemarker on that path. However, it's harder to see how far they've come or to feel like the site has a well-functioning foundation/core functionality, since the path is a continuous curve rather than a step function.*

Google might be considered a counter-example to this; from very early on its goal was quite broad and audacious. However, having a steady stream of corpora to add shows definite progress. There is also the matter of perceived goals versus actual internal goals. Thefacebook was long discounted by some as being a site just for college kids, surely even after they set their sights higher. Having others underestimate your ambition (but not too much, lest they ignore you) seems beneficial.

In the end, this probably reflects my personal bias towards the incremental Ben and Jerry's model. Though less exciting, over time it can lead to pretty good results.

* All of this might be a reflection of my being more aware of what Stack Overflow has done over the years via their podcast; Quora is harder to keep up with.

Non-fiction books for (curious) busy people #

I'm in the process of re-reading The Baroque Cycle and have gotten curious about Newton's time at the Royal Mint. Ideally, I would like something more detailed than the two paragraphs that Wikipedia devotes to this, but shorter than a 300+ page book*. I've had similar experiences in the past: no matter how much The Economist raved about a ~1000 page history of the British Navy, I was never able to commit to actually reading it all the way through. I think this is more than Internet-induced ADD; I manage to read a book every 4-6 weeks, and dedicating a slot to such a unitasker seems wasteful.

I realize that producing a shorter book on the subject may not be any cheaper or less resource/research-intensive than a long book. I would even be willing to pay the same amount for the digested version as I would for the full version. With recent developments like Kindle Singles there also wouldn't be the issue of fixed production/distribution costs that should be amortized by creating a longer book. Though fiction-centric, Charles Stross has a good explanation of why books are the length that they are.

I used to think that abridged editions, CliffsNotes, and the like were an abomination (as far as not getting the experience the author intended) and for lazy people. To some degree I still do; I think ideally these alternate editions should be produced by the same author, or with the author's blessing.

* As it turns out, there is a 128-page 1946 book about Newton's time at the Mint. Perhaps there was less need to pad then?

Update later that day: Based on the endorsement on Buzz I'll give the (modern) Newton book a try. Part of the reason why I was soured on longer non-fiction books was that I tried reading Operation Mincemeat and was put off by the amount of seemingly extraneous background information and cutesy anecdotes. Incidentally, Operation Mincemeat has a brief appearance in Cryptonomicon, another Neal Stephenson book – I promise that I read other authors too.

Bloglines Express, or How I Joined The Google Reader Team #

Since Bloglines is shutting down on November 1, I thought it might be a good time to recount how I joined the (nascent) Google Reader team thanks to a Greasmonkey script built on top of Bloglines.

It was the spring of 2005. I had switched from NetNewsWire to Bloglines the previous fall. My initial excitement at being able to get my feed fix anywhere was starting wear off -- Bloglines was held up by some as a Web 2.0 poster child, but the site felt surprisingly primitive compared to contemporary web apps. For example, such a high-volume content consumption product begged for keyboard shortcuts, but the UI was entirely mouse-dependent. I initially started to work on some small scripts to fill in some holes, but fighting with the site's markup was tiring.

I briefly considered building my own feed reader, but actually crawling, storing and serving feed content didn't seem particularly appealing. Then I remembered that Bloglines had released an API a few months prior. The API was meant to be used by desktop apps (NetNewWire, FeedDemon and BlogBot are the initial clients are mentioned in the announcement), but it seemed like it would also work for a web app (the API provided two endpoints, one to get the list subscriptions as OPML, and one to get subscription items as RSS 2.0).

This was also the period when Greasemonkey was really taking off, and I liked the freedom that Greasemonkey scripts provided (piggyback on someone else's site and let them do the hard work, while you can focus on the UI only). However, this was before GM_xmlhttpRequest, so it looked like I'd need a server component regardless, in order to fetch and proxy data from the Bloglines API.

Then, it occurred to me that there was no reason why Greasemonkey had to inject the script into a "real" web page. If I targeted the script at http://bloglines.com/express (which is a 404) and visited that URL, the code that was injected could make same-origin requests to bloglines.com and have a clean slate to work with, letting me build my own feed reading UI.

Once had the basic framework up and running, it was easy to add features that I had wanted:

  • Gmail-inspired keyboard shortcuts.
  • Customized per-item actions, for example for finding Technorati and Feedster backlinks, or posting to Del.icio.us (cf. "send to" in Reader).
  • Specialized views for del.icio.us and Flickr feeds (cf. photo view in Reader).
  • Inline viewing of original page content (including framebuster detection).

A few weeks into this, I saw an email from Steve Goldberg saying that a feed reading project was starting at Google. I got in touch with him about joining the team, and also included a pointer to the script at that state. I don't know if it helped, but it clearly didn't hurt. As it turned out, Chris Wetherell, Jason Shellen, Laurence Gonsalves and Ben Darnell all had (internal) feed reading projects in various states; Reader emerged out of our experiences with all those efforts (Chris has a few more posts about Reader's birth).

Once the Reader effort got off the ground it seemed weird to release a script that was effectively going to be a competitor, so it just sat in my home directory (though Mark Pilgrim did stumble upon it when gathering scripts for Greasemonkey Hacks). However, since Bloglines will only be up for a few more days, I thought I would see if I could resurrect the Greasemonkey script as a Chrome Extension. Happily, it seems to work:

  1. Install this extension (it requires access to all sites since it needs to scrape data from the original blogs
  2. Visit http://bloglines.com/listsubs to enter/cache your HTTP Basic Auth credentials for the Bloglines API.
  3. Visit http://persistent.info/greasemonkey/bloglines-express/ to see your subscriptions (unfortunately I can't inject content into bloglines.com/express since Chrome's "pretty 404" kicks in.

Or if all that is too complicated, here's a screencast demonstrating the basic functionality:

For the curious, I've also archived the original version of the Gresemonkey script (it actually grew to use GM_xmlhttpRequest over time, so that it could load original pages and extra resources).

Somewhat amusingly, this approach is also roughly what Feedly does today. Though they also have a server-side component, at its core is a Firefox/Chrome/Safari extension that makes Google Reader API requests on behalf of the user and provides an alternative UI.

Moving down the stack #

After more than 5 years (!) of working on the Google Reader team, I'm switching to working on Google Chrome, and specifically the "web platform" piece that uses WebKit.

Reader has heen a lot of fun, I got a lot of things done and got to interact with a lot of users. Though Reader is by no means done, I've been having an "itch" lately to break out of my frontend development niche (where I've been working at higher and higher levels of abstraction lately).

I think there's still a value in knowing how things work at lower levels (even if there's way too much to know), and as much I told myself that I should skim through the Apache, Linux kernel or V8 sources to understand how they work, I never quite had the time. Now that web browsers are approaching OS levels of complexity, it seems like a good time to be forced to dive in.

Of course, the hope is that my web development background will help inform my Chrome/WebKit work, and I'm not the first to make this transition. It'll also be an interesting transition to be working on an open source project, with my (so far not very interesting) commits visible to the world.

persistent.coffee #

I've been trying my hand at latte art. Though I have a very long way to go, I've been documenting my efforts, with a hope of learning from my mistakes. Blogger's mobile support makes it pretty easy to collect pictures, and I've finally gotten around to making a decent template for the "blog."

coffee.persistent.info is the result. Technically, this isn't a Blogger template, since I just have some static HTML as the content. Instead, it uses the JSON output that Blogger's GData API supports. Rendering the page in JavaScript allows for more flexibility. I wanted to make pictures that I liked take up 4 slots (a layout inspired by TwitterPoster). This imposed additional constraints (in order to prevent overlap between sequential large pictures). The display is generally reverse-chronological starting from the top left, but images are occasionally shuffled around to prevent such overlaps. There is also a bit of interactivity, the pictures are clickable to display larger versions. To help with all this, I've been experimenting with jQuery (also on Mail Trends), and am liking it quite a bit.