Follow Zube on Twitter

Tickets that seamlessly work with GitHub Issues

If you’re reading this, you’re probably as excited as we are about a ticketing system that’s fully integrated with GitHub issues in real-time. The other cool thing about the new ticketing system is that it is dynamically linked to your Zube scrum/kanban board. So no matter where you make changes, everything remains up-to-date without you or your development team having to do anything special.

We created Zube to solve specific challenges we faced as developers at growing startups. One of the main challenges we faced was the “black hole” that was the development team which, to be fair, we were a part of. While we tried our best, it takes effort to keep everyone in the loop and we always prioritized cranking out code over team communication (whether that was the right thing to do is a different story).

We created Zube to eliminate the effort needed to keep everyone on the same page. We made a scrum/kanban board that’s real-time synced with GitHub issues so that the entire development team can be on the same page, even if some team members just continue closing issues from the command line. And today, we’re super excited to unveil a ticketing system that brings that same level of visibility to the entire team, whether you’re a project manager, working on the support team, or a backend engineer.

Zube tickets, like tickets in a normal ticketing system, can be given priorities, assigned to team members, commented on, and be marked as closed when they’re complete. The cool thing about Zube tickets is their ability to be linked with GitHub issues. The cards on the lower right of the image below are GitHub issues.

A Zube ticket with three linked GitHub issues

If you change something on any of those cards, the corresponding GitHub issue will be updated in real-time, and vice versa. This is super powerful because once you’ve linked some GitHub issues to your ticket, your ticket will automatically change its status based on the linked GitHub issues. For example, once all of the linked GitHub issues are closed, your ticket will automatically change its status to “Completed”. The same goes for all stages, actually. The ticket status will automatically change from “New” to “Queued” once some of the linked cards have been triaged and from “Queued” to “In Progress” once a developer starts working on one of the linked issues. All without requiring the developer to do anything out of the ordinary.

It’s also easy get an overview of the project’s tickets (or just the tickets assigned to you) from the ticket index view.

An overview of your active tickets

We need upvotes

We think it is ridiculous that you can’t upvote GitHub issues. There are many, many open source projects that use GitHub issues for bug tracking and feature requests and there has to be a way to let the core team know that a certain bug is screwing up your whole world. So why no upvotes?

There is a hack, of course, and that’s to make a comment that says +1 or . But that sucks because +1 comments add a bunch of noise to the discussion. The best solution would be for GitHub engineers to add an upvote button the the native UI, but since that hasn’t happened yet, we’ve done our best to make things more manageable. In essence, what we’ve made is a bot that counts all comment upvotes in the background. The way it works is that it monitors your GitHub issue comments in real time for any comments that start with +1 or and keeps a unique total in the issue body like this:

Upvote count in issue body

Since upvote counting runs in the background, you don’t have to use the Zube UI for it to work. Once upvotes are enabled you’re good to go.

Having an upvote total displayed in the card body makes it easier to see at a glance which issues have the most interest. If you’re a maintainer of an open source project, you’re now free to remove the +1 comments from the comment thread. Even when +1 comments are removed from the thread, an accurate unique total is still maintained. A list of the users who upvoted any issue is available on Zube, and if there’s sufficient interest in making the list appear on the GitHub issues itself (like at the end of the card body), we’d be happy to make that happen.

Upvotes on Zube

And for those people already using Zube to manage their GitHub issues, there’s a board setting you can enable to add upvotes to every Zube card, so upvote away!

We hope you find upvote counting useful as stopgap until GitHub realizes that there’s a true need for upvotes.

When we launched Zube a month ago, there was a lot of interest around how we made the animated demo video on the homepage To be honest, it was a real pain in the ass, so we thought we’d share exactly how we did it.


  • Capture a movie using Quicktime or equivalent
  • ffmpeg to create a color palette from the movie
  • ffmpeg to create a gif from the movie using the color palette
  • convert to create a sequence of 8 bit png frames from the gif
  • display the pngs sequentially in the <canvas> with a Javascript loop


There were a few requirements that we wanted to hit. First off, the video had to autoplay on page load. This was a problem on mobile browsers since they don’t autoplay videos due to bandwidth concerns. There are some hacks you can do to get around this but none of them work very well. So using an mp4 was out.

Our second requirement was that the video should not overload the browser. This meant a gif was out because, for whatever reason, large gifs crash my cofounder’s phone.

Finally, we wanted the text to be readable while keeping the total file size relatively small. This meant a series of .jpgs was out because jpg text is all blurry.

In order to hit all these requirements we decided to use Javascript to loop over a sequence of pngs. We chose this approach because the text in a png is readable and since pngs support transparency, we could overlay them to produce the animation. Since most of the frames are largely transparent, each frame is rather small in size and the total size is about the same as a gif.

One last note. Gif’s have 8-bit color to reduce file size. This gave us the idea to use 8-bit color for our pngs as well. The only problem is that 256 colors is rather limited so the colors will come out all wrong. To get around the problem, you need to generate a custom color palette, which is explained in this great article by Clément Bœsch.

The Full Procedure

Capture your demo movie using video capture software.

We used Quicktime, File -> New Screen Recording, but you do you. Quicktime generates a movie which we called If you program generates something else like an .mp4, that’s ok too.

Create a color palette from your movie using ffmpeg
ffmpeg -i -vf fps=10,scale=1378:-1:flags=lanczos,palettegen palette.png

Put in the appropriate filename and format for the movie you generated in step 1 instead of if you called your movie something else. The scale parameter should be set to the width of your video. In our case, was recorded at a width of 1378px, so we used scale=1378. The resulting color palette looks pretty cool:

8-bit color palette for our movie

Next we’re going to create a gif from our movie using the color palette we generated in step 2. As a hack, we first convert the movie to a gif, and then convert the gif to a series of pngs. Our objective is to create a series of pngs, and there may be some way to use ffmpeg to directly create a series of transparent 8-bit pngs layers, but we don’t know how to do that. Just think of this step as a free gif. :)

Create a gif from the movie
ffmpeg -i -i palette.png -filter_complex "fps=10,scale=1378:-1:flags=lanczos[x];[x][1:v]paletteuse" demo.gif is the movie from step 1, palette.png is the color palette we generated in step 2, we specifed the frame rate to be 10fps, the scale parameter of 1378 is the width of our video, and we named the resulting gif demo.gif.

Create the series of transparent 8-bit pngs using ImageMagick
convert -dispose Background -coalesce demo.gif -colors 256 PNG8:demo_%d.png

The resulting pngs are just the pixels that need to be added to screen in order for the animation to move forward. The first png is the initial background:

First frame of the png set

Each subsequent image is mostly transparent with some random looking opaque pixels. Here’s the next image in the animation:

Even though each frame looks weird, when we layer them on top of each other, the resulting animation will look perfectly normal.

As a note, our first instinct was to make a single large image that contained every frame a.k.a a sprite sheet. Sprite sheets in general are awesome because they reduce the number of image requests to the server, which have http overhead and are subject to browser parallel request limitations. However, when we made a sprite sheet and then tried to render it in the canvas using ctx.drawImage(), the whole browser came to a grinding halt. More specifically, Chrome came to a grinding halt. Safari, was super fast. Apparently Chrome has a bug. From our brief research we are unclear if the problem lies in trying to use drawImage() to draw subsections of a larger image, or if the problem is in how Chrome handles canvas memory. The sprite sheet we ran the test on was rather large 1,378 px × 157,762 px in size and 3.6 MB. Your mileage may vary.


Somewhere on your page put

<div id="demo">
<img id="imac" src="images/imac.png">
<canvas id="demo-animation"></canvas>

The sibling to our canvas element is an image of an iMac. We want the demo animation to look like it is playing inside the iMac so we place both of those elements inside a parent div so there is a common point of reference.


To make sure the canvas scales along with with parent we added the css

#demo {
position: relative;
#imac {
width: 100%;
#demo-animation {
position: absolute;
width: 91.56%;
top: 4.8%;
left: 4.19%;

All the magic numbers in the CSS are there to position our animation in the center of the iMac’s screen.

The Javascript

We used JS to dynamically load and display our sequence of pngs by drawing on the canvas. In our case, when we used ImageMagick to generate our images, we ended up with 202 of them ranging from demo_0.png to demo_201.png. The first thing that happens in the JS is to fetch and load the 0th image, demo_0.png. This image will act as a placeholder while the other images are loading.

Then we loop over all the possible image numbers and fetch all the images. We keep count of how many have loaded and when they have all been fetched we kick off the animation by calling renderFrame(). The renderFrame function draws an image on the screen, waits 100ms, and then calls itself. That way the frame rate is approximately 10 frames/second which is what we specified when we created the gif from the original movie.

var canvas = document.getElementById('demo-animation');
var ctx = canvas.getContext('2d');
var images = {};
var total_frames = 202;
var loaded_images = 0;
var current_frame = 0;

canvas.width = 1378;
canvas.height = 781;

// The animation loop
function renderFrame() {
ctx.drawImage(images[current_frame], 0, 0);
current_frame += 1;
if (current_frame === total_frames) current_frame = 0;
window.setTimeout(renderFrame, 100);

// Load the first image as a placeholder
images[0] = new Image();
images[0].onload = function () {
loaded_images += 1;
ctx.drawImage(images[0], 0, 0);
images[0].src = 'images/static/demo/demo_0.png';

// When all the remaining images are loaded, kick off the animation loop
for (var i = 1; i < total_frames; i++) {
images[i] = new Image();
images[i].onload = function () {
loaded_images += 1;
if (loaded_images === total_frames) renderFrame();
images[i].src = 'images/static/demo/demo_' + i + '.png';

Two notes on bandwidth and performance.

In production you should serve your images with a cache header so the browser only has to download your set of images one time. This means that if you ever change your set of images, you need to choose a new filename for every image or the old images may still show up. The common technique for choosing unique images is to md5 hash the image and append that string to the file name. If you decide to go the md5 hash route you’ll probably need to create a dictionary of the filenames so you can programmatically iterate over them.

I should also point out that, since the video you make autoplays on mobile, you need to be considerate of bandwidth limitations. The aggregate size of the png sequence is significantly larger than an mp4. On our site we load a smaller version of the video on browsers that are less than 768px wide. The smaller version of the video is 3x smaller than the full size video. We also kept our video short to reduce file size. As a rule of thumb, if you find the aggregate size of your video is nearing 10 MB, then you should seriously consider an mp4 instead.

Drag and Drop Triage

The latest release of Zube makes it easy to prioritize and triage your GitHub issues. The two most notable features are the addition of a global Backlog and an Inbox. The Backlog is board column that appears on every milestone board view. This means that you can milestone an issue by simply dragging a card from the Backlog to a milestone category (To Do, Tasks, In Progress, etc.). The ability to drag cards from the Backlog into a milestone board view is super useful when you want to populate a new milestone with a bunch of issues, and is especially useful for repositories with hundreds of backlogged issues.

Backlog and Inbox Triage Board View

Along with the global Backlog is another new column, the Inbox. The Inbox holds all of your issues that are in need of triage. You can add an issue directly to the Inbox from within Zube, or if an issue is created on GitHub, it will automatically appear at the top of the Inbox. The Inbox is an optional column that appears by default on all newly created boards and can be enabled/disabled from the board’s settings page.

Points and Color Codes

The latest release of Zube also makes it easier to estimate the complexity of an issue with Points (aka story points). Zube points are the same values as those commonly found in planning poker decks, 0, ½, 1, 2, 3, 5, 8, 13, 20, 40, and 100. Points are disabled by default but can be enabled on the board’s settings page. Also, Zube color codes (code: red, orange, yellow, green, blue), which are commonly used to indicate the priority of an issue, can now be enabled/disabled on board’s settings page.

Card with Points and Color Codes

We’d love to hear your feedback on these new features or any suggestions for future features that you’d like to see

Why we created Zube

You know what’s one of the worst feelings to have when you’re working on a team?

“What the hell is everyone doing?”.

My cofounder, Jen Dewalt, and I felt this way a lot at our previous companies. Jen was hacking with the guys at, and I was doing web development and data science at 42Floors. We felt lost even though our teams were made up of great people who spent a good deal of time trying to keep everyone in the loop. We had weekly engineering meetings and we used GitHub issues to keep track of bugs and features. But come mid-week, we really couldn’t tell you what anyone else was working on.

We had no project management. To be fair, we didn’t really want project management. It takes work to organize and prioritize all of your issues and we tended to favor cranking out code. We tried to use various tools to help get us organized. We’d be able to keep things up to date for a while, but then we’d just stop. The cost of constantly updating the project management tool as well as maintaining GitHub issues was just too high. We weren’t willing to give up GitHub issues so we would ditch the project management tool instead.

Not having any project management tool is bad. Things aren’t so bad when you’re just a couple of developers working in a room, but once your team is a size of 5 or more, communication becomes the primary factor that determines the rate of progress… and team happiness for that matter.

That’s why we created Zube. We were working with teams of around 10 developers and desperately needed to organize our Github issues. We didn’t want to waste a bunch of time bringing up some complicated process when all we needed was some structure. Zube was born from these notions – create a virtual board with just enough structure to keep everyone on the same page and make the process so seamless that the burden of project management disappears.