366: Developing Tito with Paul Campbell
Paul Campbell talks with us about the technology behind selling tickets on his app Tito.io and the journey they've taken in trying to rewrite the app.
Time Jump Links
- 00:49 Guest intro
- 02:40 Why build Tito?
- 03:34 Tech behind selling tickets
- 13:05 Sponsor: Varianto:25
- 14:36 What was year one for you?
- 25:58 Rewriting in 2016
- 29:07 The great rewrite is underway
- 44:24 Sponsor: .TECH Domains
- 46:07 How much has been rewritten in Vue?
- 57:42 What does your app look like in 5 years?
Transcript
[Banjo music]
MANTRA: Just Build Websites!
Dave Rupert: Hey there, Shop-o-maniacs. You're listening to another episode of the ShopTalk Show, a podcast all about front-end Web design and development. I'm Dave Rupert and with me is Chris Coyier.
Chris Coyier: Hey! We have a special guest here to talk about software development in a way that I think is kind of near and dear to both Dave and I's heart about long-term projects, working on stuff, refactoring things, and getting it all out. We have Paul Campbell here. Hey, Paul.
Paul Campbell: Hello.
Chris: Hey.
Paul: Thank you for having me. It's great to be here.
Chris: Yeah. Yeah, thanks for coming on. Paul, you are the developer for and really cofounder of Tito.
Paul: Yes. Yep, absolutely.
Chris: I got it right, right? Like Tito Jackson?
Paul: Yeah, or Tito Vodka.
Dave: Tito Vodka?
Paul: There you go. Jinx.
[Laughter]
Chris: Oh, yeah, yeah, yeah. I don't know why I think of--
Dave: Tito is event registration software, right? And everyone loves it.
Paul: That's what we try to go for. Originally, I was going to conferences and people used software that wasn't fast or stress-free, and so I felt there ought to be something better, and so I built Tito for my own conferences and other people use it for theirs.
Chris: That's a nice story. What were you running at the time?
Paul: The event I started with a buddy of mine was called Funconf. I'm both very proud and very ashamed of Funconf, as I am of many things that I do. Funconf brought people from all over the Web development spectrum to Ireland for a tour around Ireland to talk about the Web and some very interesting people came. We had good times and firm friendships were made for years. We had a lot of fun.
Dave: One was in a castle. Is that right?
Paul: Yeah. It went from the first year, which was in a moving bus; the talks were in moving buses. The second year was in a castle. The castle was the castle that Robert Boyle, the father of modern chemistry, was born in. It was rather remarkable. Then the third one was in an island off the coast of the west coast of Ireland. Yeah, it was a trilogy and it was good that it was just a trilogy.
Chris: [Laughter] You had to build software. Well, you wanted to build some software to make it better, to scratch an itch, or whatever the people say about making stuff.
Paul: One of the reasons I started Funconf was that Andy McMillan, who now runs XOXO Fest in Portland, he started Build in Belfast. He was thinking along the same terms that buying tickets for tech conferences was kind of a crappy experience, and so he built his own system as well.
I built mine and then, eventually, it became a SaaS that people could use. Andy's was bespoke, but Andy's was amazing because you just went onto Build. You said, "Buy a ticket," you paid, and then you had a Build ticket. I was like, "Every conference in the world should be like this. It should be this stress-free."
I wanted that for Funconf because, the previous year, we'd use Eventbrite and it just didn't really work as well.
Chris: There's a lot to talk about development-wise. Just before we get there, it might be nice to understand all the things that it does so that our developer brains can start churning as to what, behind the scenes, this thing needs to do. What if I wanted to throw a conference next week and I really had to do it really fast? I might be tempted to be like, "Uh, well, I have to sell tickets because I want pre-sales. I need money coming in." Maybe I'll just make a little JAMstack-y whatever, put it on Netlify, and put a PayPal, like a Buy Now button for my tickets on PayPal. Done - kind of. You can pay with a credit card. You can pay with anything. I'm now selling tickets. I think that's one of the easiest ways. Love it or hate it, PayPal has enabled people to take money online pretty quickly.
I might be tempted to that, but I'd probably find pretty quickly, like, "Oh, crap. Well, all I have is a PayPal dashboard then that's proof of who signed up, maybe. Maybe it generates emails. Then I guess I'll have to use a Google spreadsheet or something to put who paid and didn't, or something, in that spreadsheet." It's really manual at that point, right?
Maybe I'd be like, well, maybe I'll upgrade and I'll use a Wufoo form or something. If I at least use a Wufoo form, then I can say, "Well, I'm hearing impaired." I can collect more information about the person. It's not just a button, which PayPal doesn't allow me to customize any of that crap. Now I have a form, but I can attach that form to a payment system, so that kind of works. Maybe I could use a Wufoo report or something to show me who has registered or something. Even that feels like this wasn't designed for this. It was designed to be super generic for any purpose. It's not really for events.
I don't know what the progression pass that is but, eventually, you end up with something like Tito. Why don't I just use software that's straight up for this?
Paul: Right. Yeah, a few things come to mind is invoicing and invoicing for different localities. That becomes quite hard. Invoicing and refunding, you can obviously do it through PayPal or do it through Stripe, but doing it in a way that works together, particularly if you want to offer some people to pay with their credit card or some people to pay directly with PayPal. What if you've only got 50 tickets and you've got 1,000 who want to go?
Chris: Oh, yeah. How do you manage inventory? Yeah, sure.
Paul: Yeah. It gets a bit tricky quite soon. Then there are all these little niggly kind of things. I think asking questions, but asking questions is part of the reason that registering for many events is really frustrating because, if it is a form and you have to fill in 50 or 100 pieces of info before you can even pay, your heart is kind of racing whether you can get a ticket or not, so there are a lot of considerations. That was one of the things that, with Tito, you click "Buy a Ticket" and you pay for your ticket. Then if there is any more information to be collected, we say, "We'll collect it after you've confirmed your ticket purchase."
Chris: Oh, so that was a psychological choice you made. Let's just get the ticket bought because sometimes people are racing to buy a ticket. It's not the case for all conferences, but some of them are. Certainly, something like XOXO, I know they have their whole weird lottery system or whatever, but some conferences are so exciting that you want to get in right away. You made the questions happen after you've already guaranteed the ticket.
Paul: Yeah. There was the Build conf experience and then the other experience was, I think it was a Ruby conference in the U.K. where I knew I wanted to go with somebody, but I didn't know who that was going to be. If I was asked to provide attendee number two information upfront, I would have put in, "Do not know. Do not know. Do not know."
Chris: Mm-hmm.
Paul: That kind of would have been a bit junky for the organizer and not convenient for me. With Tito, we introduced, "I don't know yet," [laughter] which still exists and I think is a really subtle little--
Chris: That's great.
Paul: Yeah, and so you can come back later.
Chris: This reminds me of, like, I finally read that book "Jobs to Be Done," or whatever. That philosophy that's like, try to understand, at a lower level, what people are trying to do when they register. They're not just trying to cough up a credit card to you. They are, but there are other things they're thinking about: who they are going with, what their anxiety level is before buying stuff like that.
Paul: I thought that was really important. I know I want to buy a ticket, but I don't know who it's going to be. At some point in the future, I'm going to add a colleague, a friend, or maybe offer the ticket on Twitter or something.
Chris: Well, you're the co-founder, so you know. Presumably, you track stuff like that. Do people use it? Is it a highly used feature?
Paul: The "I don't know yet"?
Chris: Yeah.
Paul: Only really anecdotally. That's another thing. We're not big on data analysis and we kind of leave the data be because we don't really consider the data ours to mine in that way. I don't know if you want to go there in this conversation.
Chris: I'm always fascinated in stuff like that. I have a variety of experiences. Early software things were like, "We're just building something. We don't have time to screw around with what percentage of the population of the users of this app clicked this button or not. We just know that button needs to be there because I want it to be there and it's fundamental to the vision of my software," all the way up until working with some software that's been online for 20 years that makes millions and millions of dollars a month and everything is tracked down to the line. I don't blame either scenario. I think both of them have valid scenarios.
Now I work for a company in the middle of that where it's trying to be a little bit smarter about stuff because if 0.03 percentage of people ever use a particular feature and it tends to cause bugs, that feature needs to go.
Paul: Yeah.
Chris: You know what I mean? I don't know, but I doubt a little checkbox that says, "I'll tell you later," is a significant cause of technical debt for you, but if it was--
Paul: No, I don't think we've adjusted that in, like, six years.
[Laughter]
Paul: I have heard from people who were like, "This is amazing. This is exactly what I wanted." It's usually a manager of a software team and he's got ten folks that he wants to send to a conference, none of which are him. He's like, "Yeah, I don't care. I just want to ship the invitations off to people. I approve the purchase. I just want to send the tickets off and they can fill in their details."
Chris: Okay. It allows you to make registration for the event and it takes care of payments for that and stuff like limiting ticket sales, I don't know who I'm coming with yet, and stuff like that. It also provides -- what else does it do? For me or you who is running the conference, I get nice dashboards and stuff, I imagine. Can't I use it on the day of the event, too, to help run it?
Paul: Yeah, sort of. We could do a lot better there, but we provide the ability to create lists for checking people in. One of the cool things is, you can create multiple lists. If you've got a pre-party and you've got the main event, you can create separate lists and check people off to those independently to know who showed up for either of those.
We have a messaging tool, which is like a lightweight mailing list tool that does things like shows your tickets automatically attached to any emails that are sent, which is pretty cool, so that you don't need to go looking for your tickets after you receive a message.
Dave: Oh, my gosh. [Laughter] How many times are you like, "I saved this somewhere in my phone. I flagged it. Lord knows if I'll find it again."
Paul: Yeah, so I think that's really cool. The philosophy is to build supporting tools that are simple enough that most people will find them useful. Our check-in list tool is pretty limited by what it does. It doesn't do onsite printing. It doesn't do this, that, and the other fancy things that may be more robust check-in things would do. But it has a cool feature where, if you've got a volunteer at your event, you give them the app. They scan a QR code. They get the attendee list. Then you can say, "Wipe the data after the conference."
The volunteer comes in. They don't have to have a Tito account. They don't have to have any registration. They just get a list. They just top people off on their phone and then, the next day, the data goes away. It's really simple and it works for most of the kind of customers that we have but it doesn't do an awful lot more than that. That's kind of the philosophy around all of the supporting features is to keep them simple enough that they're useful for a lot of people, but not crazy.
Chris: Cool. It's a pretty straightforward pricing model, too. It's kind of like free to set up and get going with. Then the minute you start actually selling tickets is when the cost happens. Is that true?
Paul: I believe it's called metered billing. I learned that recently. Ironically, it's sort of similar to Amazon Web Services. You pay for what you use. We just take a percentage of the ticket price.
Chris: Yeah, that's cool, so there's no gateway to getting set up and stuff like that. I wonder. That's funny. It makes me think of spam. Do spammers use this thing to set up--? Do you have to watch for that?
Paul: Occasionally, there are spammers, yes. We've had to put in a few spam protections but, thankfully, not a whole lot. That's not an invitation to anybody to try.
Chris: [Laughter] Certainly.
[Banjo music]
Chris: This episode of ShopTalk Show was brought to you in part by Varianto:25. V-A-R-I-A-N-T-O:25 is their name. No colon in the URL, of course. Just viaranto25.com.
It's so cool. They've had two really successful kickstarts. It's like gifts for developers. They have all kinds of cool stuff. They've got playing cards. You can learn Git on each playing card. They're functional playing cards, but each card has something that you should know about Git on it, which is awesome. Mugs and T-shirts, posters, mousepads, tote bags, stickers, notebooks, they're all just stuff like swag, but they're nerdy stuff. It's super fun, like a functional programming poster or a notebook that's got all kinds of languages on the cover. It's just nerdy looking and kind of fun.
There's a mug that says, "Make Let Not Var," on it, which I think I might buy, actually. I'll put that in my cart right now. Oh, God, and there's like 100 mugs on here.
It's just cool. Just check it out. Maybe it'd be a good gift for a developer friend of yours, yourself, your employees, or your secret Santa party at work or whatever. If you're looking for developer focused gifts, this is the perfect place to find them and it's great. They have discounts on bulk orders, too, if it really turns out to be gifts for your whole company or something like that. Free international shipping, too. Check out varianto25.com. Very cool.
[Banjo music]
Chris: Okay, so what was year one for you?
Paul: Wow. It has to go back a long way. The first Funconf was 2010.
Chris: Mm-hmm.
Paul: I started writing code in March 2011. It was basically….
Chris: …after Funconf. It was after the first one….
Paul: It was after the first one. Yeah, it was in between the first two. The first piece of code was a PayPal redirect that exposed a JSON API endpoint with the purchase details to a redirect page. You started off on funconf.com. You clicked "Purchase." You get taken to PayPal. Then you came back to funconf.com with a little unique identifier.
Chris: Mm-hmm.
Paul: That identifier went off to the Tito API because there was no UI. [Laughter] Pull the details down and said, "Thanks, Chris. Thanks, Dave. Here is your ticket reference," and you're going to Funconf.
Chris: Yeah.
Paul: That was it. It didn't do anything more than that, and it stayed that way for quite some time.
Chris: But now you know, in a database somewhere, who bought a ticket and whatever.
Paul: It was literally just in the database, so it was on Heroku and, in order to find out who had bought tickets, I had to go into the console and do a list of tickets. [Laughter]
Chris: Yeah. Yeah.
Paul: Yeah.
Chris: Well, it's better than an email sitting somewhere.
Paul: Yeah. [Laughter]
Chris: Cool. I mean it's medium-aged software. I think we share about the same length of history, Tito and CodePen, really. That was about when we started building CodePen. We're kindred in that way in that technology has moved under our feet a little bit and we need to be rearchitecting things in rebuilding things because people use our software now, believe it or not.
At what point was it Rails? Pretty soon?
Paul: Yeah, from the start. That first piece may have been Sinatra. I feel like I used Sinatra at some point.
Chris: Oh, we did that too!
Paul: [Laughter]
Chris: In fact, our repo is named -- our main repo that's never changed, or our second one. Our first one was Sinatra and the second one is called CPOR, which is CodePen on Rails.
Paul: Oh, very good.
Chris: We're so proud of moving it to Rails.
Paul: Nice.
Chris: That's still the name of it.
Paul: Yeah, but I think it was on Sinatra for maybe five minutes and then switched to Rails.
[Laughter]
Dave: Sinatra has that -- I mean it's very express-like, but it definitely is like an all-day world's optimism that I'm going to be able to maintain all this kind of spaghetti.
Paul: I feel like I should correct you that Express is Sinatra-like. [Laughter]
Dave: Oh, no. I think Express is exactly Sinatra in Node.
Paul: I'm pretty sure that's exactly what it was, wasn't it, originally?
Dave: Yeah.
Paul: Yeah.
Dave: Yeah.
Paul: This is where I sometimes go off and say, "Oh, yeah. The author of Sinatra, he was at Funconf."
[Laughter]
Dave: That's probably why you had to use it.
Paul: Not the DHH, not the author of Rails. But, yeah, the founder of NPM was at Funconf.
Chris: Oh, nice.
Paul: A lot of stuff went down at Funconf. There you go. Those were the kinds of people who went. That's all I'm bringing those names up to is people who implemented things and made things and made conferences went to those events.
Chris: It's still Rails, right? Yeah? Probably?
Paul: Yeah. Yeah, and so it's probably a good point to introduce the two problems that Tito has to solve. We're still working on solving that.
Chris: Let's do it, two problems.
Paul: Yeah, I mean at a very high level. One is looking after the organizer and the UI or the dashboard that they view. The second is the checkout experience or the checkout flow.
If you use Stripe, this is whatever the Stripe dashboard and the Stripe checkout JavaScript widget. We need to provide those. Even now, the Tito checkout widget that you can embed in your page or that we use when you purchase on ti.to is a Rails app running inside an iFrame.
Chris: Sure.
Paul: Obviously, there's a progressively enhanced JavaScript flow there, but it's basically a Rails app running inside an iFrame. The piece of JavaScript that we send over the wire to a third party page is literally just, pop up and iFrame and load a Rails app inside there.
Chris: Yeah. Sure. Sure.
Paul: Then the admin dashboard is your stock Rails app. It's Rails, top to bottom, with a sprinkling of JavaScript on top and probably too much custom stuff.
Chris: Do a heck of a lot of conferences have their own website, if not all of them? They like it when you check out on their webpage, not your webpage, right? That's kind of a fundamental feature of Tito is that you take the registration form and plunk it on your own website.
Paul: Yeah, and it's something that is tricky enough to do, but not enough people do it, and so what we're thinking about now is to try and make it even easier than we did before, to be able to do that, to be able to make it seem as if you're not using a third-party provider for your ticket experience because that's what the Funconf thing at the start was. At no point did you ever see Tito in the experience. You were on Funconf, you went to PayPal, and you were back at Funconf. That's what the embedded piece is supposed to enable.
Maybe this is a good thing, but when a Tito widget pops up, you know it's Tito if you've seen it before. It's sort of like some people prefer the hosted and some people prefer the widget. We think that the ideal is if you're on somebody's conference website and you purchase, it should all happen in there.
Chris: That's kind of interesting. I worked at Wufoo for a long time, which was basically an iFrame company. It was like, make a form; put it on somebody else's website. It's almost always an iFrame, but it's a piece of JavaScript that kind of enhances itself into an iFrame. The idea being that you can have a paragraph tag or something that says, "Please fill out my form," with the link to the real form. If it's in a blog post or something it'll come across RSS, and the JavaScript won't run, but there'll still be a link to run out the form, et cetera.
Paul: Yeah.
Chris: Then with the JavaScript on the page, especially in 2012 or whatever the heck, it was necessary to adjust the iFrame's height and characteristics and stuff such that it fit the form nicely. That's still kind of a challenge.
Paul: Oh, of course, because it wasn't a full screen.
Chris: Yeah, right. It's just a little chunk on there. That was kind of difficult. That's kind of getting a little bit easier to do, but not too much. iFrames are still a pain in the butt. Now, at CodePen, we have iFrames all over the place.
Paul: Right.
Chris: That's what embeds are, so that's. You know. It just is what it is with third-party sites sometimes.
I'm tempted to someday do the Web components thing. It's so cool to see embedded tweets embrace that where it's like--
Paul: Sure.
Chris: Embeds-Twitter component, so it's not really an iFrame, which is nice because you might be able to inherit some font styles and stuff, maybe, a little easier than you could in an iFrame.
Paul: That's a perfect segue to the "in progress" new widget or checkout style that we've been working on.
Chris: Is it? I was thinking the other way you could do it is just give somebody the HTML and have a hidden endpoint that's a Tito endpoint. Rock it that way.
Paul: Yeah.
Chris: It's a little dangerous, maybe.
Paul: Like in PayPal in 2005.
Chris: Yeah. Wufoo offered that, too. Now, there's like use Basin. There's about 12 different startups that have started lately. Even Netlify has this, like, just put a form on your website and, if you make the action attribute of that form us, we'll accept the data and process it for you.
Paul: Yeah. It's beautifully simple.
Chris: But a credit card… Maybe not.
Paul: Yeah, well, I mean our widget has a little failsafe where, if it doesn't detect SSL, it will redirect. Then we try to get people to put SSL on their websites.
When I was implementing the widget, the third party widget initially, I decided I wanted to be Web component forward compatible in 2013. I'm pretty proud of that.
Chris: In 2013? Wow. Yeah, you should be proud of that.
Paul: [Laughter] Yeah, so obviously you have to put a script tag, and so you put the js.tito script tag. Then you just have a little Tito widget, an HTML component. You put that where you want the widget to show up.
It just worked. It always worked because, obviously, even then you could fake customer elements.
Chris: Right.
Paul: That's what has been live on the Internet for the last six years, which I'm pretty proud of. The new implementation, my dream is that we won't have to use an iFrame at all, in most cases, and so we can run the whole checkout experience natively on people's sites. As you say with credit card forms, that can be tricky, but tools like Stripe elements somewhat help mitigate the risk there because then just the piece that accepts the credit card details, that's the only piece that's iFramed and Stripe does all that.
Chris: Oh!
Paul: What's nice about being sort of like a marketplace is that we can often outsource the really hard security pieces to companies who are better fitted to do that kind of thing, like Stripe.
Chris: I mean as a user, I would be interested in using a third party just for that reason. More and more, I want to outsource things to companies who are incentivized to make it better for me so that I'll stick around as a customer.
Paul: Right.
Chris: For example, Apple has got some new login thing. That is not relevant here, but Apple Pay, all of a sudden, existed in the world. It would be so nice if whatever provider I was using just started accepting it somehow, magically, without me having to do a hell of a lot. You know?
Paul: That's one of the challenges of Tito is keeping up with things like that as well as building out new features or doing this great rewrite that we're currently doing with such a small team size. Yeah, so we did do Apple Pay because we had--whatever it was--the somewhere between Apple announcing it and it becoming available. We had a bit of time to implement but, since then, Stripe have implemented tons of European payment methods and just like other ways to pay by direct debit and things like that.
Chris: Yeah.
Paul: We just haven't had the bandwidth to add that to our staff.
Chris: That's funny that that responsibility kind of falls down the way, too, in that you also use services that help you implement. That was the promise of Braintree, right? Oh, it's PayPal, but it also has this single API. You can accept bitcoin through it, or whatever crap. It doesn't matter. You don't have do any work. It just works. I don't know if that's really true.
Paul: Yeah, absolutely. No, and that is absolutely one of the benefits of using a service like Tito or any of these services that we use. You just get functionality that you don't have to implement yourself.
Chris: Yeah, pretty rad. Okay, so it's a Rails app. Time is ticking on. You're deciding to rewrite this thing because technology moves and you just have to move with it to stay smart and relevant.
Paul: If we go back to 2016, I just had my first kid and I'm back from paternity leave. I got and I want to add a feature to the app or maybe want to kind of spike our new iFrame or whatever. There's just so much code. I'm like, "The app should be really simple. There's just so much code and everything takes so long to do, even in development mode." I just felt like there's got to be a better way to do this and that was the painful decision that we made to start our reimplementation process. [Laughter] It's still going.
Chris: The moment was, "This feature is too hard to implement. There are technical debt problems," or something.
Paul: Yeah. I don't even know what I was doing, but I was like, this should take me an hour. It should take me half a day, and I'm spending a week on it. What's going on? Half the time, I'm just waiting for the page to reload.
Chris: [Laughter]
Dave: Yeah, I know how that feels.
[Laughter]
Dave: Working on a 20 -- I mean it's been a really legacy Java app. It's almost like--I don't know--demoralizing. Is that the right word? It takes so long to do anything, even just to click the buttons to get it up and going. It takes … ten minutes.
Paul: Hugely demoralizing.
Dave: You're just like, "Ah, man. I hope I don't have to eat lunch and start this over again.
Chris: Yeah. I feel like sometimes there are more talking points out there that kind of crap on developer experience because developer experience sometimes is thrown out there like, "This is so important for this app," and people are like, "No, what matters is user experience. Don't make technological choices based on developer experience; make them based on user experience." I'm like, "Yeah, I get what you're trying to say there, 'Don't impart the cost of technology unto the user just because it makes life easier for you.'" Then I'm like, "But, there is a balance." Of course, there is. I need a good experience too. Otherwise, I can't and won't work on this thing.
Paul: Yeah, and if you're into an app. What was that? That would have been five years into an app. Then you just take a week and you start a new app and it's completely fresh. There are no models. There are no database tables. There are no JavaScript cruft. You start it up, and you start saying, "Has this been deployed already in my development environment?" because it's so fast. You forget what it's like to work on a new app and you can actually implement ten features a day.
Chris: Yeah, so you're the cofounder. You can't leave; but other developers, they can leave.
Paul: [Laughter]
Chris: They'd be like, "Ah, this is old and sucks. I want to work on something new and fast."
Paul: Absolutely. Yeah.
Chris: Yeah.
Paul: Yeah. Absolutely. Every day. I don't know how. We now have three full time developers working on it and their patience, it absolutely amazes me. I'm humbled by their patience to work on these problems. We're maintaining two versions of the app at the moment.
Chris: You really went deep here. It's iterative, in a way, I'm sure, because everything has to be, right? It's kind of like a ground-up thing. Tell us about it. The great rewrite has arrived.
Paul: I was really inspired by the Rails manifesto that DHH wrote. We had kind of drifted from doing things the Rails default way. I just love it when reading Basecamp posts that it's all about extreme productivity with small teams. I'm also very mindful that a lot of the very new technologies are coming from the likes of Google and Facebook where it's the opposite. It's large teams solving huge problems for massive apps.
To me, it felt very neat that the philosophy that we should be following is the small company doing a lot with very little. I decided to embrace the Rails way, everything just from the architecture, the controllers, the models, and things like that, just within the server side, and then to use Turbolinks for the snappiness of the UI.
Chris: Long live Turbolinks. Turbolinks rules.
Paul: Absolutely. It was amazing. It sort of culminated at the end of that summer where I had more or less rewritten the UI with just basic navigation for all of our major features. I deployed it and I sent it to a customer. He said, "Wow! Did you just reimplement the whole thing in React?"
Chris: It felt like React to him.
Paul: I was like -- yeah, exactly. Everything was so snappy. Pages that were taking a long time to load on the legacy system that is still our live system were loading instantly under this new thing. That's kind of where the magic ended because, for all intents and purposes, it was just a prototype. It was just a demo, and it didn't include the really, really deep layers of functionality that were the main reason that the legacy app was slow.
I saw this brave new world, and I sort of dove in. I'm not necessarily sure if it was for the right reasons. I kind of got caught by the shiny of something that wasn't necessarily a silver bullet for the kind of thing I was looking for.
Chris: Some of it was a Rails rewrite that was like, let's just clean up here. Let's do things the Rails way.
If that's the case, I feel you there. I don't write a hell of a lot of Ruby on Rails, but our team does. It was and it was our first really big Rails app. As the years tick on and you go revisit old code, you're like, "Eww, gosh, I can't believe we did it this way."
Paul: Yeah.
Chris: Let's do it more the Rails way.
Paul: Right. Then what I find was that Turbolinks allows you to add your own JavaScript after the page load, and so you can kind of stuff that into a file somewhere. But then the same problems start creeping up. You've got a section that requires a little bit more. Do you keep that in the same file or do you spin off a new file? Then if you come back to it two months later, can you remember which file it was in? All of these little problems started to creep in.
Then there are limitations at the edge of what Turbolinks does. For example, we have a three-pane, the standard kind of three-pane view with the menu and the list and a show. I think a master-detail UI architecture. I wanted to have it so that, if you did a search, the search would appear in the URL so that if you shared that URL, the search would stick in the center column but that the content of the viewer wouldn't change. But if you clicked on things inside the viewer, the search wouldn't change. I wanted to keep that all while maintaining the integrity of the URL.
With Turbolinks, that was hard. It was also hard because one of the things that the Rails way says is to use this onion skin caching, which means that you can't really put URL modifiers into cached list elements. I was just finding that at some point, to get this UI that I wanted, I was fighting against the constraints that Rails was bringing to the table.
Chris: Yeah. You're writing a JavaScript application, but you're using server-side tech to do it and trying to force it into place, maybe. I think Turbolinks really shines when it's not particularly JavaScript heavy otherwise.
Paul: Agreed.
Chris: Yeah.
Paul: Agreed. Sort of like I almost feel like I was up at night thinking about this. When you have a Turbolinks app that has zero latency, it is the most amazing thing ever. But if you have any kind of latency, a little progress bar appears at the top. The philosophy that I had written, the original Tito app, was that if you clicked something, anything, something should happen on the screen immediately.
Turbolinks does answer that with the progress bar that appears but, to me, the UI should change if you're clicking into a new section. Turbolinks doesn't really have a good strategy for changing the UI when you click a link. If you've got a list and then the list needs to move over and a show view appears, and you have also changed the URL, obviously you can do it in plain old JavaScript, but I want the URL to change as well. Turbolinks doesn't have a good way to manage that, and so these were limitations that they were fine. I was like, "Oh, we could ship this," but it's not right.
Chris: Have you seen all that portal stuff? It seems like that's going to be the future of Turbolinks-like stuff.
Paul: I'm not sure.
Chris: It's like Chrome's new thing.
Dave: It's multipage, but it has like single page….
Chris: Yeah, but it has the animation capability built in, so that slide-y stuff that Turbolinks makes hard won't be so hard with this….
Paul: Oh, got it. Yeah, well, that was another thing. Then the other thing is, like, we introduced modals, like edit modals in our new UI. Turbolinks didn't like that either. [Laughter]
I was just running up against these constraints. On the face of it, if I wasn't pushing the UI design to feel more like an expressive desktop app experience, then maybe Turbolinks would have been great. Obviously, there are great examples of desktop class experiences that Turbolinks does. Turbolinks is amazing.
Chris: I feel like it should ship with all WordPress sites.
Paul: Absolutely!
Dave: Yeah.
Paul: There is a WordPress Turbolinks plugin, but you're right; it should be completely native. That's what's wonderful about Rails is that, instead of having a plugin for Turbolinks, whatever it was with Rails 4, everybody got it for free and it's really easy to take out. To me, that was wonderful because, suddenly, all of these apps became fast, instantly, overnight, with very little overhead for the developers. That's amazing. Turbolinks is amazing. I love it.
Chris: But you outgrew it. I think I know what you're going to end up picking here, but you wanted to write, basically, a JavaScript-powered frontend like every other website in the world.
Paul: That's where we ended up. There were a few bits inside Tito where we required that little bit more stateful interaction within certain sections of the page. My ambition for the UI in terms of, for example, if you're adding tax to a ticket. I want there to be a little live preview of how the pricing is going to be affected.
You can write that in jQuery. You can write that in plain JavaScript in the old way no problem, but this is where I reached for Vue. I don't know where I got Vue from. I guess I was reading somewhere or it was on a mailing list I'm subscribed to. Somebody showed me Vue, I think, a couple of years before I reached for it this time. I was like, "Oh, that looks nice," and I never looked at it again.
At some point, I just went through the Vue tutorial. Having looked at React, having looked at Angular, and gone through the tutorials. I was coming at it from the point of view like, I had built JavaScript frameworks of my own in the past and kind of built custom backbone things, so I felt like I wasn't coming at it as a JavaScript newbie, but Angular and React just didn't work for how I thought about building JavaScript applications.
Chris: Did you look at Ember? Isn't that the one that's most, like, spiritually connected to Rails?
Paul: I'm not sure. Didn't Ember come out of SproutCore?
Dave: Yeah.
Paul: Then it was something that was at Apple?
Dave: I think it was Yehuda and Tom.
Paul: Tom Dale?
Dave: Dale.
Paul: Yeah.
Dave: But yeah, Ember is good. I know a lot of people are using it still, but it was kind of like you have to fully buy into it.
Paul: Right.
Dave: Now you're doing Ember, kind of thing.
Paul: Yeah. Maybe at the time, and I think Ember has gone through a lot of changes since I last used it. At the time, the magic of Vue was that, well, with Ember, you're building an Ember app. The magic of Vue was that I was able to target a single div and just make that a Vue app. Then I would get all the benefit of Vue in just that one tiny little piece of the page without having to put Vue anywhere else in the app. To me, that was amazing.
The parallels to jQuery were strong for me because, with jQuery, you could just target an element and add functionality to that element whereas, with Vue, you could just target an element and have that element act like Vue and get all sorts of reactivity and really interesting UI. It felt so lightweight to me. The pathway to it was so easy. Then it sort of grew from there.
Where, with Ember, I felt like I had to learn the whole framework before I could even get started. I did build a couple of Ember apps and I had moderate success. With Vue, I was like, "Okay. I'd love to be able to maybe look at the URL." I was like, "Oh, look. Vue Rooter exists." I was like, "Oh, I'd love to be able to share data between two components." I was like, "Vue Extra exists." All these extra pieces existed and, any time you Google for, "How do you do this in Vue?" there always seemed to be a really good, clean, simple answer.
Then the way that it presents HTML, CSS, and code inside single file components, that was then like, "Oh. My. Word. That's nice." It was jaw-dropping for me.
Chris: Well, that's a good enough reason to pick anything. I feel like you hear that a lot from Vue people. It just clicks with their brain a little better and, if you feel productive in it right away and whatever, then why not use it?
Paul: Right. I've spoken to people who simply cannot stand the look of Ruby code. Personally, I don't understand that because it looks very clean and elegant to me, but I totally get it that it's just not going to work for everyone.
Dave: Ruby code def looks the best.
[Laughter]
Chris: Solid. Pretty good. Pretty good.
Paul: I think that the speed at which you get that joke--
Dave: I mean it'll be 3X by Ruby 3.
[Laughter]
Chris: But you're not really considering replacing Rails. For some reason -- do people ever leave Rails?
[Laughter]
Dave: Are you stuck on Rails for life? Are you imprisoned? Does DHH have you imprisoned? Please blink twice.
Paul: I'm aware there are tons of options. In my mind, I've already rewritten the whole thing using Lambda functions.
Chris: That's the thing, Paul! You totally can!
Paul: [Laughter]
Chris: That's what we're really excited about is that never at CodePen do we consider dropping Rails. You're like, "Why? It does lots of useful stuff and fast. Who cares? It's fine."
Even without us knowing it, little by little, we're slowly replacing the whole damn thing with Lambdas. It's incredible. There must be 40 of them now that we have. They're all in this mono repo thing now that we moved to where they can all kind of like share little bits together. I'm like, dang. We've actually accidentally rewritten the app in Node, almost.
Paul: Wow. Are you happy with the local development story? Does that feel solid to you?
Chris: Well, it does sometimes, you know. It depends on what you need it to do and you're testing and stuff. I feel like we took a couple of steps there, but serverless.com is, I think, I'm afraid to say, the best answer. I don't know why I'm afraid to say it. It's pretty sweet.
Dave: Yeah.
Chris: I wish we found it sooner, but it's kind of the most robust choice, I think, for needing to spin it up locally. Netlify is making some good strides there too with their Netlify Dev Tool for testing all your Lambdas locally and stuff.
Paul: Yeah. I put out a long Twitter -- well, it wasn't that long -- just a rant. I see this come up on Twitter every so often, but just the local development ecosystem. Maybe on a Mac, maybe on Ubuntu it's better with Docker or whatever, but to be a local developer, to set things up locally is still really, really difficult because, like you just say, "Oh, yeah, no it's not. You just install a few commands." It's like, "Yeah, but to install a few commands, I've got to install Homebrew. To install Homebrew, I've got to install this and that."
Chris: Yeah, that's true.
Paul: It's getting better all the time, but I feel like the story is still--
Chris: I mean it depends on what you're writing. If you're just writing a little Node function, you can kind of just hit it directly without installing anything, but you're right; you really should be testing it in as close to the development version as possible. It's a particularly big deal with Lambdas because there are so many limitations for how long they can run and where you're allowed to write files to and where you're not.
Dave: Yeah, that timeout part of Lambdas keeps me out of that game because I'm like, what if it's slow and it just bails? Oh, no!
Chris: Oh, my God. Did you see--? This stuff is really shaking out. If you haven't yet moved to literally AWS Lambdas, for one thing, they're not the only player in that game. The latest player is Cloudflare with their workers, which are like, "You can run a million a month for free." Then you go over that and you struggle to pay for it, even with Lambdas, which is the most expensive choice. Our Lambda bill is like $5 a month. It's insanely cheap.
Paul: Which is absolutely great. Yeah. Yeah, I don't know where I stand on it. Ruby on Rails is an extraordinary framework and it has been responsible for pushing many other areas or parts of the Web development, software development folks forward in many ways. I think that it's all just part of an ecosystem. I think the more ways to do things is a good thing. I think having lots and lots of ways as a newbie is not necessarily a good thing. But, yeah, to be able to have that level of power without putting a huge amount of cost behind it is the most amazing thing about Lambdas.
I think the future of Rails is pretty solid, and I think the future of Lambdas, as an architecture, is also pretty solid. That's what's great. For one to win, the other doesn't have to lose.
[Banjo music]
Chris: This episode of ShopTalk is brought to you in part by .TECH Domains, which I think are very cool. Of course, I'm a big fan of just picking the right TLD for you. If your thing is technology focused at all, there's really no downside to having a domain that isn't like .com or .net or something. Why not pick .tech?
I think it's very cool and it's not particularly old. It launched in 2015, so I think the chances of you finding a really nice .tech domain are a heck of a lot higher than they would be on anything else. There are all kinds of people using .TECH Domains. CES, that huge technology conference is doing it. Viacom, Intel, and there are just lots of big companies that totally trust this and go with it.
Of course, you buy them on a .tech domain. Just go to go.tech, which is pretty awesome. I know go.tech/codepen works, and you get 90% off one- and five-year domain registrations; 90% off is pretty significant too, so you might as well go check it out. See if the perfect domain for you, too.
I would say, don't think too hard about domains for fresh projects. I feel like my little story behind this is that I ended up just putting a dash in CSS-Tricks because it was like, whatever, you know. This thing doesn't even exist yet. I think that's kind of fine. I feel like, just get the domain that you feel is pretty good and pretty close, and your chances of doing that on a .tech domain is a lot higher. Then put all your time, effort, thinking, and work behind what you put on that website. That's what I care about and that's what you should do too.
Check out go.tech/codepen. There might be a /shoptalk, but I know /codepen works, go.tech/codepen.
[Banjo music]
Chris: There is some medium ground here. As you're getting closer to releasing Tito 2, or whatever you're calling it, to the world, how much did you rewrite in Vue, ultimately? All of it?
Paul: Anything to do with managing an event is now a Vue view. We've kept around a couple of legacy paradigms, like we just kind of copied some of the forms over and we're still serializing them in JavaScript before sending them to the server rather than using Vue data attributes. We will slowly get rid of that. That's the desktop. Basically, the UI was completely reimplemented in Vue.
Chris: There's a lot less Rails, right? Your views just don't have them anymore. They're Vue views, not….
Paul: We're deleting ERB files, yeah. It's weird. It feels really weird to me, but it feels right most of the time. Then, for the checkout app, it was just a grand up reimplementation.
On the Rails app, the main driver, the wonderful driver was that we have this kind of stateful UI where Vue Router just made so much more sense for Vue Router to control what components and what was showing and being shown and hidden on the page. That solved all the problems I talked about earlier.
You can have a search, and the search is independent of what's currently showing. You mess around the search. You can click through things. The show pane changes, the URL changes, and the state is kept…. Everything works, one, like magic and, two, without changing Vue or having to write anything custom, which was wonderful.
Then the checkout is a completely embedded Vue app. To me, that bit almost makes me more happy because the checkout was always intended to be a separate kind of conceptual app because we wanted to run this checkout experience on other people's websites. It didn't really make sense for that to be a hosted server-side app because the only way to deliver that is inside an iFrame. To me, it brings me great joy to be able to say to people, "Use the same implementation," because we're basically future compatible with our Tito widget custom component, but now we can say, "Upgrade to V2 of our JavaScript and you get an embedded app rather than iFrame experience."
For event marketers, which is a lot of our customers, there are a lot of added benefit there because the checkout now runs natively on their page. For things like attribution and Google Analytics, writing their own analytic hooks, or just making sure that they have kind of full control of anything that happens on the page, they get all that for free.
Chris: All your state is Vue x2. Yeah.
Paul: All the state is Vue X and then the other great thing about it being an embedded app is that we will be able to do more or less pure white label without having to actually white label anything because we can use hashtag URLs on third party websites, and the hashtag URL controls the URL of what our widget shows. What we're going to be able to do is go all the way back to that initial Funconf experience where the whole experience stays on the site and it looks like you never left Funconf with URLs that work, so we'll be able to send an email that is like whatever. If it's CodePen Conf, you will get an email that says, "Go to CodePen." You'll get an email with your ticket, and the ticket will have a "View Order." You'll click on "View Order." You'll still be on codepenconf.com/hash/titoticketid. Then the page will load. Then, in an overlay, the Vue app will deliver the Tito experience within codepenconf.com. To me, that is just as close to magic as I can imagine.
Dave: That works because you're shipping a Vue app to the page, right?
Paul: Yeah. Yeah.
Dave: You're just putting a Vue app on the page.
Paul: Yeah.
Dave: Wow. That's cool. I think a lot of the, "Oh, we moved to a JS framework." Stories are all about developer ergonomics. But now, this is a customer thing now. You've enabled new customer territory. I think that's really cool.
Paul: Right. In the dashboard, it is mainly about allowing us to build a UI that, one, conforms to what I talked about earlier and, two, allows us to build more advanced UIs in terms of helping the customers, which is our customers. Yeah, the other piece, the checkout piece, is actually allowing our customers to have an experience on their own website that feels much more rich and app-like.
Dave: Yeah, that's very cool. Can I go back to Rails, because Rails plus Vue seems like my dreamland and I want to live there one day?
Chris: We just need one more piece to make it really dreamy, though, Dave, and that's GraphQL.
Paul: What's that?
Chris: GraphQL.
Dave: GraphQL? Oh, man. Yeah, I guess so.
Chris: [Laughter] No, I'm just kidding. No, go where you were going to go.
Dave: I like Active Record. [Laughter] You still have whatever/posts or tickets, let's say, because you do tickets, right? I assume that's in a Vue folder. Then you just have an index RB or do you just have the application index ERB that just renders an empty body tag?
Paul: Yeah.
Dave: What are you rendering there?
Paul: This is where we need a Screencast.
Dave: Yeah.
Paul: I went full on Rails inspired when I was designing the Vue architecture. Vue doesn't have a folder structure that it sort of enforces, so Rails -- I assume you saw the Rails is now shipping Webpack by default.
Dave: Yeah, yeah.
Paul: Which really helped us a lot.
Chris: Webpacker.
Paul: With Webpacker, yeah, but it is Webpack. [Laughter] Webpacker provides a Vue getting started setup. Going from there, what I did was I just basically created a components folder. I structured that exactly as Rails would structure its views folder.
Dave: Okay, so like component/tickets or component/ticket.
Paul: Yeah, but if you're in Rails and you're doing the RESTful Rails, you'd have tickets_index.html.erb, show.html.erb, edit.html.erb, and form. I just did exactly the same with Vue. So, if you go to the tickets components folder in our repo, you'll see edit.vue, delete.vue, form.vue, new.
If you click into new, the pattern in Rails it so just load a form initialized for new records. That's exactly what we've done here. We've created a little mix in for new items and then you import the tickets form. That becomes a component. The tickets form is basically the only thing that is inside the new.
Dave: The new and the edit share the same form mix in.
Paul: Exactly, yeah. Then we've got--
Dave: Aw!
Paul: Yeah, and it's really, really good. It's such a joy to use. Then the most wonderful thing for me about Vue is that, once I got it, I realized I'm in JavaScript world here, so anything can be dynamic instantly. That to me was amazing because one of the things that I was doing so often with forms for Tito was showing and hiding elements. I think I was using Twitter, Bootstrap, and jQuery--obviously jQuery--to just put the show element inside a tab. We were kind of switching between tabs to show and hide. In Vue, it's just literally display, true or false, and things actually come out of the DOM or come back into the DOM. You can also, optionally, simply show or hide elements as well.
Now that it sort of looks and feels like standard simplicity of the Rails templates that I had before but, at any point, I can just make something dynamic. There's just a great comfort knowing that. Then seeing it in action for a very simple -- as I was saying before, the tax calculator is what comes to mind. You type in a ticket. Then you say, "I want to do 23% tax," and it shows you a preview of what people are going to see. That's just how UIs should be.
Dave: I felt like, in Rails, or any application, you have, let's say, a follow button or a favorite button. It needs to send a request and turn green or checkmark when it's finished, right?
Chris: Mm-hmm.
Dave: Maybe you trick it and you do the checkmark and then you send the request and then check if it fails.
Chris: We call that a GraphQL mutation with an optimistic response over here.
[Laughter]
Dave: Optimistic response. Hey! You can code that in Rails, like an ERB, jQuery, and Turbolinks, or whatever. You can code that about once.
Paul: Yeah.
Dave: You code it three or four times just for a button, a toggle, or anything, you're just like, "I hate this. This is not a system at all."
Paul: Yeah.
Dave: I have to modify my controller to send back a JSON response just for a favorite thing. I think you get pretty deep. It's a lot of work to make it happen, but that's what's pushing me to something like Vue or something with Rails. It's just like, yeah, it just happens.
Paul: Right. I've literally brought up my implementation of action button. It's what you described. The action button is a button element that has a font awesome spinner icon inside it. When you click it, it ships off to whatever action you've sent it. It displays a spinner, and it stops the spinner once it's done what you've asked it. I've sprinkled that all over this Vue app.
It's amazing because then what it does is, you can feed that action back into whatever Vue component you're using. Let's say that's something that updates the Vue X state. The action button doesn't know anything about the state at all. It just does the spinner and it ships off the request. When the request comes back, the component that you're in updates the state. Then the whole template updates. It's as perfect as I can imagine that kind of interaction or building that kind of interaction.
Chris: I like that. I also like that Suspense is coming to React, which is actually pretty rad. I wonder what the Vue -- any time you think of spinners in JavaScript components like this, I think of that. I feel like I've seen talks that are like, "Hey, you can get a little heavy with the spinner thing." I don't know if they have Chase Bank in Ireland or not, but it's a great example over here where they've clearly built the whole app--
Dave: Ireland has every company, Chris, because they all offshore their…. [Laughter]
Chris: Yeah.
Paul: We have a lot of banks, but they're not necessarily all consumer banks. We probably do.
Chris: The home page for chase.com has like 90 spinners on it. You're like, "Whoa! Whoa! I'm on a super-fast Internet connection here." I think there is more to it, but part of the heart behind Suspense is like, "Can you just wait on showing the spinner if the response happens in 40 milliseconds anyway?"
Paul: Yes. Yes.
Chris: Yeah. Yeah.
Paul: I've done that a couple of times. Absolutely.
Dave: Paul, let's look five years from now. You just did a big rewrite. Again, I'm like dreamland, never hills view. In five years, what does your app look like? Do you think it's all Vue with a Lambda back-end kind of thing or is it Web components? Where do you think it goes in five years?
Paul: That's a really good question. Based on the previous five years, it's going to be exactly the same as it was as it is today.
[Laughter]
Paul: It's a really good question. The next thing for us is to decide how ambitious we want to take the ability to scale Tito. The example I have in my mind is, like, could we handle the kind of event like a giant popular conference that needs to sell 100,000 seats to 20 million who want it in 5 minutes? That would obviously influence our architecture choices immensely.
The other interesting question around the idea of Lambdas is that Lambdas, to me, are not necessarily interesting as a technology themselves. They are interesting in the ability to build those kind of massively scalable systems, but also whilst keeping the app architecture in your head.
That's the nice thing about Rails is that you can go to your Rails app, and you can just have a quick look around. Then you can instantly understand how it all fits together. I don't think I would easily sacrifice that.
For me, with the idea of a large app with lots of dependencies built with whether it's serverless functions or a different stack, to me it feels like it would have to be really easy to conceptualize. So, I'm keeping a very close eye on everything Netlify is doing because they're definitely, I think, leading in what they're proposing to do, which is developer experience. I think companies that value developer experience are really attractive to-to me at least, but I guess--the kinds of folks who appreciate Ruby on Rails and all of the values that are in the Rails manifest, which is being human friendly and coding for joy and code being aesthetically pleasing.
If we're going to go a little bit abstract, I would like to move toward a case where our app--to use a little bit of a design cliche--you could appreciate the user experience on the front-end but, if you looked at the code, you could really appreciate how it's all put together.
Chris: Sure.
Paul: At the moment, we're not there. There are areas that are a bit of a mess. But, whilst I was doing that re-architecture, I was always really mindful of, like, I want this code to be beautiful. I want people to come and look at it and say, "Oh, that's nice." Just based on your reaction of just how I described one of the pieces today, I sort of feel like we are getting there, so that was really nice to hear.
Dave: Yeah. Does your team, are they like, "This is good," or maybe you don't want to speak for them too much? Is everyone just exhausted like, "This is the longest rewrite of my life. I'm going to just cry in a puddle"?
Paul: No. We've abstracted a few bits of the rewrite. We shipped a single sign-on system that was abstracted out of the rewrite and we shipped that pretty early. We've been able to iterate tons of features on that, both kind of back-end features and customer-facing features. We did a rewrite of our billing engine. We've been able to iterate on that as well.
Having rewritten a lot of stuff, it doesn't mean we haven't shipped everything. Some of it is really fun to work on, and I think that that's what folks appreciate. I think everyone is quite jaded that we've had a lot of stuff in development for so long, but it always feels like the light isn't too far away, which is, I guess, a good motivator. It always feels like any delays that we introduce, we take a collective decision and it's all in the spirit of trying to get it as right as possible before we do ship.
Chris: That's probably smart. My tendency is just kicking crap out the door. I'm like, kick-kick-kick-kick-kick. It's nice to have people reign you in in that. It feels like there needs to be a balance. There needs to be somebody that just pushes super hard on getting stuff out the door, and some people being like, "Whoa, whoa, whoa, this needs to be better tested. It needs to be better QA'd. We need to rearchitect things to make this more scrutable." I don't know. You need a good cop, bad cop, thing.
Paul: Yeah. Check back and see how it works out in the future, but we've made the decision this week to ship half of the rewrite to a subdirectory.
Chris: Hmm. Nice.
Paul: [Laughter]
Dave: All right. Almost there. Halfway there. All right, well, I think that's a good place to stop. Thank you, Paul. This has been very cool and cathartic for me to get a look into how cool products get made, so I appreciate that.
Paul: What a pleasure. Thank you so much.
Dave: I guess for those who aren't following you and giving you money, how can they do that?
Paul: I'm @PaulCA on Twitter. Tito is @useTito or ti.to on the Web.
Dave: Awesome. Well, thank you so much. Thank you, dear listener, for downloading this in your podcatcher of choice. Be sure to start, heart, favorite it up. That's how people find out about the show. Follow us on Twitter, @ShopTalkShow for tons a tweets a month.
If you hate your job, head over to ShopTalkShow.com/jobs and get a brand new one because people want to hire people like you. Chris, do you have anything else you'd like to say?
Chris: ShopTalkShow.com.