Blake (old posts, page 13)

Hurray! (A technical diversion.)

It took a while, but my first patch to Thunderbird was committed today!

changeset:   2727:98a7de404c08
user:        Blake Winton <bwinton@latte.ca>
date:        Fri May 29 10:35:37 2009 +0100
summary:     Bug 45715 - ""Reply to List" [button/(context) menu item]"
             [r=mkmelin,sr=bienvenu,ui-review=clarkbw]

The patch started off by adding a “Reply to List” button to the message header pane as seen below, but after some discussion, the scope was expanded to change the “Reply” button to “Reply All” or “Reply to List”, depending on the message you’re currently viewing.

First cut of the Reply to List button

Of course, there’s still some things I’ve got to add, but those can go in a separate patch, which will be much smaller, and so much easier to get reviewed and committed. And once it is, we might be able to close a 9-year old bug, which would be pretty sweet.

My old/new bike.

The new Fixie.

I did something new today. A while ago, I had bought a 10-speed Bianchi for $30 at a garage sale, with the intention of converting it into a fixed-gear/single-speed bike to tootle around on. Well, today, I took it down to Bikechain, and talked to Steve, who sent me on a merry goose chase picking up various parts. Once I had gotten a new wheel, tire, and tube and biked back to Biekchain, Steve and I put it on the bike, checked the alignment of the cogs and chain, and then I had to head out to buy a new rear cog, and a lock wheel…

Sadly, my bike didn’t have a functional rear wheel anymore, so I had to walk down to Urbane Cyclist to buy the cog and lock. I ended up with a 52:19 ratio, which is harder to start on than the gear ratio I use to start, but isn’t too bad, and doesn’t go as fast as the gear ratio I use to go fast, but again isn’t too bad. Given those two things, I figure it’s pretty close to perfect for my style and level of riding. When I got the cog and lock ring back to Me and my Fixie. Bikechain, we put them on the new rear wheel, hooked everything up, ran into the obligatory problems, fixed them, and finally I was good to go, so I did! The first trip I took was a fairly short one, from U of T over to the Dark Horse Cafe at Queen and Spadina.

The second trip was from Queen and Spadina back home, which was a little longer. I learned a few things from that trip, but let me start off with something I didn’t learn. I had test-ridden a fixed gear bike before, so I had already been almost bumped off by trying to coast, and this time around I was expecting it. So, now on to the things I did learn.


  • Stopping is hard. It’s not that I can’t stop. I’ve got both brakes and pedals. The problem is stopping with one of the pedals in a decent position to start from when I want to start. The other problem is that I really want to coast when I come to the end of a stop, and that totally doesn’t work.

My favourite feature.

  • I can carry it! (As you can see over on the right there.) My commuter bike is a good ride, and very solid, but damn is it ever heavy, especially after I loaded it up with accessories like a rear rack, and panniers, and water bottles. The fixie is simple, clean, and light enough to carry all over the place.

  • The fixie is slower than my commuter bike. Not just slower for the obvious reason (because I don’t have a higher gear to switch to), but it’s also slower for me to start, because I don’t have a lower gear to switch to. It’s really kind of strange, since one of the things I seem to be really good at is starting really quickly from a dead stop. Well, that used to be one of the things I was really good at. On the fixie, not so much.

  • Even though it’s slower I found that the fixie was a far smoother ride. Thinking about it a little more, perhaps because it’s slower. Since it takes me so much longer to stop and start, I found myself slowing down earlier to try and conserve as much momentum as I could.

  • All in all, I think that the new bike is going to be really good for me. It’ll slow me down, and calm me down, which are two things that I think I could use. I can also feel how it’s changing the way I ride, making it more smooth, controlled, and thoughtful; keeping my legs moving to give me more exercise and stop them from seizing up; teaching me how to lift my butt off the seat to go over speed bumps while continuing to pedal.

It’s fun. A lot of fun. I’m glad I finally got the conversion done, and I’m really looking forward to riding it.


Too many robots! A newbie lesson about alertView:clickedButtonAtIndex:

(With thanks to Bunnyhero, both for the title, and the inspiration to get blogging again.)

As you can probably guess, if you’ve read Bunnyhero’s blog post, I recently learned that the AlertView delegate method alertView:clickedButtonAtIndex: can be called multiple times for one AlertView, much to my surprise.

And here’s how I found that out. A couple of weeks ago, I ran across a bug in my still-in-development iPhone game. I was displaying an alert when the game ended, and when the user clicked “Okay” it would go to the next level. And it all seemed fine until one day, when instead of clicking the “Okay” button, I hit the home button to exit the game while the dialog was displayed, and the next time I entered the app, there were, like, 40 robots where I was expecting 8!

As a favour to a friend of mine who’s a prof, I let the bug sit until I could debug the code in front of a room full of undergrads, as a part of the software engineering class he teaches. (The whole experience turned out to not only be something that I wished I could have seen when I was an undergrad, but also something really fun to do from the the industry-type person side of things! Anyone who has a laptop, and some code with a small bug that they don’t mind showing to a bunch of students should really give it a try!). Debugging the problem led me not to the archiver/unarchiver as I was expecting, but instead to the observation that when I hit the home button, my delegate method was being called up to 5 times!

This is why I saw way too many robots, because I was advancing 4 more levels than I should have been. And so when I re-entered the game, it happily put me on level 5-ish, instead of level 2.

The fix was fairly simple, if slightly inelegant. I merely added in a flag to tell me when I was handling an alert, and would only advance the level when I thought I had popped the alert up. (Now that I write it, I wonder if I can set the alert’s delegate to nil when I’m done handling it instead. Thoughts, anyone?)

Seven Things You Probably Didn’t Know About Me

My life, such as it is, is pretty much an open book. I’m not a very complicated person, so I don’t know if I can find seven things that the average reader of this blog probably doesn’t know about me.

Still, that’s no reason not to try. So, here we go.

  1. I’ve had two jobs that didn’t pay me. After the first, I swore that if an employer didn’t pay me again, I would quit, but this last time they laid me off before I found out that I wasn’t getting paid. The most recent company swears that they’re trying to get me my money, but I’m not going to hold my breath.

  2. I hated country music from the first time I heard Billy Ray (father of Miley) Cyrus until Elizabeth Campbell sent me a CD full of the good stuff. So now the only kind of music I don’t like is “New Country”. And Schönberg. Der Mondfleck sucks.

  3. I know enough Visual Basic to write a compiler for it, but you will never ever see it on any of my resumes. Ever! My policy is to only put on my resume things that I would enjoy working with on a day-to-day basis, and VB definitely isn’t one of those things.

  4. I am almost every link but for one in the first 5 pages of Google results for “Blake Winton”. I guess that’s one of the benefits of being on the Internet for 17 years. (Well, kinda. Amy has been online as long as I have, but doesn’t show up until page 8 of the results for her name, mainly because of the Unicorns-riding-fairies artist who shares her name.)

  5. There’s a quarter of a cow in our downstairs freezer. Well, quite a bit less now that Amy and I have eaten a bunch of it. We split half a cow with some friends, and it arrived pre-chopped up, so we’ve been having at least one beef dinner per week, and often two.

  6. I download, watch, and enjoy, American Idol. Less so, now that we’re out of the “Simon says snarky things to really bad singers”, and into the “Simon says snarky things to Ryan”, but it’s still enjoyable. (Go Alexis Grace!)

  7. My first hard drive wasn’t actually 5 Meg, as I like to claim. My father bought two hard drives, and took the 5 Meg one to the office, leaving the 10 Meg one at home. The story about having 11 Meg of RAM is true, though.

Okay, that’s my seven. There were a few more I could think of, but I’m glad David didn’t run across the 25-things-about-you version, since then I would have had no choice but to resort to things you probably don’t want to know about me.

In the tradition of disbelieving in bad luck, I will now tag no-one, thus breaking the chain. (Although, if any of you are feeling verbose, feel free to pick up the meme and post a list of your own.)

Creating invoices with CouchDB.

A while ago I (and the rest of the company I worked for) was laid off, so I was forcibly thrust into the world of consulting. It’s been quite a change, keeping track of my hours and sending out invoices for the work I do. And it’s that second point that I want to talk a little more about.

Perhaps I’m too picky, but there seemed to be a fatal flaw with all the online invoicing tools I tried. One didn’t allow enough clients for me on the free plan. (Yes, I am that cheap, especially when I’ve just been laid off.) Another would have allowed the client to dispute the invoice. I mean, really. This is the invoice. You don’t get to dispute it. Or at least, I don’t particularly want to make it easy for you. So I ended up using an online time tracker, and left the invoice creation step until later.

Well, later rolled around, and I really kinda wanted to get paid, so I downloaded a report of my hours as a csv file, and whipped up a quick Python script to parse it, and import it into a locally running copy of CouchDB.

The python script looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import couchdb.client
import csv
import path

server = couchdb.client.Server('http://localhost:5984/')
if 'timelog' not in server:
    db = server.create('timelog')

db = server['timelog']
print db

def cleanup():
    # Clear out old rows.
    for row in db:
        if not row.startswith("_"):
            del db[row]

def input( docName ):
    print docName
    input = open( docName )
    reader = csv.DictReader( input )
    i = 1
    for row in reader:
        row['Import Doc Name'] = docName
        row['Import Doc Row'] = i
        if 'Client Name' not in row:
          row['Client Name'] = "Client1"
        key = "%s_%03d" % (docName.name, i)
        print "  ", key
        db[key] = row
        i += 1

cleanup()
base = path.path("/Users/bwinton/Documents/Client1")
for file in base.files("*.csv"):
    input( file )

Pretty easy, eh? After that, I had to create a couple of CouchDB views:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// hoursPerDay
map=function(doc) {
  key = [
    doc['Employee name'],
    doc['Client Name'],
    doc['Date of work'], 
  ];
  emit( key, doc );
};
reduce=function (tag, values) {
  sum=0;
  temp = []
  for( var i=0; i<values.length; i++ ) {
    sum += parseFloat(values[i]['Time in hours']);
    temp[i] = {
      'Order':values[i]['Import Doc Row'],
      'Description':values[i]['Description'],
      'Time':values[i]['Time in hours'],
      'Type':values[i]['Activity Type'],
    }
  }
  temp = temp.sort(function(a,b) {
    a = parseFloat(a['Order'])
    b = parseFloat(b['Order'])
    return a - b
  })
  return [sum, temp];
};

// totalHours
// This view is just to save me re-calculating this value every time
// I call the page, because it should only change when we add a new
// document.
map=function(doc) {
  key = [
    doc['Employee name'],
    doc['Client Name'],
    doc['Date of work'], 
  ];
  emit( key, parseFloat(doc['Time in hours']) );
};
reduce=function(keys, values, rereduce) {
  function sum( values )
  {
    retval = 0;
    for (i=0; i<values.length; i++)
    {
      retval += values[i];
    }
    return retval;
  }
  return sum(values);
};

it was a quick snippet of HTML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
  <head><title>Time Log</title>
    <link rel="stylesheet" href="style/blueprint.css" type="text/css">
    <script>
      // Set up some variables.
      months = [ "2009-02" ]
      users = {
        "Blake Winton":{
          invoiceDates : [ "March 1st, 2009."],
          address : ["16 Forman Avenue",
                     "Toronto, ON, M4S 2R2",
                     "bwinton@latte.ca"]
        }};
    </script>
    <script src="script/bwTimesheet.js"></script>
  </head>
  <body>
    <p class="prepend-1"><b>Blake Winton</b><br/>
    16 Forman Avenue<br/>
    Toronto, ON, M4S 2R2<br/>
    bwinton@latte.ca</p>
    <p class="prepend-1"><b>Invoice Number:</b>
      XX-<span id="invoiceNum">000</span><br/>
    <b>Date:</b> <span id="invoiceDate">March 5th, 1973.</span><br/></p>

    <div class="prepend-1 span-1"><b>To:</b></div>
    <div class="span-22 last">
    Client Name<br/>
    16 Client Address</div>
    <p class="span-24">&nbsp;</p>
    <p><span id="timesheet">Timesheet loading...</span>
      <span id="summary">Summary loading...</span>
    </p>
  </body>
</html>

and Javascript:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Get a default month, if one wasn’t passed in.
month = $.url.param('month')
if (typeof(month) == 'undefined')
{
  month = "0";
}
month = parseInt(month)
if ( month < 0 || month >= months.length )
{
  month = 0;
}
invoiceNumInt = month+1;
month = months[month];

// Get a default user, if one wasn’t passed in.
// Yeah, I’ve defaulted it to me.  ;)
user = $.url.param('user')
if (typeof(user) == 'undefined')
{
  user = "Blake Winton";
}
if (!(user in users))
{
  user = "Blake Winton";
}

// Figure out when to start and end the invoice.
invoiceDates = users[user].invoiceDates;
invoiceDateStr = invoiceDates[invoiceNumInt-1];
start = [user, client, month+"-01"];
end = [user, client, month+"-31"];

// Why, oh why, doesn’t Javascript have printf?
function zeroPad(num,count)
{
  var numZeropad = num + '';
  while(numZeropad.length < count)
  {
    numZeropad = "0" + numZeropad;
  }
  return numZeropad;
}

// Update the header, with the address, invoice number, and date.
function updateHeader() {
  var address = $("#address").empty();
  address.append( "<b>"+user+"</b><br/>" );
  for (var i=0; i<users[user].address.length; i++)
  {
    var line = users[user].address[i];
    address.append( line + "<br/>" );
  }

  var invoiceNum = $("#invoiceNum").empty();
  invoiceNum.append( zeroPad( invoiceNumInt, 3 ));

  var invoiceDate = $("#invoiceDate").empty();
  invoiceDate.append( invoiceDateStr );
  updateTimesheet();
}

// Update the timesheet, with hours and descriptions.
function updateTimesheet() {
  var timesheet = $("#timesheet").empty();
  var dbs = $.couch.db("timelog").view("invoice/hoursPerDay",{
    group: true,
    startkey: start,
    endkey: end,
    success: function(r) {
      table = "<div class='span-20 prepend-1 last'><table class='timelog'>";
      rowNum = 0;
      response = r['rows']
      for (var i = 0; i < response.length; i++) {
        var record = response[i];
        var date = record['key'][2];
        var total = record['value'][0];
        var entries = record['value'][1];
        table += "<tr class='row"+rowNum+"'><td>" +
        date + "</td><td>" +
        total + "h</td>";
        for (var j=0; j < entries.length; j++ )
        {
          if (j > 0)
          {
            table += "<tr class='row"+rowNum+"'><td/><td/>";
          }
          table += "<td>" + entries[j]['Time'] + "h - " +
            entries[j]['Description'] + "</td></tr>";
          rowNum += 1;
          rowNum %= 2;
        }
      }
      table += "</table></div>";
      timesheet.append( table );
      updateSummary();
    }
  })
};

// And finally, update the important stuff.
// The total hours, rate, and amount owed.
function updateSummary() {
  var dbs = $.couch.db("timelog").view("invoice/totalHours",{
    startkey: start,
    endkey: end,
    success: function(r) {
      summary = $("#summary").empty();
      content = "<p class='span-20 prepend-1 last'>";
      if ( r['rows'].length == 0 )
      {
        content += "No data available.";
      }
      else
      {
        value = r['rows'][0]['value'];
        content += "<b>Total hours:</b> " + value + "<br/>" +
          "<b>Rate:</b> $" + rate + currency +"/hour.<br/>" +
          "<b>Sub-total:</b> $" + (value*rate).toFixed(2) + "<br/>" +
          "<b>Tax (GST @ 5%):</b> $" + (value*5).toFixed(2) + "<br/>" +
          "<b>Total Payable:</b> $" + (value*(rate+5)).toFixed(2) + "<br/>"
          }
          content += "</p>";
          summary.append( content );
    }
  });
};

// When the document is ready, kick off the updates.
$(function() {
  updateHeader();
});

And we’re done. Not to sound too much like an infomercial, but with JQuery, Blueprint, CouchDB, and Python, it took me only 255 lines of code to get a decent-enough-quality invoice that I could save it as a PDF, send to the client, and get paid. In fact, the client liked it enough (or just wanted things to be standard enough) that they asked that the other consultant on the project use the same template, so I had to spend some time taking my original invoice script and editing it to support more than one user. Either way, I would call it a success.

Home again…

A while ago, I downloaded Aquamacs, an emacs port for OSX, and tried to switch to it as my default editor. I mainly did it because I wanted an editor I could add functionality to, à la Steve Yegge. I used it for a while, and got fairly proficient at it. There were a lot of things about it I really liked, like the Markdown mode, and the ability to edit files on a remote host, and the way I could write small functions in elisp and use them to make me more productive.

But while it worked, and worked well, I didn’t really write all that much extra elisp, and whenever I did, I didn’t really enjoy it. So the other day, when I found myself reading a weblog about the lack of a good graphical text editor on OSX, I found myself agreeing with him. So when I read a later article on how the author had finally switched back to vim, I thought that I might give vim another chance.

Well, it turns out that vim is actually pretty close to a perfect text editor for me. It’s got Markdown support, remote file editing, and not only can I write functions for it, I can write them in Python! As an example, here’s Steve Yegge’s blog-check function in vim/python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function! BlogCheck()
  python << END
:::python
import re
import vim
pattern = re.compile( r"\s" )
count = 0
for line in vim.current.buffer:
  line = pattern.sub( "", line )
  count += len(line)
if count <= 5000:
  message = "Okay so far"
else:
  message = "Dude, too long"
print "%s:  %d chars, %d words" % (message, count, count/5)
:::vim
END
endfunction

Okay, this is cool.

I found myself downtown today, with nothing really to do between 3:30 and 6:30. I suppose I could have headed over to U of T, but it seemed like sort of a long way to go just to connect to the internet and come all the way back again. So instead of making the trek, or wasting two tokens, I took advantage of the 2 hours of free WiFi that comes with my Starbucks card, and stayed near Yonge, drinking green tea with a little honey, and investigating a few bugs for work. After it runs out (in another 31 minutes), I think I'm going to pack up, and walk north to my next appointment, grabbing some dinner on the way. If I'm right, that should put me right where I need to be, right when I need to be there.

Ah, if only everything in my life could fall into place this easily. (The upgrade to Aquamacs 1.6 that I just did also worked out really well. Maybe today is just my day for doing stuff!)

Things on my mind.

I was chatting with a friend and former co-worker of mine today, and asked him why I was never a part of the group guiding where the company was going. (Okay, I actually asked why I was never a part of the management, but in retrospect that was the wrong question, for reasons detailed below. I should have asked the question I said above.) His answer basically boiled down to “You never wanted to be.” One of the things he mentioned was that when I traded salary for extra vacation days (something I was fairly proud of thinking of, since it makes me an easier sell to the CFO, and the two are pretty much equal, if you’re allowed to cash out unused vacation days), it indicated that my priorities lay more with my family than with the company, with the unspoken implication that that attitude isn’t one that will lead to a position of power within the company. That initial assumption led to a series of misunderstandings, and miscommunications, until I was effectively shut out of helping to guide the company.

I guess that’s fair enough, kinda, but it’s a shame that people think that having interests outside of the office means that I’m somehow less interested in the success of the company. Particularly since I didn’t take the vacation days, and was always intending on cashing them out. So, for my next job, I think I’m going to take that option off the table, since I didn’t use the vacation days anyways. (I had 27 days built up when I was laid off!)

I also asked him what I could do to get into management next time, but I’m not really sure that’s really where I want to be. I know a few people who have moved into management, and then found it hard to get a job when they were laid off. Along the same lines, I still believe I’m a far better architect and coder than manager, and so it’s sort of foolish for a company to hire me for my managerial skills when my coding skills are far superior. And I really really like programming. Really. With the spare time I had after I was laid off, I wrote code for an iPhone game. For fun I read up on programming languages I haven’t seen before, and solve Project Euler problems with them. But I don’t know if there’s a way to parlay my architecture and coding prowess into the ability to help guide a company, since that seems to be the exclusive province of people who manage other people. (Of course, my work on Basie last term also has me wondering if I’m actually a mediocre manager, or if I may be better at it than I always thought I was.)

I suppose there’s always one way to find out how good a manager/team lead I am, and I expect I’ll start moving that direction in my next job. I can always write code on evenings and weekends, right? Well, either way, it’s not something I’ll worry about until I get another job. And certainly not something I should be worrying about this late at night.

Some notes on cross-compiling GambitC

The command to use is:

$ env CC=/usr/local/bin/arm-apple-darwin-gcc CC_FOR_BUILD=gcc ./configure --host=mac; make

Well, kind of. First you do that, then you copy gsc/gsc to gsc/gsc.onboard, then you go to a new directory, and type:

$ ./configure;make

and copy the gsc/gsc from that directory into the first directory.

To compile a script into an exe:

$ gsc/gsc -:=. -c euler.scm
$ gsc/gsc -:=. -link euler.c
$ /usr/local/bin/arm-apple-darwin-gcc euler.c euler_.c -Iinclude -Llib -lgambc -o euler

It's freaking huge!

$ ls -alh euler
-rwxr-xr-x   1 bwinton  bwinton  4M Jan 23 14:22 euler
$ ls -alh /WifiToggle
-rwxr-xr-x   1 bwinton  bwinton  17K Jan 16 14:07 /WifiToggle

And it's not a lot faster. 0.1643 seconds for the compiled version, as opposed to 0.1803 seconds for the interpreter.

But on my Mac:

$ more m1.c
power_of_2 (int x) { return 1<<x; }
$ more m2.scm
(c-declare "extern int power_of_2 ();")
(define pow2 (c-lambda (int) int "power_of_2"))
(define (twice x) (cons x x))
$ more m3.scm
(write (map twice (map pow2 '(1 2 3 4)))) (newline)
$ gsc/gsc -:=. -link -flat -o foo.o1.c m2 m3
$ /usr/local/bin/arm-apple-darwin-gcc -Iinclude -bundle -D___DYNAMIC m1.c m2.c m3.c foo.o1.c -o foo.o1
$ ls -alh foo.o1
-rwxr-xr-x   1 bwinton  bwinton    13K Jan 23 14:45 foo.o1

then on the iTouch,

# scp bwinton@latte.ca:/Users/bwinton/Programming/Bazaar/iTouch/gambc-v4_1_2/foo.o1 .
# gsi foo.o1
((2 . 2) (4 . 4) (8 . 8) (16 . 16))
# gsi
Gambit v4.1.2

> (load "foo")
((2 . 2) (4 . 4) (8 . 8) (16 . 16))
"/private/var/root/foo.o1"
> (twice 5)
(5 . 5)
> (pow2 10)
1024

How to get laid off.

A few Fridays ago, I was laid off from my job. After seven years I’m sort of out of practice at looking for a new job. The economy is also kind of slow, and I know a few people who are going to graduate soon into this economy, and who might want to know what I did when I was laid off, and how I’ve found a new job.

So, Friday. As a side note, if you’re ever in the position of letting go of employees, please don’t lay them off or fire them on a Friday. They can’t do anything but sit and brood over the weekend, which can really get them depressed. On the other hand, I’ve been pretty upbeat about it because after seven years, I’m kind of ready for a change. It was a big shock when I first learned about it, but I had made my peace with it after around 30 minutes or so. An hour later, there was a meeting where the rest of the employees found out, and that was kind of tough to sit through, but afterwards it was like there was a big weight lifted from my shoulders, and my co-workers and I started to joke around about it.

That afternoon, and the next couple of days, I emailed my friends and asked whether they knew of any companies that were hiring. From that I got six or seven leads, and three or four interviews, and finally decided to do some work and sign a three month contract, which will probably turn into a longer term contract. I don’t expect it’ll turn into a full time job, but that works out fine for me, since I expect the market will have changed by the new year, and I’m hoping that a friend of mine will get the hiring freeze he’s currently in lifted by then, and hire me.

On the plus side, now that I’m working from home, I bought a new computer because I can deduct the price from my income as a business expense. Similarly I can deduct part of the interest on my mortgage from my income. And I’ll be making a fair bit more than I was getting as a full-time employee, because of the lack of benefits, my inside knowledge of the app, and finally because I have had only one raise in the last seven years. (It was hard for me to ask for a raise, because I knew the company didn’t have a lot of money to give me a raise with, and because I owned some of the company, so I would rather have seen my shares increase in value than get extra cash in the short term. Of course neither ended up happening, but who could have foreseen that?)

So yeah, there’s my tale of woe and hope. It’s really not that bad, apart from the sadness of seeing something that you worked really hard for crash and burn. But even in that sadness is the spark of excitement for something new and different. If you haven’t gotten your first job yet, then I don’t know if this will help you all that much. I guess the main thing to take away from it would be that you should cultivate contacts among your peers, profs, co-workers, and clients because they’re the ones who will find you the good jobs, in the end.