Air Canada is on my shit list

Air Canada is on my shit, or as I like to call it, my “Continental list”. (Guess which airline I hate the most.)

At this point, I’m seriously contemplating cancelling my upcoming conferences. I’m tired of the airline industry. I’m tired of their complete disregard for their customers. And I’m tired of shelling out over $200 because they can’t do their job. I can’t afford to do this anymore.

I was supposed to fly back from the GNOME documentation sprint last Wednesday. I had a 5:00 flight on Air Canada, direct from Toronto to Cincinnati. I got through customs and security without much incident. (I was misinformed about the customs procedure by an Air Canada employee, and I was selected for additional screening, but that’s all minor.) I got an overpriced chicken sandwich and sat at my gate. I pulled out my laptop and worked on the help.

It snowed in Toronto Wednesday. There was a dusting of snow on the ground when I woke up, and it really picked up as I headed to the airport. I grew up near Chicago. I understand crazy weather and lake-effect snow. I was fully expecting delays. So when they announced a delay, I sighed and kept working. When they changed gates, I sighed and walked to the new gate. When they announced they were down to one runway and there would be further delays, I sighed again. You have to deal with what nature hands you. As long as I could get home, I didn’t mind waiting.

Then they cancelled the flight. This was maybe two hours after when our flight was supposed to have left, which struck me as very early to cancel flights. They still had hours and hours to try to push flights out. Not only did they cancel the flight, they told us that there was no guarantee we’d be rebooked. Really. We all paid for a service, and they had no obligation to provide that service.

They sent us to a customer service counter behind security. Disheartened, we all shuffled down the concourse and stood in line. We waited. Then we were told that they couldn’t rebook us at that counter, and that we’d have to leave security and go through customs to rebook.

When you fly to the US from Toronto, you actually go through US customs in Canada. You’re in a special part of the airport that is, effectively, the USA. It’s usually very convenient. You land in a domestic gate and don’t have to deal with customs at your destination. But if you need to go back out of security, as we did, you have to go through Canadian customs. And they ask you questions like “Where are you coming from?” Um, “Gate 161”. “When’s the next flight?” Oh, hey, good question.

What this means is that, before you can get to the ticketing counter to rebook, you have to collect your bags. Another thing we had to wait for. When we got to the baggage carousal, there weren’t many people there. I think we were one of the first flights cancelled. But our bags didn’t come. We waited for nearly an hour and a half for our bags. Meanwhile, other flights were cancelled. Those passengers arrived, their bags came, and they went through customs. They were already out there filling up the rebooking line, while we were still waiting for our bags.

When we finally got through, we headed to the customer service desk, the one we were told to go to. There were surprisingly only about 20 people there. We waited. Airline employees told us we were in the right place to be rebooked. Then another employee told us we were in the wrong line. We had to go downstairs. More wasted time.

We went down to a mob of 300 people. I saw three employees working. If they can each process a customer in five minutes (and that’s being very generous, in my experience), that’s eight hours in line. And you just know they’re going to close when you get near the front of the line. (Yes, it’s happened. See paragraph 1 about my least favorite airline.) Eight hours to not even talk to anybody about the flight that they’ve already said they probably won’t even rebook? No thanks.

So I found a couple of guys who decided to drive the next morning. They offered to let me tag along. (Dean and Tony, if you happen across this blog, you helped a stranger in need, and I thank you.) But we were driving at 5:00 in the morning, so I had to get a hotel room. The only hotel near the airport with rooms available was the Crowne Plaza. Five hours of sleep at the Crowne Plaza: $172 USD. (This blog post is already too long, so I won’t go into how badly they screwed up when I tried to get a quick bite to eat at the bar.)

Here’s the kicker: While driving to the hotel, there was no snow. It stopped. This was not a surprise. Every TV in the airport is showing CP24. All the passengers were looking at the radar on their smart phones. We all knew the snow was going to stop. I have a hard time believing Air Canada didn’t know as well. And we could see two planes coming in for a landing at the same time, which means they got more runways open.

Flights were getting out. My friend Phil got home to the UK, and he flew later than I did. The snow stopped. The runways opened. Why did they cancel our flights? Because we were a small flight without enough passengers to care about. It wasn’t profitable to try to get us home. We didn’t matter. And that’s what pisses me off the most.

Mallard can do that?!

Phil mentioned the future of Desktop Help, but he was sparse on details. Let me fill in the blanks. With Mallard, we worry mostly about structure and content, and we leave the rendering details to Yelp. Sometimes we want to influence the style, and for that we use Mallard’s style hints. But sometimes, just sometimes, for very special pages, we want to inject some pizzazz.

We can do that too. Watch:

This is using some custom style hints and a very small Mallard extension, which falls back gracefully thanks to Mallard’s well-defined rules for extensions and fallback.

This is still a work in progress. And with work continuing into the night at the help hackfest in Toronto, it’s very much in progress.

Free XML Press Book for Open Help Conference Speakers

Have you submitted a talk proposal for the Open Help Conference yet? If not, here’s an extra incentive: XML Press is providing a free book to every speaker. You can choose between these three fantastic titles:

In light of the promotion, the proposal deadline has been extended to Martch 25. But why wait? Submit your talk proposal today.

The Open Help Conference focuses on how we can provide better help in open and collaborative communities. It’s not just about documentation; we’re also interested in participatory support for (and by) users. Please share your experiences, whether it’s about documentation, support, or communities.

Open Help Conference

Confessions of a Window Shader

I’ve been a window shader for a very long time. For those that don’t know, window shading causes the main body of a window to disappear, leaving just the titlebar in place. It’s an alternative to minimizing (GNOME 2, Windows), iconifying (OS X), and hiding (GNOME 3). I shade windows by double-clicking the titlebar. There’s a hidden option to enable this in GNOME 2.

Window shading has been around for a long time. It’s not even exclusive to *nix desktops. It was used in pre-X versions of MacOS. I seem to be one of the few remaining window shaders in the modern desktop world, so I want to share my experience. I’m not trying to debate what’s better, and I’m certainly not demanding that designers cater to my whims. I’m just sharing how I use my desktop.

I have no problems with overlapping windows. In fact, I rely on them. I often read something in one window while writing something in another. Shifting my gaze is much faster than changing windows in a window list. This is important to me, because I do it a lot. I have terrible short-term memory. I can tell you what lot we parked in at Disney World when I was 8, but I can’t tell you what I read five minutes ago. Consequently, I almost never maximize windows. I confess, I really don’t understand maximizing windows on large, wide-screen monitors. (Mine is 1680×1050.) It’s too wide to read. My windows are at their natural size, and that size depends on what’s in them.

But when you have lots of small, overlapping windows, it’s easy to lose track of them. I have a fairly static arrangement of windows. I know my web browser and other document windows cascade from the top left. I know my terminals are flush bottoom. I know my smaller utility windows are somewhere in the middle of the screen. I don’t really think about where the windows are, because I have years of habit ingrained in me. Habit helps me.

When I need to get behind a window, or when I don’t want to look at it, I shade it. I could just as easily minimize it to get it out of my way. There’s no practical difference in the hiding act. The difference comes in when I want that window back. And more often than not, I want it back soon.

Window lists and zoom overviews have little to no spatial resemblence to how your windows are (or were) arranged. But shading leaves your windows where they were, just smaller. Because I have strong window placement habits, I don’t have to hunt for the window I want in some arbitrary list or grid. I go to where the window is. It’s in the same place as I left it. And as a result, I spend almost no time looking for windows.

I’ll probably have to change my habits soon, because modern desktops are moving away from supporting this kind of feature. But with all the innovation that’s happening, I’m hopeful that somebody will come up with a new way of bringing back hidden windows that actually takes advantage of spatial window placement habits.

Is Your Community a Community?

There’s a lot of buzz in the tech world around the word community. Perhaps stemming from the wrench that social media has thrown into the works, business folks have started tossing around terms like community content, community relations, and community management. But do they know what community means?

“You keep using that word. I do not think it means what you think it means.”

I’ve been a community leader in the open source world for the last eight years. Before that, I was a community member. You cannot be a community leader without being a community member. To understand what a community leader does, you have to understand what a community is.

  1. A community is self-governing. Community members are empowered to decide the direction of the community. If you’re telling people what to do and how to do it, you don’t have a community. You have a workforce.
  2. A community is social. I don’t mean it’s on Facebook or Twitter. Community members might use Facebook or Twitter, but they might use something old-school like mailing lists or IRC. What’s important is that people are talking to each other, getting to know each other, and making decisions together. Without social interaction, you just have a random group of contributors.
  3. A community has a common interest. Community members will have differences on many topics, but there has to be something that brings and keeps them together.

Community leaders don’t set direction or dictate what gets done. That would work against the self-governing nature of communities. And they don’t keep projects or priorities secret. That would be anti-social. So what does a community leader do?

  1. The first job of a community leader is to welcome new members. People will come and go from your community. That’s the nature of things. A lot of people will just stop by because they’re curious. If you don’t welcome them and give them a way to get involved, they’ll be gone before they’ve ever started.
  2. A community leader keeps things running smoothly. If your community is trying to get things done (and when businesses talk about communities, they want those communities to do something), then someone needs to stay on top of those things. You can’t make community members do anything. But you can keep track of what they’ve decided to do and remind them. Leaders are like personal assistants to the entire community.
  3. A community leader keeps the big picture in mind. You have to have your finger on that common interest that’s keeping your community together. When people get bogged down with the trees, you have to remind them of the forest.

Every successful community I know of works this way. And every time a company tries to set up a community, but ignores the nature of communities, it eventually fails. So, do you have a real community, or are you just treating your users like employees?

Developer Documentation Hackfest

I’m looking for a few talented hackers/writers to help us work on the developer documentation for the Gnome platform in Toronto March 17-22. We’re already hosting a hackfest for user help, and we’d like to colocate a developer documentation hackfest there. We’ll be focusing on the Platform Overview and the new Developer Demos. Time permitting, we can also put some extra polish into our (already mostly comprehensive) API references.

Here’s the skills we’re looking for:

  • Familiarity with the Gnome platform. You don’t have to be a Matthias Clasen or an Owen Taylor. In fact, knowing too much can sometimes cause you to overlook what new developers need to know. But you do have to have some experience building applications with Gnome.
  • Some experience writing XML markup. The Overview and Demos are in Mallard. The API references are done with gtk-doc, often with inline DocBook. You don’t need to be an expert on any of these. If you know XML and are comfortable writing it in a text editor, the details aren’t hard to pick up.
  • Patience and a willingness to learn. Effective writing is more than just dumping what you know into a text editor. We’ll have experienced technical writers on hand who will help you plan and organize your material.

If you’re interested, email me at shaunm@gnome.org.

A Quick Primer on Blip Plugins

The nice thing about Blip is that it’s completely open and insanely extensible. It tries very hard to find things like documents and translations wherever it can, but sometimes your content just doesn’t look like what it knows about.

But that’s OK. You can easily add plugins to find and display more information. In fact, everything you see in Blip—every bit of data collected and every page generated—comes from a plugin. Some of them are in the core blip package. Others are provided by the blip-gnome package, which has plugins to scan modules that look like typical Gnome modules.

If you want to deploy Blip for your project, you can create a package full of plugins to deal with the kind of data you actually care about. To show how easy this is, I’ll walk through a plugin I just added for the Evolution Quick Reference Card. This is a PDF document generated from a TeX file that lists common keyboard shortcuts. Obviously, this won’t be picked up by the plugins that handle DocBook, Mallard, and gtk-doc documents.

First, look at the report on the Evolution Quick Reference Card on Blip. Notice that Blip knows the title of the document, when it was modified, and what translations exist.

All of this comes from the evolutionquickref Blip plugin. This is less than 100 actual lines of code. Let’s break it down.

class QuickRefScanner (blip.plugins.modules.sweep.ModuleFileScanner):

In Blip, you create plugins by subclassing known extension points. In this case, there’s a module scanner (which is itself just a plugin). The module scanner reads the SCM history for a module, then walks the tree of files calling each file scanner.

A file scanner contains two methods. The process_file method is called for every file in the module. It’s expected to decide for itself whether it’s interested in the file. After all the files have been walked, the post_process method is called on each file scanner. Sometimes you need to find everything before you can fully process stuff.

    def process_file (self, dirname, basename):
        branch = self.scanner.branch
        is_quickref = False
        if branch.scm_server == 'git://git.gnome.org/' and branch.scm_module == 'evolution':
            if basename == 'Makefile.am':
                if os.path.join (self.scanner.repository.directory, 'help/quickref') == dirname:
                    is_quickref = True
        if not is_quickref:
            return

Here we’re just determining whether we care about the file. Unlike most scanners, we hardcode the SCM server and module to restrict it to Evolution. We then only look at help/quickref/Makefile.am. Why that file? Because we’re going to parse it for language information.

        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            try:
                stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            except:
                data = {'parent' : branch}
                scm_dir, scm_file = os.path.split (rel_ch)
                data['scm_dir'] = os.path.join (scm_dir, 'C')
                doc = blip.db.Branch.select_one (type=u'Document', **data)
                if doc is not None:
                    self.scanner.add_child (doc)
                    self.document = doc
                raise

Blip aggressively timestamps to avoid needless reprocessing. Trust me, in a production environment, you do not want it reprocessing every file from 100+ modules all the time.

Blip provides a convenient timestamp-checking class you can use with Python’s awesome with statement. When you call stamp.check, it checks the mtime of the file against what’s recorded in the database. If the file is newer, or if the database has no timestamp, it just returns and lets your code execute. Otherwise, it throws an exception that’s caught by the with block. If the entire block finishes without exception, a timestamp is added to the database automatically.

(By the way, Blip uses introspection to automatically attach a source function to each timestamp. So this timestamp won’t interfere with timestamps on the same file from another plugin.)

Sometimes you still want to do things when you’re skipping a timestamped file. In this case, we try to find a matching document in the database and add it to the module scanner. Why do we do this? The module scanner deletes child objects (documents, applications, translation domains, etc) from the database unless it’s told about them on each processing run. This is how we prevent stale information from lingering around in Blip. But that means we need to let the module scanner know about the document, even if we’re not going to process it.

            makefile = blip.parsers.get_parsed_file (blip.parsers.automake.Automake,
                                                     self.scanner.branch, filename)

Blip has some built-in parsers for the kinds of files it usually encounters. This one tries to extract some basic information from Makefile.am files. It can’t do everything; after all, an automake file is a template for a shell script. But it does a good job with most data.

            ident = u'/'.join(['/doc', bserver, bmodule, 'quickref', bbranch])
            document = blip.db.Branch.get_or_create (ident, u'Document')
            document.parent = branch
            for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                setattr (document, key, getattr (branch, key))
            document.subtype = u'evolutionquickref'
            document.scm_dir = blip.utils.relative_path (os.path.join (dirname, u'C'),
                                                         self.scanner.repository.directory)
            document.scm_file = u'quickref.tex'
            self.scanner.add_child (document)
            self.document = document

We first create an ident. This is how just about everything is identified in Blip. In many cases, idents match the path of the URL you use to access objects. We use some information from the ident of the parent module. We then get or create an object in the database. If it’s not there already, Blip just makes it for us, and handles some extra administrative work.

After setting some information, we add the document to the module scanner. This is the same thing we did in the except clause above. We have to add it, or it will be deleted.

            translations = []
            for lang in makefile['SUBDIRS'].split():
                if lang == 'C':
                    continue
                lident = u'/l10n/' + lang + document.ident
                translation = blip.db.Branch.get_or_create (lident, u'Translation')
                translations.append (translation)
                for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                    setattr (translation, key, getattr (document, key))
                translation.scm_dir = blip.utils.relative_path (os.path.join (dirname, lang),
                                                                self.scanner.repository.directory)
                translation.scm_file = u'quickref.tex'
                translation.parent = document
            document.set_children (u'Translation', translations)

We now find translations by looking at built subdirectories (except C, because that’s the source). We go through the same business of creating idents and getting or creating database objects.

Then we call set_children on the document. Remember that business of having to add children to the module scanner to keep them from being deleted? This is exactly what the module scanner calls on the module, using the objects that get added to it. This one line prevents us from having stale translations in the database if they’re removed from the module.

We’ll handle everything else in post_process.

        regexp = re.compile ('\\s*\\\\textbf{\\\\Huge{(.*)}}')
        filename = os.path.join (self.scanner.repository.directory,
                                 self.document.scm_dir, self.document.scm_file)
        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            stamp.log ()
            for line in open(filename):
                match = regexp.match (line)
                if match:
                    self.document.name = blip.utils.utf8dec (match.group(1))
                    break

Here we process the quickref.tex file. (That’s what we set scm_file to above.) Again, we do this behind a timestamp so we don’t read a file if it hasn’t changed. We just loop over the lines in the file looking for one that matches our regexp, and use that to extract the title of the reference card. This is a one-off plugin, so we could have just hardcoded the title. But what’s the fun in that?

        rev = blip.db.Revision.get_last_revision (branch=self.document.parent,
                                                  files=[os.path.join (self.document.scm_dir,
                                                                       self.document.scm_file)])
        if rev is not None:
            self.document.mod_datetime = rev.datetime
            self.document.mod_person = rev.person
        self.document.updated = datetime.datetime.utcnow ()

Now we look up the most recent git revision to have touched quickref.tex and record that information. “But Shaun,” you say, “that information is already in the database. Normalize!” I’m not dogmatic about normalization. I put a lot of effort into fast page load times (and there’s still work to be done on some pages). This helps a lot.

We also set the updated field. This just says when Blip last looked at this particular thing. After that (code not reproduced here), we loop over the translations and do the same thing for them.

That’s it. A simple but non-trivial Blip plugin in about 100 lines of code. If you’re feeling adventurous, you could try to compute completion statistics for the translations. They’re not using PO files, but the TeX files are predictable and parseable, so you could put some sort of statistics together.

What kind of data do you have in your modules? As long as it’s parseable, it’s as simple is this to tell Blip about it. Try it yourself. And get in touch; I’m happy to help people build on Blip.

Open Help Conference

Help Hackfest

The documentation team will have a hackfest in Toronto from March 17 to 22. Most of the core contributors are already planning to attend. We’ll finish the desktop help for 3.0 and work on as many application help documents as we can. This could mark the first time in years that we ship a point-oh release with complete documentation.

We’re interested in colocating a developer documentation hackfest. We need to add more content and polish to the new developer demos and finish the Platform Overview for 3.0. Time permitting, we can also review and improve our reference documentation. This can only happen if we have people.

So if you have experience with our developer platform and developer documentation, and would like to spend a week making Gnome a more enticing platform to developers, please email me at shaunm at gnome dot org.

Blip Up and Running

A long time ago, I started working on a universal project tracker called Pulse. (There’s even a half-broken stale-data copy of it running here, for now.) After a series of refactors and rewrites, as well as a name change owing to the ubiquity of the name Pulse, I had Blip. Over the last week, I’ve been setting up an honest-to-goodness live Blip instance on my VPS. This is the real deal. Information is currently updated hourly.

Highlights:

What does Blip do?

Blip scans stuff for information. You hand it some initial set of information (like some jhbuild modulesets) and it crawls everything it finds. It records history. It finds documents and parses them for status information. It finds translations and records their completion. It finds mailing lists and scans their history. (Mailing lists aren’t being scanned on my instance right now, because I’m working on some changes.) It associates all that information with people, and lets you see what everybody’s been up to. If you have an account, you can even watch projects and people.

What else does it do?

Blip can do anything you teach it to do. Everything it does—every piece of data it collects and every report it generates—is done with plugins. The built-in plugins handle a lot of generic cases. The blip-gnome package is a collection of plugins that make Blip play better with the sort of stuff you see in Gnome. Need another document format? A different translation setup? A different version control system? Just write some simple plugins. Or contract a Blip expert to do it for you. 🙂

What’s next?

A few summers back, Florian Ludwig did a bunch of work on Pulse for the Summer of Code. One of the biggest things he worked on was integrating issue tracking systems like Bugzilla. Unfortunately, this stuff hasn’t yet made the transition to Blip. I’d really like to go to Yelp’s Blip page to get a glimpse of bugs not just on bugzilla.gnome.org, but on downstream trackers as well. I’d also love to track releases, along with distribution patches and packages for those releases. Features like these will make Blip a one-stop overview for developers in our crazy distributed world.

Open Help Conference

More Faceted Navigation

I blogged earlier this year about doing faceted navigation with Mallard. The idea sat dormant for a while, but with the new GNOME developer demos, I had a really fun use case.

Here’s the basic idea: Topic pages declare values for facets. These are basically just categorized tags. So my Message Board tutorial has some simple markup that says it’s written in C and it’s teaching you WebKit. You then have a page that collects all the topics and provides selectors to narrow what you’re looking at.

So as I browse the demos, I can say that I’m only interested in C. And perhaps I’m looking at GTK+ and Clutter to see which one is a better fit for a project I’m starting. I can uncheck everything else. Then I’m looking at only demos in C that teach either GTK+ or Clutter. Screencast:

All of the scripting is handled by Yelp. Document authors only need to write some simple declarative markup in a Mallard extension format.

(Aside: When I try to upload an ogv file to WordPress, it tells me the file type doesn’t meet security guidelines. If I rename it to ogg, I can upload it, but WordPress inserts it as audio. I have to write the HTML for the video by hand. Could somebody please make it easier to use Ogg Theora on blogs.gnome.org?)

Open Help Conference

Creative Commons Attribution 3.0 United States
This work by Shaun McCance is licensed under a Creative Commons Attribution 3.0 United States.