Hacker News new | past | comments | ask | show | jobs | submit login
Flight: A lightweight, component-based JavaScript framework from Twitter (twitter.github.com)
236 points by uggedal on Jan 31, 2013 | hide | past | web | favorite | 83 comments



Recently I discussed with colleagues why Ruby on Rails succeeded where no Python framework did. One hypothesis put forward was that there was 1 popular Ruby framework (Rails), but many popular Python frameworks.

I wonder if it will be the same for JavaScript front-end frameworks, with no one gaining the greatest mindshare.

Not necessarily make any claims about what's preferable (one framework to rule them all vs many competing options) - though I'm certainly curious about that as well for the front-end frameworks.


It was my understanding that Django basically dominated the Python web framework field and is the Python equivalent of Rails. Is that not the case?


Django does dominate the Python web framework world. Flask does pretty well as a microframework runner up.

Methinks someone who left Python in 2002 is out of touch. ;-)


I would say Django (framework) and Flask (microframework) are the biggest. But lots of people build many mainly due to the WSGI support that makes it easy.

At first, I actually thought this post was about a PHP microframework called Flight: https://github.com/mikecao/flight


For JavaScript, it doesn't matter. It is privileged by being the only thing that runs in browsers.


There are a alternatives:

https://github.com/jashkenas/coffee-script/wiki/List-of-lang...

I, for one, really like Dart. It's very nice to use.


I, like most, hadn't even heard of Ruby before Rails. The two were synonymous - I remember having to explain to a number of people that should have know better that Ruby was a language and Rails was a framework.

I don't really know the history of Django but I'd wager it took a lot of inspiration from Rails.

Rails had that year's head-start on Django. By the time Django came along everyone was pretty deep into the development of Rails. I think it's mostly down to first mover advantage (it really did revolutionise web development).

In terms of frontend frameworks I have no idea what's going to happen.

Backbone definitely had the initial traction and it's nice and clean, but damn AngularJS makes development quick and easy. I've implemented things in the last couple of days in Angular that would have taken a week or more previously.

The Angular approach is unorthodox and seemingly inefficient but serves the developer well at the cost of CPU cycles.

Then again, I backed Prototype during the initial js lib battles and we know how that worked out.


> I don't really know the history of Django but I'd wager it took a lot of inspiration from Rails.

Not exactly. Django mainly came into existence as a formalization of a lot of helper functions that The Lawrence Journal-World newspaper found themselves building over and over again when they built web apps. Adrian Holovaty, Jacob Kaplan-Moss, and Simon Willison created Django separately from Rails, even if it wasn't released publicly for a while (and then got kinda stuck at 0.96 for a while after that).

It's like Newton and Leibniz with calculus or Darwin and Alfred Russel Wallace with natural selection; there were a lot of similar problems to be solved in the web dev space in the early '00's, particularly around accessing databases, structuring URLs, and handling authentication, it makes sense that a few similar approaches would show up around the same time.

If you're really interested in the history, this "Snakes and Rubies" video is worth watching: http://www.youtube.com/watch?v=cb9KDt9aXc8

(I should mention that I tend to personally prefer Python to Ruby, very much like Django, consider Adrian Holovaty a friend and we both once worked at msnbc.com at the same time.)


Ah, thanks for the info - need to learn my history a bit better.

My point still stands, I think that by the time Django made it out into the world Rails was already established as the dominant framework. Django 0.96 hadn't been as thoroughly thrashed as Rails and definitely had the warts to prove it.

Django is a much more mature beast these days, though I still lean more towards Flask if there's a choice to be made.


That's a good summary. I answered a question on Quora about this which goes in to a bit more detail: http://www.quora.com/What-is-the-history-of-the-Django-web-f...


> It's like Newton and Leibniz with calculus or Darwin and Alfred Russel Wallace with natural selection; there were a lot of similar problems to be solved in the web dev space in the early '00's, particularly around accessing databases, structuring URLs, and handling authentication, it makes sense that a few similar approaches would show up around the same time.

And can you imagine, they could have patented each and every one of those solutions, and spurred their competitors to greater innovation! What a surprise that they didn't - how silly of them. /s


First Django succeeded. i.e. Instagram and Pinterest. Second you guys have Sinatra. Quite often when I inspect a webpage of a notable site, I find Backbone in the mix. Agree or Disagree?


I will give you my 2 cents on this argument.

tl;dr - Python had no web frameworks built in or community favorite. Rails was a complete re-think of how to do web apps in the age of XML (acaffolding, convention over config, etc.) Every language since has now realized this and copied Rails. This is a good thing.

if you want a story while drinking coffee, read below...

I was a serious Python guy from 1996 til 2006. I still like and respect Python. Before Python, I was a wanna-be Smalltalk fan. (This is important towards the end)

Python always had a philosophy of 'batteries included'. This means that it will have libraries for just about everything you need as supported parts of the language. Contrast this with Perl where CPAN quality was very hit or miss. Then contrast this with Lua or Scheme. They had too many libraries and none were standard. Most of those were, again, not supported or not of production quality.

As a result of 'batteries included', Python was just a dream to work with. Everything was doable or easy and performant 'enough'. I could go on, but that still wouldn't do it justice. They had a great language design, great libraries, and portability. Python is still a great framework. They didn't have a good 'CPAN'. This was ok, since the built-ins were good. Outside of that, it was a bit of a mess.

However, over the years, it became obvious that Python had a huge flaw. They did not deal with HTTP or HTML well. People don't even talk about this anymore, but the promoted framework was 'Plone'. This framework was so complex and convoluted, that building a simple web app was just not possible. It is not spoken about anymore. New frameworks popped up as a result, and very few of those were much good. This was a huge problem. Most people had to build their own production system. I had to switch languages for a job in 2002, and I didn't deeply follow the web framework discussion in Python for quite some time. I can tell you this, though. There wasn't a single framework that popped up as the winner or as exciting.

I'm typically a conservative. When I see a new thing, my typical take away is "this has been done before, what did they not learn from prior art". For example, lets say someone creates a new OS. If they don't really understand Unix, they will get it wrong.

When the first Rails screen-cast came out, I took a look. There was a lot of buzz that was increasing. I go, 'what the heck, it is only 5 minutes and it is a video.'

What I saw was an excellent architecture for building web apps. This architecture was better than anything else out there by quite a wide margin. They supported modern HTML. Convention over configuration. ActiveRecord was truly a treasure. The default site actually looked relatively decent (vs. the typical programmer, zero whitespace, huge serif, Netscape circa 1998 site). They URL conventions looked good. This architecture was human friendly. These people understood what a modern app should look like.

I was sold. Just from that screencast.

I believe in using whatever language is needed, if the framework is excellent. I wanted to build excellent web apps, so I learned Ruby. Ruby was easy to learn, and it is as good as Python. Much to my surprise it was the first language to pull off having Smalltalk blocks! People didn't even notice this and now they really like it. Everytime I showed this to coworkers, they would go 'what is that? I don't understand'. When people eventually learn Rails, they would go.. "check out these block things. They

So, in summary, Python didn't have a web framework. Rails was an excellent design, compared to any languages framework. Rails is pretty much going to be here for quite some time.

BTW, at this point. Rails is not my first choice. I think it is too heavy and web architecture has shifted a lot. Still, it would be in my top 3 choices.


Great summary and a joy to read. Having moved from Django to Rails not so long ago, I do enjoy some things in Rails, but feel the heavy-weight aspects you touched on.

Dealing with some of the deployment and infrastructure aspects of our rails app, I also see how much more memory and resources are consumed. Anything from Unicorn processes through rake tasks, to the time it takes to launch the console or run tests seem an order of magnitude longer. It's like steering a 747 or a cruise-ship. Once it's running it's fast and quite stable, with nice and comfortable features and lots of elegance. But it doesn't feel anywhere near as nimble and responsive as python/django (and django is by no means a small framework).

I'm wondering what's your top 3 choices today.


actually just re-read my post. it makes me cringe a little. I was in a hurry and hit send a bit too fast.

The other choices:

I like node.

I've always liked the async/model on unix. Their community is healthy and a lot of the open source is clean. The npm system is a good system. Even if you aren't running node, people are using node as their toolchain for pure front end development (css transform,asset pipeline). If you are doing anything real-time with a browser, it is my first choice. (Erlang used to be my choice there)

Also, yes Javascript has its pros and cons, but at this point everyone must know it.

As another choice, I still like sinatra with rack middleware. Very light and it works well.

Beyond that, it depends on the application. I might use C,Scala,PHP or ?? who knows what.


I went the other way, started using ruby and then switched to python because of the machine learning libraries and async frameworks. I found rails too bloated and too much magic and fell in love with minimal frameworks like tornado that i have a lot more control over. My productivity increased dramatically using tornado and flask. It seems like each of these languages have their phases to be on top, right now i am using nodejs and twitter storm for a project and i am finding it quicker to build async web services and the amount of async libraries is really helpful.

Scala is allowing access to some good java machine learning libraries and tools such as mahout, scalding, storm, akka, finagle, therefore i believe as a developer we should not define ourselves to a platform or language but we should always be curious and use the best tool for the job. I am not using tornado for my current project because nodejs had the async libraries i needed, that simple.

As business opportunities are arsing around big data, sensors and the internet of things our favourite platforms and languages are going to evolve as well.


Hi, good perspective. I've used Scala as well. Twitter held a meetup with Martin O back in 2007. At the time we started using it, the actor code was not ready for production. I think there is a lot of great activity in that space. I'm sure most of these have been addressed.

I also agree with your last statement. This is not the end of the road.


> I had to switch languages for a job in 2002, and I didn't deeply follow the web framework discussion in Python for quite some time

Good Lord. This is like 11 years ago?


heh - I didn't think about that til you mentioned it. Yes, but during the 2002-2007 time frame, I was aware of what was at the "front of the parade" for python. Nothing really stood out til Rails. If I knew Aaron Swartz, things might have been different.

Oh yeah, this reminded me of another painful thing back then. It was so difficult to find documented, clean MySQL bindings for python. Every experience there was terrible.


Learning Plone was a requirement for an internship I was considering in 2006, fwiw.


You have my sympathies.


Out of curiosity, what's your first and second choice?


Great writeup.

What would be your #1 and #2 choice?


see my reply above


Or, Rails was well-marketed, had lots of 3rd party plugin components, and actually had database migrations (oh the pain of databases and models in early Django)


Javascript front-end framework are really new (3 years old) compare to web frameworks. The need is also quite different. There was a need for a Javascript framework and we can say that jQuery won the battle. We will have to wait few more years to see most of the web app running with a Javascript front-end framework, then you will maybe have a winner!


If Django isn't a shining example of a Python web framework succeeding then I don't know what is.


This looks like Twitter's version of Web Components?

You might have heard of it shadow DOM etc. Basically the idea is to be able to add GUI components eg <progressbar> that are as integrated as the native ones are.

https://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/... https://plus.google.com/103330502635338602217/posts

Mozilla's X-Tags implementation seems closer to the goal though: http://www.x-tags.org/ IIRC it can do this because it's using Object.observable internally to detect DOM changes.

ie with X-tags you don't need the javascript definition part, just: <x-map data-key="39d2b66f0d5d49dbb52a5b7ad87aea9b"></x-map>.


Web Components is the storm that will wipe away all these frameworks: Backbone, Ember, Angular, Knockout, etc.

Unfortunately, we’ll have to stick around with them for the following couple of years, probably, as for now, most of the Web Components APIs are available only in Chrome Canary and Firefox dev builds. Monkey-patching (AKA “shimming”) to the rescue…

In my opinion, the more certain nowadays libs resemble the Web Components specs, the more clear the developers’ choice towards those libs should be.


I've played around with most of the JavaScript MV(whatever)'s and I'm just not sure about this.

"Flight is organized around the existing DOM model with functionality mapped directly to DOM nodes"

It seems the point of using backbone or which ever other framework (I'm partial to Angular so far), is to decouple from the DOM, so that if and when your markup changes, your JavaScript doesn't. This doesn't seem any better than well written jQuery.

Am I missing something?


It looks like the concerns are different between Backbone and Flight.

Flight is for building UI components, not client views. I think it might be akin to jquery widget plugins, but with more structure to make it easier and potentially more extensible (eg the mixins).

So yeah, in this case I think they were right to ride on DOM events.


What I think is interesting to see, is that the patterns from Nicholas zakas' "scalable Javascript architecture" spreads into new frameworks.

Enforcing components w/o return values, communicating via pub/sub is also seen in aura.js and backbone marionette. I think this thinking will lead to more stable, easy to change js apps. Exciting!


why not just use component?

http://github.com/component/component


agreed. the problem with these AMD frameworks is that the asynchronous nature and deeply nested callbacks are almost completely unnecessary because you end up concatenating/minifying the assets before production anyways. You end up with unreadable files. With synchronous CommonJS frameworks, most of this nesting is eliminated.


With AMD you generally only have one (in a rare case, two) levels of indentation added on top. I much prefer CommonJS too, but deep nesting's not really a fair accusation.


true. i'm being a little sarcastic, but 1-2 levels of _unnecessary_ indentation bothers me when it's easily avoidable.


Fair enough. Aesthetic reasons aren't the worst ones in the world.

It's also just so... Yeesh. Complicated, in comparison.


Do you find that this is a big deal in practice, or does it just feel messy to you?


it's more like it's unnecessarily messy, which bothers me. if it was necessary, then it wouldn't bother me. and it's not just the nesting that is messy, the whole `define` stuff going on is messy.


This is a really great concept, that enables client applications on a different scale (and reduces their complexity very much). Thanks Twitter team!

A big advantage over web components (or angular, or others) is that this is much more lightweight, less polluted and has a better separation of concerns. (On the other hand, web components will be a great deal for browser-app shops developing for third parties).


Seeing as how Bootstrap fails at even the most elementary of tasks (using absolute instead of relative values for positioning, for example, screws everything up when the user's zoom is anything other than 100%, among many, many, many other flaws), anybody who uses this framework is a competitor I am... not worried about.


I've read the page two times now and still don't understand what it does. Can someone explain?


Why not just use Flash?


welcome to 2013


>When you create a component you don't get a handle to it. Consequently, components cannot be referenced by other components and cannot become properties of the global object tree. This is by design. Components do not engage each other directly; instead, they broadcast their actions as events which are subscribed to by other components.

This is a maintainability nightmare. I've gone down this route on hand rolled JS frameworks before and it leads to untold headaches. Sometimes a little coupling is the right solution. Many times it is better for components to have knowledge of their environment. In most applications (other than iGoogle maybe) one thing on a page depends on another. I feel it's far better to explicitly call out those dependencies by having a reference to the other components and directly invoking methods on them with known arguments of a known type.

Edit: Also, what kind of memory allocation implications are there with event subscriptions and no references to the components themselves?


I understand where you're going here: pubsub communication can be both a blessing and a curse, it can be hard to ensure a consistent usage, hard to debug when there's long chains than one place -> the other.

I think the sandboxing from aura.js is pretty interesting here, since you can always look up which widgets are allowed to talk, it makes it easier to narrow down the problem.


The real nightmare comes from the undiscoverability. The right tools can help you out. Unobtrusive JavaScript used to be a nightmare, for example, until Chrome added its 'Event Listeners' tab in the developer tools. Another being the 'Go To Implementation' instead of 'Go To Definition' when writing C# with code that uses interfaces.

Yes, you need coupling when debugging. But it need not be in the libraries and code you use.


Er...why is this broadcast model a maintainability nightmare compared to coupling components to their environment and to other components? I guess maybe it's too much abstraction for a website in which you know most components won't be reused...but then you wouldn't be Flight's intended audience.


Because you need to know what events you care about.

If instead you declaratively define the state of a component via a shared model then you don't have to care how something was updated, just that it was. Any new way you come up with mutating the object will be reflected in all of the components that watch the object.


i might be misunderstanding what they mean, but one immediate thing i thought about was how this would make composing a component out of "private subcomponents" (i.e. those that only the parent component could see) a lot harder.


So a component subscribes to some event somewhere. Who's triggering the event? When you're debugging in Firebug or someplace, what does your call stack look like? How hard is it to find the source of the event? All you know is it bubbled up from somewhere.

Say I want to add a component to a page. Which events do I need to trigger to make it do the kind of things I want? What do I need to pass as a payload to those events? How do I know if there's a consumer for my event or not? If there isn't a consumer that's probably an issue.

The eventing bus adds an additional layer of indirection. If your app can be truly decoupled, then this would be great. But I just don't see that use case being very common. I have never worked on an application where the broadcast model was more benefit than pain in the long run.


> So a component subscribes to some event somewhere. Who's triggering the event?

Look at the event source.

> When you're debugging in Firebug or someplace, what does your call stack look like?

Look at the data passed to the event. The callstack starts at the function attached as a listener to the event.

Is the event data incorrect? Then the problem is in the code that triggers the event.

Is the event data correct? Then the problem is in the code listening to the event.

> Say I want to add a component to a page. Which events do I need to trigger > to make it do the kind of things I want? What do I need to pass as a > payload to those events?

Documentation. That is indeed the cost of using an abstraction that decouples code, but it's also a cost that has benefits - good documentation is useful.

> How do I know if there's a consumer for my event or not?

It's not important. That's the point of an event-based system, it's a decoupling. De-coupling is an abstraction. It works on pages that are based on components or modules.

If you don't want that abstraction, then using code based on that abstraction isn't a particularly good idea.

Event-based frameworks do need documentation - but reusable code should be documented anyway. There's just more emphasis on getting the documentation done in event-based systems.


> Look at the data passed to the event. The callstack starts at the function attached as a listener to the event.

> Is the event data incorrect? Then the problem is in the code that triggers the event.

> Is the event data correct? Then the problem is in the code listening to the event.

It's difficult to reason about the event triggering code when it's call stack is not available. For a larger code base where an event is trigger in multiple locations with different logic in each location, it may be difficult to pinpoint the triggering code, let alone reason about that code. Direct function calls are much easier to debug because you can walk up the chain of calls and inspect the variables and state of each frame.


> For a larger code base where an event is trigger in multiple locations with different logic in each location, it may be difficult to pinpoint the triggering code, let alone reason about that code.

Populate the event with the event source.

> Direct function calls are much easier to debug because you can walk up the chain of calls and inspect the variables and state of each frame.

If your listener depends on knowing the state of the triggering code, it is a sign your events are too granular. If you require a complete stack trace to be able to debug a listener to the event, that sounds like you have a bigger architectural problem to deal with.

Events are coarse. The listener should not care about why the event was fired, only that the event has occurred. Events are not a replacement for direct function calls. They are a mechanism for de-coupling pieces of code that can be independent of each other. It doesn't sound like your situation is one where there's that independence.


> If your listener depends on knowing the state of the triggering code, it is a sign your events are too granular.

That isn't practical debugging. If I'm debugging an event that was fired and I don't know why, or the payload doesn't match my expectations, it would be very useful to inspect the state of the triggering function and see where things went wrong.


This library uses jQuery events which are synchronous. I.e. the event handler's callstack will include the triggerer a few levels up. Not any harder to debug than a direct call version.


>That's the point of an event-based system, it's a decoupling. De-coupling is an abstraction. It works on pages that are based on components or modules.

Right, we're on the same page. I agree that in certain scenarios these decoupled event based systems make sense. However, Twitter's example app is an email client. That to me is not a use case I would typically envision as being free of coupling. Thus it's more work to maintain in the long run.


> I have never worked on an application where the broadcast model was more benefit than pain in the long run

Unless you're extremely new to the game I bet you have, if only in the sense that it's a very short hop from classic MVC design to this.


How can anyone build a serious front-end without proper keyboard support? Every single one of these new-fangled web UI kits is missing this important feature and if you're going to do it at all, it really does need to be a core consideration.

This is my biggest complaint about most web apps and the number one reason that I think web apps are perceived to be less powerful. Give me a break web devs! (Or, should I say browser makers?) Let's get serious already!!

(Also if we're talking browser manufacturers, I'd really, really like to see a completely separate abstraction for apps than documents. C'mon man!! We've been inventing the same thing for 20 years, let's get it right for once :)


Why not just use Angular?


Because not everyone wants to define application behavior in markup.


I might be misunderstanding you but Angular behaviour isn't defined in markup. The views are, as well they should be, but they're just representations of the model behind the scenes.


http://angularjs.org/#todo-html

This just does not jive with me. My template knows that submission should be handled by `addTodo()`? Or even worse, looping. I want all of my logic to live in code, not some abstract framework specific attributes that don't add much in the way of clarity (context switching between template and view just to follow the workflow, for example).

I use (and love) Backbone because the DOM is considered a pure presentation layer, nothing else. Any behavior, logic, whatever that the code controls is defined only in the code.


>My template knows that submission should be handled by `addTodo()`? Or even worse, looping.

Most template engines support loops. Would be very weird without that capability, really.

Also, you have to add hooks to buttons and the like somehow. There isn't much of a difference between adding a class like "js-addWhatever" or using Angular's in-your-face approach.


you have to add visual styles somehow too. you might as well add them right in the html attributes, along with the <font> tags and <center> tags. We'll have js-addToDo and class="white big helvetica" and call it a day.


The best thing you can do is to connect those parts by one semi-generic identifier. There is no actual difference between using a class, an id, a data-attribute, or a ng-* attribute as hook. If you want to rename it, you have to rename it. If you want to move things around, you have to move them around. There is no way around it.

Your template will also use names of properties, arrays, and objects directly. There also isn't a way around that.

At some point you have to connect your pieces. They can't float around in mid-air forever.

The presentational things you mentioned are different. We can separate those things more, we can use abstractions, and we can make it extremely reusable.


I think you missed my point. Unless you really think there's no difference between naming an html element, and giving it an inline named function handler.


There is no difference. There is one identifier which ties them together. If you change that identifier on one side, but not the other, it will stop working.

It doesn't matter if that identifier is a class or something else.

Well, if it's a class people may feel inclined to reuse that identifier on the CSS side, which is a bad thing, because now you've tied 3 parts together instead of just 2. If I use classes for this, I prefix them with "js-" to indicate that they are only meant for scripting purposes.


with performAcceptanceRoutine(), that cooresponds to an actual global javascript function that must exist, and specifies how (imperative) not what (declarative).

With a class "acceptButton" you are specifying what it is, semantically and declaratively, without reference to some specific procedure that will be carried out, if anything will be done at all. This frees up to a great degree the range of code that you can write to "activate" This html, and also, makes it so that the HTML is not bound to some specific code base, and it can be parsed in situations that explicitly ban javascript.


To each his own. I don't think it's that big of a deal either way.

You don't actually have to do it that way in angular if you don't want to. You can put it all in code instead. The framework specific attributes you're looking at aren't exactly framework specific, they're kinda application specific (just happens they ship with the framework).

You can write your own ones to move any of that stuff into code instead of markup. I personally think that loops should be in markup - I happen to prefer templating systems with less restrictions.


> It's agnostic to ... if you render your HTML on the client or the server

I love Angular, but if you are rendering server side HTML (either as partials, or bootstrapping a page) it is currently lacking.


Flight is rendered server side? I must have missed that.

Okay, in that case I can see a need here.


Flight doesn't care where things are rendered.

> It's agnostic to how requests are routed, which templating language you use or even if you render your HTML on the client or the server.


Because it will "just fit" with corresponding Bootstrap assets, I'm assuming... I don't know if it does that now, but I'm assuming both frameworks will eventually share the same conventions and opinions so that developers can use them in tandem without too many extra ateps


Both of the Bootstrap developers left Twitter a few months ago and AFAIK they are still the ones developing Bootstrap.

While it is possible that they are collaborating with the current-Twitter developers working on this thing, it doesn't seem like much of a sure thing.


Different purpose. This is for building reusable UI widget components.


Why not just use Ember?


There is a very nice thread on Quora comparing both Angula and Ember. One of Ember creators also participate with good insights

http://www.quora.com/Ember-js/Which-one-of-angular-js-and-em...


Why use anything when you could just hire a web developer?


Why not just use KnockoutJS?


You can end up with a hard to maintain code with knockout.js if you don't structure it well. I found https://github.com/danderson00/knockout.composite recently that seems to adopt a similar philosophy (pubsub) to what Flight does while keeping the benefit of knockout.


I've used this knockout extension to great success [1]. It's intuitive, it leverages knockout's internal pubsub, and fits in with the KO ethos. It can be used to synchronise observables across models (which is awesome as it's events without overhead), or as a general purpose event system.

Ryan is one of the core contributors to knockout, by the way, so you can be assured of its quality.

[1] http://www.knockmeout.net/2012/05/using-ko-native-pubsub.htm...


IMO the real question is why use KO when you know Angular? (except perhaps for the lib size)

Really, I was using KO and it was really nice at the time but since I've tried angularJS I don't see a reason to go back to KO.

Two way binding without ko.observable/ko.observableArray/ko.computed, just plain js objects is really a big win.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: