Wednesday, April 3, 2019

Don't Eat Webauthn, it's Made of HOBA's!

So I recently heard about a new W3C standard called Webauthn. From what I can tell it's very much in the spirit of our HOBA RFC (RFC 7340). Ours was more of a sketch of what could be, and my javascript implementation of one of the alternative ideas in the RFC was a truly horrible hack and I knew it, even if I did try to justify some of the sketchier aspects of it. But it was 2012 and the working group didn't even spin up till 2015, so we were clearly ahead of our time.

It works pretty much like my javascript implementation, but with much better crypto, credential storage, etc, etc. But it also goes on to specify interfaces to signing dongles which is good for what it's trying to do, but I'm seriously worried that people are going to think that webauthn *requires* portable keys, biometric, etc. That would be a shame because the real enemy is not passwords, per se, it's passwords that are transmitted over the wire. And of course, their reuse. A single strong local password which is never put on a wire is perfectly fine to gain access to credentials. That's pretty much what we do today with password managers, browser fill-in-the-blanks, etc.

The reason I think that's bad is because I worry that site owners doing their own authentication will view it as niche if they think it's required to have something other a local password. Why would Epicurious implement this if they thought it was just going to be the slice of people who have access to these things? Remember, not everything is done on phones so counting on, say, biometrics only works for some of the use cases. That would be a very bad result, as webauthn using a local credential store password is a perfectly fine thing to keep others out of my super secret recipe box.

One of the things I did with my part of HOBA is elaborated quite a bit on the enrollment problem. On the tutorial sites I found, nobody seemed to mention it. I can definitely understand why the standard itself considers that out of scope (it is), but it's a little odd that it's not getting talked about much that I've found because it's probably the hardest part of deploying webauthn server-side. Maybe my Google foo failed me, but dealing with enrollment always sucks. Since webauthn is device specific, it's going to take some guidance on how to implement it server side, and users getting used to a new routine. Fortunately on the user side, we've come a long way on enrollment with more and more sites validating that the use of a new device is ok. With webauthn, that would have to be universal.

All in all, I think this is great stuff and about time. The scourge of passwords on the wire has a real chance of going away in time as sites adopt it. Let's just not hitch it to getting rid of passwords altogether. All forms of credentials have their own set of issues and tradeoffs. The important part is keeping them off the wire.

Stir/Shaken Questions

I've been looking over STIR/SHAKEN stuff for dealing with Spam-o-SIP. There's a really nice problem statement (rfc 7340) which outlines all of the interactions with the legacy PSTN and its interactions with SIP gateways and session border controllers (SBC). The gist of the resulting set of RFC's presents a pretty complicated architecture with Policy Administrators (PA) (I assume something like the ITU or some other such creature) which does admission control of who is allowed to join the telephone number bearing club, delegated CA's to sign club member's certs, and then a sort of peculiar over-the-wire signing and verifying service within each provider. From my understanding this is all within a SIP network -- anything beyond a PSTN gateway is terra incognita if the gateway switch is relaying it from a source it does not control. But that's just a previously unsolved technical problem, I think, and the layer 8 solutions from the past may still be effective, re: Caller ID. Now I must fess up that I only recently heard about this, so my characterizations are likely to have flaws or be downright wrong, so take this with a grain of salt.

Plain Old SIP

My first question is whether this is intended to be a solution for plain old SIP URI's as well? If so, it's sort of worrisome to have his PA entity determining who can join the club for an all-IP service. Binding telephone numbers to particular carriers and making certain that the carrier is part of the telephone number club is a much different problem space than SIP-SIP with no telephone numbers involved. Assuming that is not intended to solve for that problem, is there a way to identify either the end user URI, or aggregated at provider boundaries on, say, a domain basis? With DKIM, we chose to allow mail providers to sign the mail with their domain -- whether or not they are the originating domain -- to say "blame me".  This gives the originating provider some incentive not to be blamed by requiring MUA authentication, etc.

RFC 7340 talks about SIP Identity (rfc 4474) and I can certainly understand the issue with B2BUA's which have analogs in the email universe (cf mailing lists). With DKIM we essentially punted and declared that the breaking gateway should sign the mail anew, and that they are now the one to blame. This seems to have worked out ok, as far I can tell. Dealing with spam is all about messy heuristics and I expect that SIP spam will be no different. The general idea is to be able get enough clues about bad actors as part of the full set of clues.

DKIM for SIP?

It has always seemed that a DKIM like approach would work for SIP as well. It has the nice property that it doesn't require a centralized CA. I'm too lazy to look up SIP identity, but I'm guessing that it does require them in some form. The web got lucky with TLS because there were only a very few browsers and the accepted CA root list was controlled by them before things could get out of control. With SIP, it's been around a long time and SIP Identity didn't get much traction from what I read, so I'm not sure if that's part of the problem, or is just academic since it just didn't get deployed.

DKIM for SIP would be fairly trivial to implement. In fact, I hacked together a SIP proxy that signed and verified SIP messages. And, of course, the infrastructure is all there as DKIM at this point is really old and really widely deployed. If SIP Identity doesn't get used for whatever reason, and we don't have a deployed solution for SIP-SIP calls, that seems like a really big hole. Fortunately, we have an existence proof with email which could be retrofitted pretty easily.

Is this Partly a UI Problem?

When I receive a call, my phone does the best it can to tell me who it is rather than giving me just a plain old telephone number. In fact, that's one of the best ways to determine whether it's spam is if you don't recognize the number. At this point, we are all very comfortable using email addresses as identities. So what I'm getting at is why doesn't my phone app take plain old SIP calls and treat them equivalently? In which case, it can use the From: address rather than some Paleolithic telephone number. There's nothing stopping Google or Apple from adding a new SIP protocol head onto their existing call apps, after all. This would at least break the notion that legacy telephone numbers are the only ID I should see when I get an incoming call. Hopefully the legacy PSTN calling will be winding down in the next 10 years or so, but we certainly don't want to carry along legacy telephone anachronisms into the future of an all SIP infrastructure. 

Signer and Verifier Services

This is more of a quibble, but I really don't understand why there needs to be a separate on-the-wire protocol for SIP proxies and authentication. With DKIM, the signing and verifying are done in the MTA, and it demonstrably works just fine. The CPU cost of signing these days is minuscule, and verification was always nothing. Plus, it costs time to send something over the wire and get a response, and with SIP that is a consideration. So I just don't get what motivated this.

Conclusion

While I think this looks like good work, I'm worried that it's putting a lot of time and energy into dealing with a legacy issue, and perhaps either not dealing with the SIP-SIP case, or dealing with it in a way that requires being invited to a club.  Either would be very bad. We really should be trying as hard as possible to make plain old SIP the way people expect to get a "phone call", and SIP itself needs to be able to have a workable solution that doesn't depend in any way on legacy PSTN stuff or we're going to be in the same boat a few years down the line.

Wednesday, March 29, 2017

Recursively Recursing the Burstorm App

Where I've Been

It's been 3 years since I've written to this blog, but I haven't been asleep again. In fact, I've been working furiously on an web app for a startup called Burstorm. The Burstorm app implements a CAD-like graphical interface for designing IT infrastructure, for both traditional datacenter providers, but most especially for the new-world cloud providers like AWS, Google, Azure, etc. The idea of the app is that you can specify in fairly abstract terms what your requirements are for some deployment (eg, I want 2 Linux boxes with 4 cores, 16GB ram, and 200GB SSD), and the app with provide you with insight about what your choices are to take your model and actually spin it up.

The app has lots and lots of goodies like collaborative editing, importing of cloud provider billing files, the ability to do traditional provider quoting, as well as an entire provider-centric side for creating and managing products both programatically as well as UI-driven.

This post is mainly about me eating my own dog food and telling the world that it was, in fact, tasty. So here's the story. About 3 months ago, I got word that we needed to move providers because a customer was insisting that we run in a ISO-27001 compliant datacenter, and our then-current provider wasn't compliant. When I stopped being annoyed, I decided to use this as opportunity to do something with our app that I'd wanted to do for a long time: use our app to model, build and deploy itself on other cloud providers. Happily, I for other reasons I had almost all of the pieces built in the app to do just that, and this provided the perfect excuse do the last bits and dregs of code so that I could recursively recurse the Bustorm App.

IT Lifecycle

The Burstorm app models the life-cycle of an IT project. 
  • Models: when you want to just create a new design of some infrastructure and figure out who can provide it where and for how much, we call that modeling. Here, you lay out what your requirements and constraints are for your infrastructure, and you can then use the Burstorm Designer to get recommendations of providers that meet your requirements, what the cost is, and for many cloud providers even get price/performance specs on their various VM's through our performance benchmarking initiative.
  • Builds: when you see a provider's offering in the Designer that looks interesting, you can create a Build from it. A Build in the Burstorm lifecycle is much more specific: it includes pricing from a particular location for a particular provider. And for cloud providers that we support, it even includes information about how your Build can be spun up.
  • Contracts: you can also tell the app about infrastructure that you already have deployed with its pricing information, contract terms, etc. The app can give you help to figure out when it might be better to jump ship to another provider, as well as lots of other insights. Additionally, you can copy your current infrastructure into a Model, and start the whole process over again to redesign your current infrastructure with various tweaks you might want to make.

Our App Setup

Enough background, onward! The Burstorm app is a fairly standard web-like app using Ruby-on-Rails, Postgres and Linux on a bunch of different servers: some that are dedicated like our PG master, some that have dual roles like our postgres-slave/webhead, and some that are just webheads. We also use hosted Chef  to provision all of our servers, using Chef roles to determine what pieces of config/packages need to go on what servers. 

"Burstorm App" Model

So in order to achieve our goal of getting the Burstorm app to clone itself, we start by creating a model in the Burstorm app. This illustration shows a simplified view of our infrastructure.

Fig 1. Development and App model

In it, you can see a Design Scenario called "Main App" in the Model which contains the new app. You'll also notice that there's a separate Design Scenario called "Devbox" -- this models a development environment we spin up for our devs. 

By clicking on the Design Scenario "Main App", you can see that we've set its constraints to be within 5000 miles of Dallas, with month-month terms, with flexibility about upfront NRC.




We can now inspect one of our servers. You can see for pgs (our Postgres Slave) that it is set up to be a 2-core, 4GB ram, 200GB storage VM.



Note also, that you can see bunch of tags that are associated with this Objective. In fact, those are the Chef roles that will be used to provision this server should we decide to spin it up!


Here you can see a complete list of provisioning tags which correspond to all of the Chef roles we use for all of our app infrastructure. 

"Main App" Designer

The Burstorm Designer allows you to drill down on a particular design scenario and get all kind of interesting information including costs for various providers and how/where they'd deliver it, price performance information, various charts that show you provider information, histograms, etc. For the Burstorm App cloning the Burstorm App, this is the meat: we're going to use the designer to figure out which providers we are interested in.



In this view, you can see that AWS, and Google look pretty favorable. If we drill down on the details, we can see for both AWS and Google what VM instances they would use to deliver our servers, and how they differ from what we asked for: Google's instances, for example, provide quite a bit more RAM than we asked for, so it's not just the base cost that needs to be considered.



Note also that we're comparing these by Price/Performance. That is the BCU column for each provider. What's cool is that we can change that to sort things by Price alone, CPU performance, IO performance, etc so that we can really get an idea of how these VM's compare on other axes. 

Now that we've figured it all out, we can now move to the next step by copying these solutions to a Build.



Building Our App

We've now created a set of Build Specs which we can use to communicate with the various providers. In this example, we've chosen Amazon and Google, but when I was working on this I actually spun up clones of our app on Azure, Linode, and Digital Ocean too.

asdf

We do need a little setup here to tell the app the credentials to use, how to log into our VM once it's spun up, what provisioning system to use (if any), etc. Here we're spinning up an AWS  t2.medium instance, using Chef to bootstrap the box with the 'benchmark' SSH key to login with.

Spin-Up

So now we're ready to spin up the Burstorm app from within the Burstorm app. Each server objective contains a Run Spec which tells the app all of the relevant parameters, like what the instance name is, the provider's location spec, ssh keys to use, and whether to run Chef after the instance is created. Here we will spin up our AWS Build Spec, and clone the entire setup.


What the app actually does now is hands off a JSON blob describing the Build and Run specs off to a remote web server. This would be running in your infrastructure. This implementation doesn't  require us to have access to your AWS keys since they'd be running on your webserver. Other arrangements could be envisioned though to make this more seamless. Either way is viable, and we're still thinking about what the best way to support this would be (if not both).

Our general strategy has been to define an API endpoint for the Burstorm App that allows programmatic access to our models, builds, product sets, etc. Our Perfrun repo takes that approach, and is the basis of the code to do the actual instance-creation/provisioning. Looking at the repo, you will see that it's just using our API like any other API consumer. In this case, it's creating VM instances, and optionally running our performance benchmark code on them.


If you squint real hard, you can see that we are doing a normal Chef bootstrap. The key is that the Spinner-upper takes the Chef roles we set up with the Burstorm tags above and uses them to provision the servers with each specific environment. This all happens in parallel, by the way, so it only take 5 minutes or so till it's complete.

Todo

There's *tons* more to do. We have the ability to import AWS and other provider's billing files, but it would be interesting to correlate them with what we spun up. It might be fun to map our utilization too against the actual work load. There's all kinds of places this can go, but this is, after all, just a prototype which is intended to spark those kinds of discussions.

C'est fini

Once this is done, we have 4 servers up and running. In the subclassed version I use of the Spinner-upper code, I actually have it communicate with our DNS provider to set up the A and AAAA records, and thus can directly access each server by name. In reality, there are a few more bits and pieces I had to do -- like loading the database -- but almost everything was handled by our Chef.

So there you have it, I used our Burstorm app to clone itself.  The actual process of doing this start to finish probably took an hour or so, if that -- way less time than it took to write this over-long post!

Friday, July 12, 2013

Interviews as Hazing Rituals

REVIEW: Film provides fresh take on Greek hazing abuses – The Daily Aztec
Aggro Interviews Making Up for Um...

I started this blog as a tongue in cheek kind of play on the fact that I'd been so out of the web world for so long that when I came back to look at the web, everything was so fresh and minty. Well, it turns out that that's not the only thing that has changed since I took my long snooze. I've been putting myself out again to look for a new job and I've been pretty shocked at how awful the interviewing process has become. It's become a hazing ritual straight out of your local frat house.

At some level this shouldn't be a big surprise because the entire industry -- at least what I'm seeing here in SF -- is being taken over by brogrammers. Call me old school, but I really don't want a keg on every floor and I don't see it as a feature that you use more space for ping pong tables and other toys than you do for space for your employees to work, assuming that they ever do that. And what's up with using 8' banquet tables with two people per table? It makes me want to look for severed fingers in my software because I'm surely looking at a sausage factory instead of software development.

The Greek System


But back to interviewing. One of the biggest changes nowadays is that everybody seems to be using keyword filtering software. This software doesn't appear to be especially bright as it just seems to do an intersection between the job requirements and the words that appear in your resume. If it isn't an exact intersection, you're rejected. It doesn't seem to discriminate between required, strong preference and nice to have either. If there isn't an exact match, then you're dropped. This seems especially prevalent at big companies where they force you to fill out an extensive questionnaire because, I guess, their filtering software isn't even smart enough to do the intersection function using your raw resume text. Worse, I've seen some that require you do that before you can see their job listings. I mean, seriously?

 

The net effect is that it's fairly useless to actually apply for a job because it never gets seen. Because, of course, there does not exist somebody who actually is an expert in the 99 things that you put on your listing as "requirements". So the unfortunately lesson here is that just dropping a resume off in the job inbox is a complete waste of time.


First, drink 10 beers


The biggest cancer -- and the subject of this rant -- are tests in the form of programming tests and puzzles. This is nothing more than a hazing ritual dressed up in all sorts of self-righteous excuses. The justification is that candidates might be lying about their programming prowess, thus it needs to be conclusively proven that you can write code on the spot. Never mind that this is extremely uncomfortable for the interviewee. Never mind that what is invariably given to be written you'd be fired for reinventing rather than just googling for the answer. Never mind the fact that most interviewers can't actually explain the problem they want you to solve. Never mind the fact that once you solve it, they want you "scale it up" whatever that means.

None of this matters because the point isn't to find out if you're a great programmer. The actual point is to convince the interviewers that they're great programmers. And superior to you in every way. Unless, I suppose, you look like them and have an affinity for drunken programming. The best part of this is when they tell you "we just want to see how you think". Call me crazy, but the only part of my thinking process you're going to understand after one of these tests is how good my rote memory is. I mean, when is the last time you've been asked to write a program to enumerate all permutations of a number? When is the last time you coded up a binary tree (no fair if you've been interviewing recently).

The absolutely worst are companies that use their hazing rituals -- like frats -- to prescreen pledges before they even a) talk to you and b) give you any clue whether you want to work in their sausage factory. This is abuse, pure and simple. And the larger joke is that it's been shown that it serves absolutely no purpose other than making the frat boys judge their penises large and manly. This google paper really drives this home, and I felt very vindicated.

We're not sure if you look like a Theta Tau


Every company has a "culture". Every company is completely convinced that their culture is the best. The interviewer's job is to make certain that the wrong element is not let into their restricted and exclusive frat ("exclusively what, and restricted to whom?"). You better be, well, top drawer just like Bunny Bixler and Muriel Puce.

Now there are cultural reasons why you may not be a good fit, like it being a stressful job where the interviewee is looking to destress (and vise versa!). But let's be real here for a second: every company says they only hire the "best and brightest" but not a one of them is located at Lake Wobegon. How, exactly, does that work? Or is "culture" a dog whistle for "looks-like-me"? And didn't Enron pretty much prove that hiring exclusively for "best and brightest" isn't a guarantee of success? Which isn't to say that trying to get good people should be downplayed -- not at all. It's just that looking into your sausage factory I have to assume there must be some third world country where everybody is white, male, 25, and dresses in skinny jeans to escape poverty by being exceptionally smart. At companies who do exceptionally amazing things like making sepia filters for .jpg's, and virtual row crops for e-farmers.

The PI-Thon Frat House


Computer languages are tools. Often complicated tools to be sure, but tools nonetheless. They do not describe your life essence. The ability to use them is not written into base pairs where some people inherit them, and some people don't. You do not need to give blood and do a PCR test to determine whether you are Java negative. If you're worth anything as a programmer you can and will pick up lots of languages over your lifetime. Some of them may actually be pretty awful and will make you apologize (hi PHP!). Some of them are more applicable to some problems than others.

The same thing goes for platforms and packages/frameworks. The current rage is to silo you based on whether you're iOS or Android, but it's no different than when they used to do that between M$ and Unix. Now true enough it takes time to learn a new language or platform or package/framework so it's a valid consideration to judge how much time a candidate might take to come up to speed. And true enough, there isn't much that you could do to convince me to waste my time learning .NET or some other piece of crap language/platform/package that I have no interest in learning. So, yes, thank you for putting down all of the buzz words so that I can filter too.

But where you go wrong is thinking that the lack of 7 years of mobile experience (seriously, I'm not making this up; edit: remember this is in 2013) means a priori that an otherwise competent person can't pick it up. In fact, part of the fun of a new job is getting exposure to new and different ways of approaching problems. Competent people break free of the frat they pledged to. Really.

Will nobody think of the code?


One of the most shocking things I've found interviewing is that -- except for the filters -- nobody seems to read your resume. And because they haven't read you resume, they obviously haven't seen anything online to validate your skill. The great thing about the net is that there are so many ways to show what you've done in the past, and whether it's a piece of art or a piece of crap. And since it's a given that the interviewer is smarter than Donald Knuth (even if they don't know who he is), he should be able to instantly assess whether your code is written well and whether the design choices were appropriate. Right? I've had exactly one interviewer ask me for code.

Now I understand that people lie, but it seems to me that using their own purported code to see how well they really understand what they claim they've written would be an immense help to ferret out the fakers. Much better than a real time rote memory test of coding up 2-3 tree or a lalr parser. Now this isn't failsafe because it can be perfectly legitimate for a person to not have an oeuvre that is publicly available. But to not use it as a resource when it's available? That's craziness. 

I'll pass on the spanking conga line


The Google HR guy's interview (and this one too) which says that the only thing that makes much of a difference in interviews are behavioral questions was quite a relief. I hadn't actually woken up in the alternate world where Spock and Kirk wear beards. Of course their revelation hasn't filtered out to the larger tech world, so there are still plenty of fucked companies with all of their interviewers who are looking for reassurance about their manhood. Oddly enough, it's the way I've always interviewed people too so it's doubly comforting that something I did by instinct was actually the only thing that seemingly works worth a damn. 

The last thing I'd like to say is this: instead of being paralyzed by making a mistake, do the obvious. If they can't program after you hire them, then... just fire them when you figure it out. It usually doesn't take too much time to find out somebody who interviewed well was a complete joke once on the job. And no, don't require they initially "consult" or whatever -- you're going to lose good people who view that as game playing. That and get rid of the kegs. Seriously.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Friday, May 10, 2013

MVC or Not?

With Phresheez, I've noticed a pattern that comes up over and over and over again given that most Phresheez pages are not web pages, per se, but long lasting apps that auto-refresh content dynamically. I've tried to reconcile this with the MVC (model/view/controller) pattern which is quite common, but I've never been able to tell whether the way I think of things is the same or even close to the MVC pattern.
I'm thinking that they have similarities, but that they're not really the same as MVC as far as I understand it.

So here's what I saw coming up all the time with Phresheez.

  1. There's a dynamic piece of data that needs to be fetched from the server
  2. There's a piece of HTML content I generate from that data
  3. That content needs to be refreshed as new data comes into the server
  4. I want to use that content in multiple places, but they may want to be formatted for different uses
Typically, I'd start out with a monolithic piece of code that did all of things, but I'd almost always regret it as I'd have to extend or refactor the code when I wanted to use some or all of the widget in a different context.

The rest of this post is what I'm thinking the architecture should be.

Goals


First off, I guess I should state what I want to provide.

  • A data abstraction layer that takes care of all of the grotty details of network errors, retransmissions, refreshing, etc, etc. Before refactoring, this code was ~duplicated all over the place each with its own subtle set of bugs. I want just one set of bugs.
  • A cleanish separation between content formatting and container decoration. By container decorations, I mean things like menus/nav, titles, last updated, etc. 
  • I want to be able to facilitate mixing and matching different widgets. That is, I want to be able get content from widget A, widget B and widget C and mix and match them into a new super widget D.
  • A recognition that there's some interplay between some of the aspects and not go crazy trying to ferret out every soi-disant layering violation

Data Layer


The Data Layer is in charge of getting and refreshing data between the client and the server. Other services subscribe to the data layer to get and refresh data from the server. The data layer may or may not auto-refresh depending on settings, but it will only refresh if something is actively subscribed to it.

The Data Layer should also have error callbacks for when something goes wrong. The Data Layer must never affect the DOM in any way: it is up to its subscribers to determine what is displayed.

There may be many Data Layer objects running around with different parameters to the same underlying service. For example, you might have an object that fetches the user information for user1 and another that fetches it for user2. When the Data Layer publishes the data, it must be specific to its instance.

As an optimization, however, it would be good for the Data Layer instances to know about each other such that an instance that has the same url parameters as another will share the server's results with other Data Layer objects so as not to generate useless duplicated calls to the server. I suppose that one way to accomplish that would be for them to subscribe to each other, or maybe a global data cache layer on top of the server callbacks, but that's an implementation detail.

Container Layer


The container layer is the business end of this model. It alone is responsible for inserting/updating elements within the DOM. The normal data flow is that the Container Layer subscribes to some set of data layers (or not!) and upon a published update calls the necessary content binding objects to populate the container (normally a div, but it could be anything's innerHTML really). I'm going to say that it's also its job to deal with layout and general look and feel for the container since a container may contain many objects. So it produces HTML as well, but it's much more oriented at layout than content per se.

The container layer is normally the recipient of the Data Layer's publications. It is responsible for painting the placeholders when the data is not yet available (eg, loading spinners, etc), and coordinating when it's interacting with multiple Data Layers. The Container Layer is also responsible for unsubscribing to the data layer when it has lost focus, for example, to cut down on useless chatter to the server. 

Finally, the Container Layer is responsible for implementing any callbacks (eg, onclick) that the container content requires. Which is another way of saying that the Container Layer owns the interaction with the DOM.

Content Binding Layer


The content binding layer binds data from the server to html layout and content. It never interacts with the  DOM. It merely takes the data, typically provided by the data layer, but not necessarily exclusively and grinds out the content for that data. The Content Binding layer should be thought of as effectively "stateless" even though for efficiency the Content Binding Layer ought to provide one other service: sameness. That is, if it produces the same content that the caller was returned the last time, it should inform the caller. This is needed to eliminate useless redraws at the container layer which are annoying. Note that if the content layer needs to do extensive massaging of the dynamic data, it is not a layering violation to hold onto the dynamic data (or a massaged form of it) if it wants to do delta's of old and new datasets, etc. 

It could be argued that the sameness property ought to be up to the Container Layer, but I don't think so. The Container Layer is the one that knows which bits and piece of dynamic content its using, so it alone is the one that knows if they are the same, modulo comparing blobs of HTML at the Container Layer, which I think is bad practice.

Clicks and other Events


It's not entirely clear to me who should be generating the onclick='s (typically). In the MVC pattern, I'm pretty sure that's the job of the controller, which would be more or less equivalent to my Container Layer. But that would be rather messy in practice: if you're just producing a bunch of html in the Content Binding Layer, it's much easier to insert the onclick's inline with the generated HTML. The downside here is that at the very least the onclicks need to be parameterized with the instance they represent from the Container Layer (ie, this click is associated with this container instance). And of course, the click actions across different container layers may well be very different.

What I think is probably the best compromise is let the Content Binding Layer generate the onclicks, but with input from the Container Layer's state. This still runs afoul if you want something like

<a href=# onclick="st.mywidget.clickHandler('mike')">Click me!</a>

since it is the container layer's job to handle that click, so why should the Content Binding Layer be setting up its callback parameters? Suboptimal, but in practice it haven't noticed many instances where I have one use with a set of parameters and in another use with a different set of parameters... they almost always follow a similar pattern, though you may end up with the union of parameters the various uses require.

My guess is that not stressing about this fuzziness and the apparent layering violations is best. The object here is to get better code reuse and separation of function, but that doesn't mean that we have to solve world hunger as well.

Inserts/Updates


For insert/update type callbacks to the server, I think I would keep it completely as a property of the Container Layer. This is the flip side of the argument about clicks in the previous section: the Content Binding Layer is the one that generates the input's, checkboxes, etc, so why shouldn't it provide the interface to fetch their values? I dunno, because it's sort of fuzzy in the same way that clicks are fuzzy and it would probably be messy to have a grand unified theory here. So some give and take between the Container Layer and Content Binding Layer seems reasonable.

Likewise, I don't see any particular advantage to tasking the Data Layer with the ajax callbacks to the server to save/update state. The point of the Data Layer is to deal with multiple users of data, and keeping it refreshed. It doesn't need to be the singular owner of HttpXMLRequest as well.

So is this MVC?


So have I reinvented MVC or not? Maybe it is, or maybe I've extended it a little, or something. Or maybe it's all different. You tell me. Here's my take on what I've done vs. MVC:



Thursday, May 2, 2013

Something old, something new. Something borrowed...

So I have been searching for a way to do a color picker that works ok on mobile phones. Most of the color pickers out there expect that a) mouse over works and b) that your finger is a mouse pointer. Fails on both accounts. So I found a rustic html color picker at w3schools. It uses a gif for the background, and an imagemap which is very, very old school these days. The code to do it is pretty big and definitely ugly. So I decided to do run with it and make it all fashion forward 'n stuff.

The image map contains the coordinates of the polygons for all of the hexagons in the gif. So why not just use a canvas element and render it instead of snarfing up a big png/gif? So I grabbed their source, and a couple of emacs keyboard macros transmogrified the area tags and the corresponding color into a nice compact javascript array. From there it was easy: loop through the array to draw the hexagons, and in parallel create the html for the area tags. To get the image, you just call toDataURL('image/png') on the canvas, and it can be stuffed into an <img> tag.

Their picker took the selected color and sent it back to a server to show different shades of the base color. Yuck. Instead of that, I just converted the colors to hsv and created a set of variations by changing the s and v values. Simple, easy, and most importantly relatively easy to pick with fat fingers. Does it give you the ability to pick infinitely variations on a hue/saturation/luminance? No, but a) I didn't need that, and b) it would be very unwieldy for... fat fingers. Again.

The source and docs are available on google code.


Here's the source:


/*
 *   Copyright (c) May  2 06:46:44  MTCC
 * Author: Michael Thomas
 * Module: colormap.js
 * Created: Thu May  2 06:46:44 2013
 * Abstract:
 *      License: MIT
 *    color mapper
 */

/* Edit History: 
 */


function colormap (prefix, w) {
    this.prefix = prefix;
    this.can = document.createElement ('canvas');
    this.can.width = 234;   // original image dimensions 
    this.can.height = 199; 
    if (w)
 this.scale = w/this.can.width;
    else
 this.scale = 1;
    this.can.width *= this.scale;
    this.can.height *= this.scale;
}

colormap.prototype.render = function (color) {
    var ctx = this.can.getContext ('2d');    
    ctx.scale (this.scale, this.scale);
    html = '';
    html += '<map name="'+this.prefix+'.map">';
    for (var i in this.map) {
 var slot = this.map [i];
 html += '<area shape=poly onclick="'+this.prefix+'.select (\''+slot [1]+'\')" coords="';
 ctx.beginPath ();
 for (var j = 0;  j < slot[0].length; j += 2) {
     html += slot [0][j]*this.scale + ',';
     html += slot [0][j+1]*this.scale;
     if (j < slot [0].length-1)
  html += ',';
     if (j == 0)
  ctx.moveTo (slot [0][j], slot [0][j+1]);
     else
  ctx.lineTo (slot [0][j], slot [0][j+1]);      
 }
 ctx.lineTo (slot [0][0], slot [0][1]);      
 html += '"/>';
 ctx.closePath ();
 ctx.fillStyle = slot [1];
 ctx.fill ();
    }    
    html += '</map>';    
    html += x = '<br><div id="'+this.prefix+'.shades">'+this.shades (color)+'</div>';
    return {html:html, img: '<img src="'+this.can.toDataURL ('image/png')+'" usemap="#'+this.prefix+'.map">'};
};

colormap.prototype.select = function (color) {
    if (this.onselect)
 this.onselect (color);
    var el = document.getElementById (this.prefix+'.shades');
    reliableNewc (el, this.shades (color));
};

colormap.prototype.shades = function (color) {
    var rgb = new RGBColor (color);
    var hsv = rgb2hsv (rgb.r, rgb.g, rgb.b);
    var html = '';
    html += '<table align=center><tr>';
    for (var i = 0; i < 256; i += 16) {
 if (i == 128)
     html += '<tr>';
 if (i < 128)
     hsv.v = i;
 else
     hsv.s = i-128;
 var rgb2 = hsv2rgb (hsv.h, hsv.s, hsv.v);
 html += '<td><div onclick="'+this.prefix+'.select(\''+rgb2+'\')" style="position:relative; height:30px;width:30px; background:'+rgb2+'; border: 1px solid black"></div>';
    }
    html += '</table>';
    return html;
};

// scraped from the source of http://www.w3schools.com/tags/ref_colorpicker.asp

colormap.prototype.map = [
    [[171,180,180,184,180,195,171,199,162,195,162,184], '#993333'],
    [[153,180,162,184,162,195,153,199,144,195,144,184], '#800000'],
    [[135,180,144,184,144,195,135,199,126,195,126,184], '#990000'],
    [[117,180,126,184,126,195,117,199,108,195,108,184], '#993300'],
    [[99,180,108,184,108,195,99,199,90,195,90,184], '#CC3300'],
    [[81,180,90,184,90,195,81,199,72,195,72,184], '#996600'],
    [[63,180,72,184,72,195,63,199,54,195,54,184],'#663300'],
    [[180,165,189,169,189,180,180,184,171,180,171,169],'#990033'],
    [[162,165,171,169,171,180,162,184,153,180,153,169],'#CC0000'],
    [[144,165,153,169,153,180,144,184,135,180,135,169],'#FF0000'],
    [[126,165,135,169,135,180,126,184,117,180,117,169],'#FF3300'],
    [[108,165,117,169,117,180,108,184,99,180,99,169],'#CC6600'],
    [[90,165,99,169,99,180,90,184,81,180,81,169],'#FF9900'],
    [[72,165,81,169,81,180,72,184,63,180,63,169],'#CC9900'],
    [[54,165,63,169,63,180,54,184,45,180,45,169],'#996633'],
    [[189,150,198,154,198,165,189,169,180,165,180,154],'#660033'],
    [[171,150,180,154,180,165,171,169,162,165,162,154],'#CC0066'],
    [[153,150,162,154,162,165,153,169,144,165,144,154],'#FF5050'],
    [[135,150,144,154,144,165,135,169,126,165,126,154],'#FF6600'],
    [[117,150,126,154,126,165,117,169,108,165,108,154],'#FF9933'],
    [[99,150,108,154,108,165,99,169,90,165,90,154],'#FFCC00'],
    [[81,150,90,154,90,165,81,169,72,165,72,154],'#FFFF00'],
    [[63,150,72,154,72,165,63,169,54,165,54,154],'#CCCC00'],
    [[45,150,54,154,54,165,45,169,36,165,36,154],'#999966'],
    [[198,135,207,139,207,150,198,154,189,150,189,139],'#993366'],
    [[180,135,189,139,189,150,180,154,171,150,171,139],'#CC6699'],
    [[162,135,171,139,171,150,162,154,153,150,153,139],'#FF0066'],
    [[144,135,153,139,153,150,144,154,135,150,135,139],'#FF6666'],
    [[126,135,135,139,135,150,126,154,117,150,117,139],'#FF9966'],
    [[108,135,117,139,117,150,108,154,99,150,99,139],'#FFCC66'],
    [[90,135,99,139,99,150,90,154,81,150,81,139],'#FFFF66'],
    [[72,135,81,139,81,150,72,154,63,150,63,139],'#CCFF33'],
    [[54,135,63,139,63,150,54,154,45,150,45,139],'#99CC00'],
    [[36,135,45,139,45,150,36,154,27,150,27,139],'#666633'],
    [[207,120,216,124,216,135,207,139,198,135,198,124],'#990099'],
    [[189,120,198,124,198,135,189,139,180,135,180,124],'#CC3399'],
    [[171,120,180,124,180,135,171,139,162,135,162,124],'#FF3399'],
    [[153,120,162,124,162,135,153,139,144,135,144,124],'#FF6699'],
    [[135,120,144,124,144,135,135,139,126,135,126,124],'#FF9999'],
    [[117,120,126,124,126,135,117,139,108,135,108,124],'#FFCC99'],
    [[99,120,108,124,108,135,99,139,90,135,90,124],'#FFFF99'],
    [[81,120,90,124,90,135,81,139,72,135,72,124],'#CCFF66'],
    [[63,120,72,124,72,135,63,139,54,135,54,124],'#99FF33'],
    [[45,120,54,124,54,135,45,139,36,135,36,124],'#669900'],
    [[27,120,36,124,36,135,27,139,18,135,18,124],'#333300'],
    [[216,105,225,109,225,120,216,124,207,120,207,109],'#993399'],
    [[198,105,207,109,207,120,198,124,189,120,189,109],'#CC0099'],
    [[180,105,189,109,189,120,180,124,171,120,171,109],'#FF33CC'],
    [[162,105,171,109,171,120,162,124,153,120,153,109],'#FF66CC'],
    [[144,105,153,109,153,120,144,124,135,120,135,109],'#FF99CC'],
    [[126,105,135,109,135,120,126,124,117,120,117,109],'#FFCCCC'],
    [[108,105,117,109,117,120,108,124,99,120,99,109],'#FFFFCC'],
    [[90,105,99,109,99,120,90,124,81,120,81,109],'#CCFF99'],
    [[72,105,81,109,81,120,72,124,63,120,63,109],'#99FF66'],
    [[54,105,63,109,63,120,54,124,45,120,45,109],'#66FF33'],
    [[36,105,45,109,45,120,36,124,27,120,27,109],'#009900'],
    [[18,105,27,109,27,120,18,124,9,120,9,109],'#336600'],
    [[225,90,234,94,234,105,225,109,216,105,216,94],'#660066'],
    [[207,90,216,94,216,105,207,109,198,105,198,94],'#CC00CC'],
    [[189,90,198,94,198,105,189,109,180,105,180,94],'#FF00FF'],
    [[171,90,180,94,180,105,171,109,162,105,162,94],'#FF66FF'],
    [[153,90,162,94,162,105,153,109,144,105,144,94],'#FF99FF'],
    [[135,90,144,94,144,105,135,109,126,105,126,94],'#FFCCFF'],
    [[117,90,126,94,126,105,117,109,108,105,108,94],'#FFFFFF'],
    [[99,90,108,94,108,105,99,109,90,105,90,94],'#CCFFCC'],
    [[81,90,90,94,90,105,81,109,72,105,72,94],'#99FF99'],
    [[63,90,72,94,72,105,63,109,54,105,54,94],'#66FF66'],
    [[45,90,54,94,54,105,45,109,36,105,36,94],'#33CC33'],
    [[27,90,36,94,36,105,27,109,18,105,18,94],'#009933'],
    [[9,90,18,94,18,105,9,109,0,105,0,94],'#003300'],
    [[216,75,225,79,225,90,216,94,207,90,207,79],'#9900CC'],
    [[198,75,207,79,207,90,198,94,189,90,189,79],'#CC00FF'],
    [[180,75,189,79,189,90,180,94,171,90,171,79],'#CC33FF'],
    [[162,75,171,79,171,90,162,94,153,90,153,79],'#CC66FF'],
    [[144,75,153,79,153,90,144,94,135,90,135,79],'#CC99FF'],
    [[126,75,135,79,135,90,126,94,117,90,117,79],'#CCCCFF'],
    [[108,75,117,79,117,90,108,94,99,90,99,79],'#CCFFFF'],
    [[90,75,99,79,99,90,90,94,81,90,81,79],'#99FFCC'],
    [[72,75,81,79,81,90,72,94,63,90,63,79],'#66FF99'],
    [[54,75,63,79,63,90,54,94,45,90,45,79],'#00FF00'],
    [[36,75,45,79,45,90,36,94,27,90,27,79],'#00CC00'],
    [[18,75,27,79,27,90,18,94,9,90,9,79],'#006600'],
    [[207,60,216,64,216,75,207,79,198,75,198,64],'#9900FF'],
    [[189,60,198,64,198,75,189,79,180,75,180,64],'#9933FF'],
    [[171,60,180,64,180,75,171,79,162,75,162,64],'#9966FF'],
    [[153,60,162,64,162,75,153,79,144,75,144,64],'#9999FF'],
    [[135,60,144,64,144,75,135,79,126,75,126,64],'#99CCFF'],
    [[117,60,126,64,126,75,117,79,108,75,108,64],'#66CCFF'],
    [[99,60,108,64,108,75,99,79,90,75,90,64],'#66FFFF'],
    [[81,60,90,64,90,75,81,79,72,75,72,64],'#66FFCC'],
    [[63,60,72,64,72,75,63,79,54,75,54,64],'#00FF99'],
    [[45,60,54,64,54,75,45,79,36,75,36,64],'#00CC66'],
    [[27,60,36,64,36,75,27,79,18,75,18,64],'#339933'],
    [[198,45,207,49,207,60,198,64,189,60,189,49],'#6600CC'],
    [[180,45,189,49,189,60,180,64,171,60,171,49],'#6600FF'],
    [[162,45,171,49,171,60,162,64,153,60,153,49],'#6666FF'],
    [[144,45,153,49,153,60,144,64,135,60,135,49],'#6699FF'],
    [[126,45,135,49,135,60,126,64,117,60,117,49],'#3399FF'],
    [[108,45,117,49,117,60,108,64,99,60,99,49],'#33CCFF'],
    [[90,45,99,49,99,60,90,64,81,60,81,49],'#00FFFF'],
    [[72,45,81,49,81,60,72,64,63,60,63,49],'#00FFCC'],
    [[54,45,63,49,63,60,54,64,45,60,45,49],'#00CC99'],
    [[36,45,45,49,45,60,36,64,27,60,27,49],'#339966'],
    [[189,30,198,34,198,45,189,49,180,45,180,34],'#666699'],
    [[171,30,180,34,180,45,171,49,162,45,162,34],'#3333CC'],
    [[153,30,162,34,162,45,153,49,144,45,144,34],'#3366FF'],
    [[135,30,144,34,144,45,135,49,126,45,126,34],'#0066FF'],
    [[117,30,126,34,126,45,117,49,108,45,108,34],'#0099FF'],
    [[99,30,108,34,108,45,99,49,90,45,90,34],'#00CCFF'],
    [[81,30,90,34,90,45,81,49,72,45,72,34],'#33CCCC'],
    [[63,30,72,34,72,45,63,49,54,45,54,34],'#009999'],
    [[45,30,54,34,54,45,45,49,36,45,36,34],'#669999'],
    [[180,15,189,19,189,30,180,34,171,30,171,19],'#333399'],
    [[162,15,171,19,171,30,162,34,153,30,153,19],'#3333FF'],
    [[144,15,153,19,153,30,144,34,135,30,135,19],'#0000FF'],
    [[126,15,135,19,135,30,126,34,117,30,117,19],'#0033CC'],
    [[108,15,117,19,117,30,108,34,99,30,99,19],'#0066CC'],
    [[90,15,99,19,99,30,90,34,81,30,81,19],'#0099CC'],
    [[72,15,81,19,81,30,72,34,63,30,63,19],'#006699'],
    [[54,15,63,19,63,30,54,34,45,30,45,19],'#006666'],
    [[171,0,180,4,180,15,171,19,162,15,162,4],'#000066'],
    [[153,0,162,4,162,15,153,19,144,15,144,4],'#0000CC'],
    [[135,0,144,4,144,15,135,19,126,15,126,4],'#000099'],
    [[117,0,126,4,126,15,117,19,108,15,108,4],'#003399'],
    [[99,0,108,4,108,15,99,19,90,15,90,4],'#3366CC'],
    [[81,0,90,4,90,15,81,19,72,15,72,4],'#336699'],
    [[63,0,72,4,72,15,63,19,54,15,54,4],'#003366']
    ];

Friday, April 5, 2013

Web Look and Feel, Part 2 -- Does Native look actually matter?

In my previous post, I explored how I think we got to where we are right now with App and Web look and feel. With the Web, there really isn't any standard look and feel (still), but with Apps the dominate ones are iPhone and Android. Phresheez took a lot of lumps -- especially very early on -- for not looking like a "native" app. So the question I posed last post is that if I had a pile of cash to redo Phresheez, say, to make it look like a "native" iPhone/Android app would I do so (even if I used still web technology)?

The answer may seem surprising but I think that my answer would be "no". I have two different axis that are somewhat contradictory from which I draw that conclusion: 1) I'm not convinced that for the average user there is any meaningful "native" look and feel and 2) there are potential forces at work that could unify web look and feel, or at least provide a better base level set of similar look and feel, making any platform specific look and feel largely irrelevant.

What Look and Feel are We Talking About?

When I consider what people mean when they talk about native look and feel, there are a number of different things to consider:
  • Does the app navigate like a modern phone app these days?
  • Does it take into account that fingers are gigantic?
  • Does it take into account that people hate typing?
  • Does it follow the expected for zooming and  panning?
  • Does the app fit the form factor instead of relying on all kinds of panning and zooming (ala unreconstructed web sites)?
  • Does it count on mouse-overs to function properly (oops!)
  • Etc, etc
None of these are specific to a given platform. They are all about making the app work in the given form factor and while that is still somewhat in flux, there's far more agreement about what "working within the form factor" is, than argument. Take swiping. You swipe to go forward/back in a related sequence. Be it dates, pictures, etc. Zooming is done by pinching. And so on. 

So what do the native bigots actually mean then when they say it "doesn't look like an iPhone app"? One has to assume that it's the set of native widgets that the app uses. What else could it be? 

Heretics and Heresies

My feeling is that people on phones these days use their phone mainly for a few tasks:

  1. Social networks -- Facebook, Twitter
  2. Messaging -- email/texting
  3. Games/Vids
  4. Tracking themselves skiing (<-- kidding)
Let's take Facebook and Twitter, here are their screen shots in the Android store:



In both of these cases, I've put up the iPhone and Android versions. Can you tell which is which? There are minor differences to be sure, but it's not clear they have anything to do with the much vaunted "iPhone" or "Android" look and feel. More like the different dev teams just disagreed. These are hugely popular apps and from what I can tell nobody ever says "that doesn't look like my phone's app!!!". They just get used to it.

Built in Messaging Apps

So with built in apps, you'd expect that they'd be paragons of their respective "Look and Feel", right? Well take the Texting apps on both. For the most part they're identical -- they both use the comic speech bubbles, neither of which to my knowledge are native widgets. Apple's uses Aqua-like gradients on their bubbles more, but there isn't really anything beyond that. The inboxes seem to both be standard-esque listviews, but they're both very utilitarian. I find it difficult to believe that people fawn over the inboxes. So it there much here that showcases platform specific look and feel? Not in my opinion.

Mail on iPhone looks a little more standard with their back button paging (their navigation controller iirc). But it too is pretty utilitarian: a mail program is a mail program is a mail program. Nor does Android win any beauty contests either. Maybe some people really do stress about the minutia of differences between the two, but then again there's a Stress Betty for just about everything. For the most part, I can't see what the big deal is, unless you're a bigot of some variety. Note I'm only talking about the look and feel here, not functionality. I'm very willing to believe that there are non-bigoted reasons to prefer one platform's native this to the other platform's, but I expect that's functionality/nav much more than look and feel you can attribute to being on a given native platform and using its look and feel.

Games

Is there any native look and feel across gaming whatsoever? Take for example Words with Friends:


Is there any appreciable difference that you can attribute to the particular platform's look and feel? They look pretty much the same to me. They play pretty much the same too. 

I expect that's true of just about any game out there. And the same goes with viewing videos: the look and feel is the payload itself, not anything that the native platform brings to the experience.

[note: I'm most emphatically not implying a web platform is a viable candidate for gaming, just saying that  native look and feel for a major use case for phones is a no-op]

Back to Web Look and Feel

Now let's be clear, Facebook, Twitter, and Words with Friends are huge and create their own wind. But their existence pretty much makes a lie of the notion that it's some platform specific look and feel that is giving them a boost in the minds of their users. I doubt it even enters their users minds at all. To be sure, all of these guys have big budgets to throw at their look, feel, and interaction, but it's clear that a credibly designed app will be accepted by users regardless of whether it conforms to a particular native platform toolkit's look or not.

So what are the implications for using WebViews in apps and/or mobile sites? The high level bit from my standpoint is that it matters far more that the app is designed well for the form factor than any particular look and feel which is typically a secondary aspect of what a user sees with most apps (ie, date pickers, say). 
The nav has to work properly, the buttons have to be big enough, etc, etc. In the early days, that was far more important as people were groping around in the dark trying to figure out what constituted "well designed" for the phone form factor. The Native toolkits definitely gave devs a leg up on that account. But I'd say that was far more of getting both developers and users to come to gradual agreement on what constituted "obvious" and "intuitive" (neither of which happen in the real world, IMO). When they did that, it ceased being particularly important what the bling looks like so long as the bling is pretty.

That said, there still is the bootstrap problem for new apps. Eventually successful apps have their own look and feel and move away from the boring native widgets, but it's useful to have them at your disposal in the beginning. With no budget. Boring is better than outright ugly, after all. And that's the big drawback of the web approach: there isn't a base level widget library to fall back onto that looks familiar and comforting, if a bit stodgy. So you have to suffer through designing all of that crap yourself, and for non-UI kind of people it's probably going to look not so great.

Enter Firefox OS

Think what you will of the concept of booting Gecko on a phone, but there is a potential high level bit that people might be missing if they dismissively predict its uselessness: FFOS too has to provide a "native" widget toolkit to be viable. That is, they need to have high level constructs like date pickers, selector boxes, listviews, modal dialogs, tabs, etc, etc. in order to compete with iPhone and Android. At this time, it doesn't look (from the outside) like they've got much fleshed out, but they do seem to understand that this a necessary baseline that must be provided. It's a little early to tell whether it will be "beautiful", but the high level bit here is that it will exist. And it will exist using regular old -- and standards based -- web technology. (it's really too bad that Palm/WebOS croaked, it too could have provided the same). 

This is potentially very important if they pull it off. Since it's just a bag of js, html and css it can trivially be ported to non-Gecko OS things like, oh say, iPhone and Android webviews. Or to mobile websites. Or even to regular old websites for that matter. Here finally we potentially get back to a solution for Rip Van Webble's expectation that there be widget libraries for the web. And since the widget toolkit effort is its own ends (rather than the usual means to an end for website developers), Moz devs can actually take the same pride and ownership that Apple and Android widget developers take with their respective toolkits. That's important, because then there is incentive to get to parity with what native libraries can do, but using web technology which has huge code reuse advantages for app developers.

Conclusion

I'm really kind of hopeful about this. I think that web technology has been given a bad rap that isn't really its fault per se. It's a victim of the atmospherics and reality distortion fields for the most part, and was abandoned for app/mobile development just as it became a credible player. But so goes fads, and the rush to Native is mainly another fad, in my opinion. Don't get me wrong: I'm glad that Native shook things up, and we'd be much poorer for the experience if we had to wait for W3C to provide everything that Native app api's provide today, but look and feel is something that ought not be one of the reasons that you decide to develop a native app. 

This is why I'd still not use a native app look/feel and their associated native toolkits, even if I had a pile of money to do so. Maintaining two different code bases suck regardless of how much money you have to throw at the problem. Minimizing the differences and reusing code to the degree possible is still a very worthwhile goal. The biggest problem with the Web UI approach is the bootstrap problem given the lack of widget toolkits for your average schmuck web developer. But given FFOS -- and eventually a lot of toolkit imitators if successful -- there is hope that that advantage of Native will be going away. Given that most people don't notice or care that major apps they use day-to-day have little resemblance to a platform specific native app, it shows that native look and feel isn't a disqualifer, per se. The app working well for the form factor is far, far more important. Which, hopefully, emerging web based widget toolkits will give web-centric developers a leg up on.