I was thinking recently about the top 3 problems left to solve online – the things I still find myself needing and not having a good solution for.
- I want to spend more time with people offline (and I want an online tool to help me do this). This has been a desire for a long time. Over ten years ago (I believe it was in 2001) I wrote the following on a (nostalgic) about page:
Computers are a weak substitute for human interaction. It is my firm belief that our society is converging towards a fear and avoidance of face to face contact. I am a front runner in this convergence. Being here, realizing the problem, and having the potential to do something is my daily realization, not having done anything yet is my daily disappointment. People will not stop using technology, they will only do it more and more. Online communities must be aimed at promoting real physical interaction, not at replacing it. This is what I should be doing.
That still reads fairly accurately. Facebook is essentially the opposite of what I want. FourSquare, Google Latitude, etc do some interesting things – but there is no killer app in this space. I want to see my friends. People make people happy. I don’t know what form this will ultimately take, but it seems like a huge opportunity.
- What events are going on? Related to problem 1 is the fact that event listings are still so decentralized and hard to maneuver. I like going to interesting things – but often times I miss out because you still have to know the right people to get a facebook invite, or read the right mailing list. This could be vastly improved (and zvents, eventful, etc are interesting, but they aren’t it yet). I worked on a couple projects in this space (ThisBounces and then SirCalendar), but it remains an unsolved problem. See “Start-up idea: Why event search needs to be fixed.” In Boston I read the Phoenix Newspaper (seriously), and subscribed to CheapThrills. New York is somewhat better equipped with TheSkint, LinkedList, a bunch of meetup groups, couchsurfing forums, etc. But the fact remains – I regularly miss awesome things in my own city, and if I arrive to a new place for the weekend, I have no easy way of figuring out what to do.
- What is the best… everything? I live on ratings, love them, trust them. IMDB 7+ for movies, GoodReads 4+ for books, Yelp 4+ for restaurants, Amazon 4+ for products. This is a start – but we all know the pain of comparison shopping on Amazon: This one has 4 stars, but only 5 reviews, and this review reads a bit fishy. And while its getting big, Amazon certainly isn’t a generic rating system. I want to find out the best anything. In any category. With relative certainty. No specialties. Gimme!
Thoughts? Are these really the top 3 problems left to solve online?
Open Sourced: “Send to Calendar” Chrome Extension
0 Comments Published December 26th, 2012 in technology, web developmentI just open sourced my “send to calendar” chrome extension. I got tired of re-typing event info all the time, so the extension allows you to select text on any webpage, and send it to google calendar (with some smart parsing in between). The whole thing is super simple, and pull requests are welcome (particularly for additional parsing, support for international date formats, etc). You can find the whole thing on github, the guts are really just this background.js file:
//create the context menu var cmSendToCalendar = chrome.contextMenus.create({ "title": "Send To Calendar", "contexts": ["all"], "onclick": SendToCalendar }); //do all the things function SendToCalendar(data, tab) { var location = ""; var selection = ""; if (data.selectionText) { //get the selected text and uri encode it selection = data.selectionText; //check if the selected text contains a US formatted address var address = data.selectionText.match(/(\d+\s+[':.,\s\w]*,\s*[A-Za-z]+\s*\d{5}(-\d{4})?)/m); if (address) location = "&location=" + address[0]; } //build the url: selection goes to ctext (google calendar quick add), page title to event title, and include url in description var url = "http://www.google.com/calendar/event?action=TEMPLATE&text=" + tab.title + location + "&details=" + tab.url + " " + selection + "&ctext=" + selection; //url encode (with special attention to spaces & paragraph breaks) //and trim at 1,000 chars to account for 2,000 character limit with buffer for google login/redirect urls url = encodeURI(url.replaceAll(" ", "\n\n")).replaceAll("%20", "+").replaceAll("%2B", "+").substring(0,1000); //the substring might cut the url in the middle of a url encoded value, so we need to strip any trailing % or %X chars to avoid an error 400 if (url.substr(url.length-1) === "%") {url = url.substring(0,url.length-1)} else if(url.substr(url.length-2,1) === "%" ) {url = url.substring(0,url.length-2)} //open the created url in a new tab chrome.tabs.create({ "url": url}, function (tab) {}); } //helper replaceAll function String.prototype.replaceAll = function(strTarget, strSubString){ var strText = this; var intIndexOfMatch = strText.indexOf( strTarget ); while (intIndexOfMatch != -1){ strText = strText.replace( strTarget, strSubString ) intIndexOfMatch = strText.indexOf( strTarget ); } return( strText ); } |
Sometimes you want a nice clickable HTML button in your emails – something that looks great and that your users see even if images are disabled when they view your email. Here’s a bootstrap-inspired button that will render well across email clients:
<a href="" style="text-decoration:none;font-style:normal;font-weight:normal;display:inline-block;padding-top:4px;padding-bottom:4px;padding-right:20px;padding-left:20px;margin-bottom:0;text-align:center;vertical-align:middle;background-color:#4ba1db;*background-color:#4ba1db;background-image:linear-gradient(top, #5CB1E4, #2693D5);background-repeat:repeat-x;border-width:1px;border-style:solid;border-color:#2693D5 #2693D5 hsl(201, 93%, 54.5%);*border:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);text-shadow:#397aac 0 2px 2px;-webkit-font-smoothing:antialiased;color:#FFF;" >I am a button! </a> |
Yes, the code is quite ugly, but it works well – as an extra bonus, if you use campaign monitor to send your emails, here’s it is as a style you can include in your template – then to create a button just take a link and make it bold and italic (a little shortcut for creating a button in the editor)
em strong a, em a strong, strong em a, strong a em, a strong em, a em strong { text-decoration: none; font-style: normal; font-weight: normal; color:#FFFFFF; display: inline-block; padding: 4px 20px; margin-bottom: 0; text-align: center; vertical-align: middle; background-color: #4ba1db; *background-color: #4ba1db; background-image: -ms-linear-gradient(top, #5CB1E4 100%, #2693D5 100%); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5CB1E4), to(#2693D5)); background-image: -webkit-linear-gradient(top, #5CB1E4, #2693D5); background-image: -o-linear-gradient(top, #5CB1E4, #2693D5); background-image: -moz-linear-gradient(top, #5CB1E4, #2693D5); background-image: linear-gradient(top, #5CB1E4, #2693D5); background-repeat: repeat-x; border: 1px solid #2693D5; *border:0; border-color: #2693D5 #2693D5 hsl(201, 93%, 54.5%); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; filter: progid:dximagetransform.microsoft.gradient(startColorstr='#5CB1E4', endColorstr='#2693D5', GradientType=0); filter: progid:dximagetransform.microsoft.gradient(enabled=false); *zoom: 1; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); text-shadow: #397aac 0 2px 2px; -webkit-font-smoothing: antialiased; } |
Note that this button is fully clickable anywhere – you don’t have to click on the text to get it to work. This is easy to mess up, and even LinkedIn does it wrong:

Visible metrics are the heart of many successful businesses. Setting up a dedicated stats or metrics TV (ours is powered by a mac mini) is a pretty straight forward endeavor – and having everyone rally around the key performance indicators is well worth it. We cycle between optimizely, customer satisfaction (NPS) results, key metrics from RJMetrics, real-time google analytics, live chat metrics, sprint burndown, wufoo form entries, and the occasional cute puppy cam.

Here’s the setup:
- OSX configured to login at boot
- Bluetooth Setup Assistant disabled at startup
- Software updates are disabled
- Screensaver is disabled
- Uncheck everything in System Preferences -> Mission Control
- Disable “Reopen Windows When Logging Back In”
- Disable crash reporter dialog in terminal:
defaults write com.apple.CrashReporter DialogType none - The below apple script is used to open chrome in presentation mode at boot (note the 20 second delay between opening chrome and going into presentation mode to allow chrome to load all the tabs:
tell application "Google Chrome" activate tell application "System Events" do shell script "sleep 20" keystroke tab key down {command} key down {shift} keystroke "f" key up {shift} key up {command} end tell end tell |
- Setup a cron to reboot the mac every morning at 7:55 am and 8 am to clear memory (using cron because system reboot is graceful and may not actually shutdown the computer). Rebooting twice seems to help avoid issues where applications cancel a reboot.
- nano
- 55 7 * * * /sbin/shutdown -r now
0 8 * * * /sbin/shutdown -r now - write out to a file
- copy the file to the root crontab: sudo crontab -u root /path/to/file
- verify that the cron exists: sudo crontab -u root -l
- Chrome is configured to reopen the desired tabs:
- TabCarousel extension configured to “start automatically”
- Stylebot extension used to hide/adjust CSS on pages to better fit full-screen (sync enabled)
- LastPass extension used to remember passwords and auto-login for sites that require it
- Better Popup Blocker extension used to block focus stealing javascript alerts
- Tampermonkey extension used to run a greasemonkey scripts (for example, I wrote one that plays a random inspectlet user recording):
// ==UserScript== // @name Inspectlet Auto Play // @namespace http://www.borism.net // @include http*://www.inspectlet.com/dashboard* // @version 1 // ==/UserScript== (function(){ var script = document.createElement("script"); script.type = "application/javascript"; script.innerHTML = '\ /*check every second */ \ setInterval(function(){ \ /* if we are on the dashboard page, go to the captures page */ \ if (window.location == "https://www.inspectlet.com/dashboard"){ \ window.location = "https://www.inspectlet.com/dashboard/site/YOURIDHERE"; } \ \ /* if we are on the captures page select a new video to watch */ \ else if (window.location == "https://www.inspectlet.com/dashboard/site/YOURIDHERE"){ \ /*make all links open in the same window*/ \ $("a").attr("target","_self"); \ \ /*choose a random capture from the list*/ \ $(".trow a")[Math.floor(Math.random() * $(".trow a").length)].click(); \ } \ /* if we have reached the end of the video, redirect back to video listings */ \ else if ((parseInt($("#pagelist").val()) == $("#pagelist option").length) && $("#stopvideo").hasClass("disabledbutton")){ \ window.location = "https://www.inspectlet.com/dashboard/site/YOURIDHERE" \ } \ \ \ }, 1000); \ ' document.body.appendChild(script); })(); |
If you use Rally, you may be familiar with the joys of trying to make it even slightly useful. Here’s my small contribution: this is a script that formats stories for easy copy/paste into update emails, etc. You can select any iteration to get a nicely formatted list like the one below:
The script uses the Rally API and is based on this and this other code sample. The script makes some decisions that can be fairly easily changed in code if needed:
- includes stories only (no defects)
- skips over stories with no owner (we sometimes include stories for formatting purposes only)
- sorts stories by highest story estimate first (with un-estimated stories at the bottom of the list)
- In Rally go to “Reports -> + New Page”
- Name it anything you like (for example “Email”) and click “Save & Close”
- Click “Start adding apps”
- Choose “Custom HTML” and click “Add This App”
- Title it anything you like (for example “Email”), paste the below code into the HTML section, and click “Save”
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>User Stories For Sprint Emails</title> <meta name="Name" content="User Stories For Sprint Emails" /> <meta name="Author" content="Boris Masis, Bigstock" /> <script type="text/javascript" src="/apps/1.26/sdk.js"></script> <script type="text/javascript"> var rallyDataSource = null; var iterDropdown = null; function showUserStories(results) { var story = ""; var storyEstimated = ""; var storyNotEstimated = ""; for (var i=0; i < results.stories.length; i++) { story = results.stories[i]; if (!(story.Owner)) continue; //skip over stories with no owner else { story.Owner = story.Owner._refObjectName.split(" ")[0]; //return the first name of the owner only } if (story.PlanEstimate != null) { storyEstimated += story.Name + ' (' + story.Owner + ')' + '<br/>'; } else { // put stories that aren't estimated at the bottom of the list storyNotEstimated += story.Name + ' (' + story.Owner + ')' + '<br/>'; } } var storiesList = document.getElementById("storiesList"); storiesList.innerHTML = storyEstimated + storyNotEstimated; } function onIterationSelected() { storiesList.innerHTML = ""; var queryConfig = { type : 'hierarchicalrequirement', key : 'stories', fetch: 'PlanEstimate,Owner,Name', query: '(Iteration.Name = "' + iterDropdown.getSelectedName() + '")', order: 'PlanEstimate desc' }; rallyDataSource.findAll(queryConfig, showUserStories); } function onLoad() { rallyDataSource = new rally.sdk.data.RallyDataSource('__WORKSPACE_OID__', '__PROJECT_OID__', '__PROJECT_SCOPING_UP__', '__PROJECT_SCOPING_DOWN__'); var iterConfig = {}; iterDropdown = new rally.sdk.ui.IterationDropdown(iterConfig, rallyDataSource); iterDropdown.display(document.getElementById("iterationDiv"), onIterationSelected); } rally.addOnLoad(onLoad); </script> </head> <style> body{ margin-top:25px !important; } #storiesList{ font:13px arial; color:#666; } #storiesList b{ color:#222; } </style> <body> <div id="iterationDiv"></div><br /> <div id="storiesList"></div> </body> </html> |
How To: Centering HTML exports in Fireworks
1 Comment Published March 23rd, 2012 in technology, web developmentIf you export from Fireworks into clickable HTML prototypes, you’ll find that your exports are always left-aligned – even if you select center alignment during the export. This seems like a bug to me, luckily there is a fairly painless workaround.
Note: Tested in Fireworks CS5 on Mac, but should work approximately the same in other versions or Windows.
Open /Applications/Adobe Fireworks CS5/Configuration/HTML Code/Dreamweaver/SLICES.XTT and locate the following line: (line 662)
WRITE_HTML("<!--Fireworks CS5 Dreamweaver CS5 target. Created ", d, "-->\n"); |
Now add the following right below it:
WRITE_HTML("<style>body {text-align:center;}</style>\n"); |
and BAM! You’ve got centered exports.
If you haven’t heard of Animata, take a look at it now. Its amazing animation software that you can control with your body. Back in 2009 we threw a party where guests could control a projected, animated musician by moving their arms and body. I corresponded with Bence, the Animata developer to get this working on Windows, and had meant to do a full write up. Here we are nearly two years later. I’ll do my best to reconstruct the steps as the effect is still very cool.
- Download Animata
- Download Eyesweb 4.0.0.10
- Download Bence’s script
- Hook up your camera
- Follow Bence’s advice: eyesweb sends the positions of the skeleton in variable sizes, eg. if you get closer to the camera the skeleton will grow, and that’s what animata cannot handle. The program I wrote maintains the size of your skeleton. Now it is working quite well: http://www.binaura.net/bnc/temp/ShadowTheatreEngine.zip. After you have to opened all 3 applications, create 12 Joints in Animata named body0joint0, body0joint1 … body0joint11 (this is very important!) after this, you will see that everything is moving, which is very good, so you are almost there….This program receives the data from eyesweb and forwards the right data to animata. Also you have to adjust your skeleton size, the length of the arms etc. to fit your character in animata, that is what you can do in EDIT mode.
- At this point you should be able to achieve something like my screencast below:

- The problem I describe in the screencast was resolved with the following reply from Bence: Your problem is in eyesweb is the camera resolution, you can change if you click on the camera icon, and you can change it in the parameter window. Try to set it 320×240 if you are using webcam the engine communicates width animata through OSC messages: you should set the OSC object in eyesweb like this: host_name : 127.0.0.1, port: 12000, OSC_command: /body eyesweb to shadow-engine communicates on port 12000, engine to animata on port: 7110…
How To: Recover from Failed Amazon EC2 Instances (and fail they will)
4 Comments Published February 7th, 2011 in aws, technologyOne of the things that’s not immediately obvious about Amazon EC2 instances is that they could fail, in fact Amazon says:
It’s inevitable that EC2 instances will fail, and you need to plan for it. An instance failure isn’t a problem if your application is designed to handle it.
The EC2 forum posts are littered with users whose EC2 instances have become unresponsive and can not be stopped or restarted. Instances can get “stuck” in “stopping” mode for 24 hours or more. Amazon generally recommends issuing a forced stop via the client tools “ec2-stop-instances –force” command, but this actually doesn’t seem to work in most cases.
Luckily, Eric Hammond wrote a post about how to move EC2 instances to new hardware if such a problem were to occur (as it did to me). Eric’s solution relies on the client tools under Linux.
It turns out that its possible to replicate these steps directly in the Amazon panel and quickly recover from a failed instance. I recommend everyone follow these steps to prepare for a failure scenario:
- In the “Instances” panel: create a new instance using the same AMI as your production instance. This is your backup instance. “Stop” the instance after it is created. (Amazon will not charge you for any stopped instances).
- In “Volumes”: detatch and then delete the drive that was created as part of this new instance.
- Still in Volumes: create a spapshot of your production drive.
- Go to the “Snapshots” section of the panel, select your new snapshot and choose “create volume from snapshot.” Be sure to choose the same availability zone as your instance. I’ve seen some caching issues here, so if you don’t see your snapshot when selecting this menu, be sure to refresh.
- Go back to “Volumes” and choose “attach volume” on your new available volume. Choose your stopped backup instance and type in the same device as your original volume (visible under “attachment information” for the volume)
- Go ahead and start your backup instance, it should be an exact copy of your production instance.
- Sleep better at night.
Git pulls are a fairly easy way to deploy your code, and we’re doing just that at my new event promotion startup. We wanted to take our release process just one small step further by tagging our releases so that we know exactly what is running in production and when it got there. Instead of using formal release numbers we decided to just use a simple date system: YY.MM.DD.HH.mm (using leading 0s and the 24 hour clock). For example if I were to do a release right now I would tag it 11.02.04.17.44
If you are developing on windows, the following .bat file will take care of creating such a tag for you and pushing it to your remote repository (i.e. Github):
call git tag %date:~12,2%.%date:~4,2%.%date:~7,2%.%time:~0,2%.%time:~3,2% call git push --tags cmd |
The next step in the deployment is to pull down the tag in QA:
git fetch git checkout YY.MM.DD.HH.mm (referring to your previously named tag) |
This process can be repeated in production, but if it fails QA testing we delete the tag with the following commands and start over.
git tag -d YY.MM.DD.HH.mm (to delete your local tag) git push origin :YY.MM.DD.HH.mm (to delete the remote tag) on QA: git tag -d YY.MM.DD.HH.mm (to delete the QA local tag) |
If you’re not immediately creating a fixed release, update QA to the prior tag using
git checkout YY.MM.DD.HH.mm |
As an alternative workflow, you could create your tags in QA after QA testing has passed, in which case you could create this bash file
tag=`date +"%y.%m.%d.%H.%M"` git tag $tag git checkout $tag git push --tags |
I have returned after 11 1/2 months abroad. My travels (mostly sans words) are documented at http://go.borism.net. The journey took me to 16 countries and over 40,000 miles (see travel map).
