Project launch: Prosper Marketplace’s retail investor experience

I’m very glad to share that the project I’ve been working on for months finally launched. Even in turmoil, the team overcame all obstacles and launched this projects. Credit to everyone who worked on this together (past or present). You know who you are.

Significance of this project:

When I first joined Prosper Marketplace, they had a low-volume consumer product. The old site consisted of a series of incoherent pages, cluttered with an overflow of information and functionalities designed for sophisticated investors – desktop only.

The new experience is simplified, visually appealing, mobile friendly and allows investors to invest in only a few clicks.

The Dashboard:
Prosper Account Overview

The Auto Invest page:

Prosper Select Criteria

Prosper Marketplace Investor Dashboard in mobile

Prosper Marketplace Investor Dashboard in mobile

Leading the engineering effort was no mean feat. I’ll write more on what we did well in the next post.

To learn more about this project officially, click here.

Fake agile: Get out of the rituals

You were told in your job interview they practice agile development. You ask a follow-up question to learn more. Then, they clarified they used their custom “brand” of agile – a hybrid of sort. You signed the offer anyway. Soon after your start you realized they had all the rituals of agile but everything felt wrong and pointless. Sound familiar?

I’ve been there. It’s easy to become a stagnant agile organization. Here are some pointers to get better at agile.

1. Be agilists

The most successful agile organization is one where everyone is on the same page about what agile is. Build an on-boarding of their agile practices before employees start. Form an agile forums to advance the organizations’ agile practices.

2. Listen

Feedback can come from anywhere – customers, retrospectives, sprint reviews, demos, stand-ups, day-to-day conversations. Collect them and address them in your sprints. There is constant self-improvement in agile – as an individual, as a team, and as an organization.

3. Ship it. Implement it.

You should always ship code at the end of every sprint. Keep the momentum. Break down big projects into smaller shippable chunks. Improve the technology to support it – e.g. release beta versions, use feature flag, improve continuous delivery and automation. Address process improvements with the same “ship it” mindset. This is important because shipping things enable customers and users to give you more feedback to further improve.

4. No jerks. No turf wars.

Treat each other equally and with respect. Everyone can suggest and make changes – regardless of how big or small the changes are. All responsibilities are task-based, not role-based. Everyone can lead, prioritize, design or code. This way, everyone is equally accountable for the output of the team.

5. Focused Meetings

Minimize the number of meetings. Understand why you need any of them, including scrum meetings. If the meetings do not help your team achieve your sprint or team goals, you should cancel them. This is why some companies (like Spotify) suggest all scrum meetings are optional.

6. Build team identity

Your team should have a team goal. It is an internal compass for the team members and helps them understand how to prioritize feedback by themselves. Name your team according to the team goal. Pick a name that anyone outside the company would understand.  With an identity, your team members will be more likely to work together as a team.

7. Measure it

Use various metrics to measure your team’s success. Estimate all known work and plan for the unknown using capacity allocation. Besides measuring team velocity, pick business metrics to measure how your team is achieving the team goal. Make these metrics accessible to the team.

[Site Launch] The new face of Sony – a responsive website

After years of hard work, I’m happy that our project has *finally* launched. Sony is the first electronics giant to launch a global multi-lingual fully responsive website. This launch covers all of Europe, Turkey and Russia/CIS. As the front-end lead for the project, I’m especially thrilled because a good front-end stack is the most important part of a responsive website. Thanks to 2 vendors and my kick-ass development team who worked tirelessly to perfect this brand new front end framework.

 

So, what’s really great about this website? Here are some important achievements from my perspective:

  1. Immersive customer experience
  2. Responsive design, responsive images, responsive everything…
  3. Separated presentation layer
  4. Modular front end framework
  5. Ability to develop on a single platform from ideation to integration
  6. Test-driven and agile

Check out the Sony UK site. Enjoy!

Page build: Sony Skype Camera (CMU-BR100) and Javascript framework agnostic adapter

I helped with this page for Sony GRO team for syndication to different countries. So far I found it on US and Canada. It may be live on other parts of the world.

One of the challenge was to make sure the JavaScript code (tho minimal) works on different frameworks. I wrote a “Agnostic.js” bridge to easily switch between prototype and jquery. I made it intentionally “dumb” so the developer can stub it out for their own framework if needed. It needs to be improved because it was a rush job. I think it works pretty well with jquery and prototype now. I wonder if there’s a place for such a library – Javascript Framework Agnostic adapter. Or it could just be polishing on turd.

 

Page on:

Just for fun – Add Konami Command (secret code) for your site.

Wrote something extremely stupid – a Konami secret code handler.  You can try it on my site. http://www.jonycmusic.com/ (Up-Up-Down-Down-Left-Right-Left-Right-B-A)

Code source: http://jsfiddle.net/jonyc/8XSaS/4/

/*
*  
*  AUTHOR: Jonathan Cheung
*  KonamiCodeTrigger.js
*
*/
var KonamiCodeTrigger = (function() {
    // Saves the returned API
    var self;
    // Key History
    var keyCodeHistory = [];
    // All the triggers are stored in an Array;
    var __triggers = [];
    // Constants
    var Key = {
        UP: 38,
        DOWN: 40,
        LEFT: 37,
        RIGHT: 39,
        ESCAPE: 27,
        a: 97,
        b: 98,
        A: 65,
        B: 66
    };

    // Here we store the winning Combinations..
    // The default is Up-Up-Down-Down-Left-Right-Left-Right-B-A / Up-Up-Down-Down-Left-Right-Left-Right-b-a
    var winningCombos = [
        String("" + Key.UP + Key.UP + Key.DOWN + Key.DOWN + Key.LEFT + Key.RIGHT + Key.LEFT + Key.RIGHT + Key.b + Key.a),
        String("" + Key.UP + Key.UP + Key.DOWN + Key.DOWN + Key.LEFT + Key.RIGHT + Key.LEFT + Key.RIGHT + Key.B + Key.A)
        ];

    //Let's save the original
    var originalKeyPress;
    if (document && document.onkeypress) {
        originalKeyPress = document.onkeypress;
    }

    document.onkeypress = function(evt) {
        var keyCode = Number(evt.keyCode);
        var charCode = Number(evt.charCode);
        
        //Process keyCode
        switch (keyCode) {
        case 0:
            break;
        case Key.ESCAPE:
            //Reset the history if ESC is pressed
            keyCodeHistory = [];
            window.scrollTo(0, 0);
            break;
        default:
            keyCodeHistory.push(Number(evt.keyCode));
            break;
        }
        //Process charCode
        switch (charCode) {
        case 0:
            break;
        default:
            keyCodeHistory.push(charCode);
            // Here's where we find out if the Konami code existed
            if (winningCombos.indexOf(keyCodeHistory.join("")) != -1) {
                __triggers.forEach(function(item, index) {
                    (typeof(item) == 'function') && item();
                });
            }
            break;
        }
        //Call our original keypress if it was there
        originalKeyPress && originalKeyPress(arguments);
    };
    
    return self={
        /*
         *  addTrigger()
         *  Adds a function to the callback stack.
         *  @callback {function} - The callback function you want to add
        */
        addTrigger: function(callback) {
            __triggers.push(callback);
        },
        /*
         *  removeTrigger()
         *  Removes a function from the callback stack.
         *  @callback {function} - The callback function you want to remove
        */
        removeTrigger: function(callback) {
            __triggers = __triggers.filter(function(item, index) {
                return item != callback;
            });
        },
        /*
         *   clearCombo()
         *   Clears the combo being used.
         *   @no prarameteres
         */
        clearCombo: function() {
            winningCombos = [];
        },
        /*
         *   addCombo()
         *   Adds a combo into the trigger. We can have multiple key combo to trigger the callback functions.
         *   @combo {string} - The string you want to use e.g. KonamiCodeTrigger.Key.A + KonamiCodeTrigger.Key.B
         */
        addCombo: function(combo) {
            winningCombos.push(String("" + combo));
        },
        /*
         *   removeCombo()
         *   Removes a combo.
         *   @combo {string} - The string you want to remove e.g. KonamiCodeTrigger.Key.A + KonamiCodeTrigger.Key.B
          */
        removeCombo: function(combo) {
            winningCombos = winningCombos.filter(function(item, index) {
                return item != String("" + combo);
            });
        },
        /*
         *   Key {}
         *   These are key constants.. You can add more to the Key costants above. Right now, only UP, DOWN, LEFT,RIGHT,A,B,a,b are in the constants as they are in the *original* Konami combo.
        */
        Key:Key
    }
})();

You can add more callback functions like this:

/* Put this guy in your onload */
KonamiCodeTrigger.addTrigger(function() {
    alert("BINGO! Now we do something interesting!");
});

And you can clear the original combo and add your own combo this way. (You need to assign the ‘Key’ object has the keyboard key mappings you want.)

//KonamiCodeTrigger.clearCombo();
KonamiCodeTrigger.addCombo("" + KonamiCodeTrigger.Key.A + KonamiCodeTrigger.Key.B);