Commits

Michael Granger committed 1f3ac63

Initial attempt with Ember appkit and adminplus template

  • Participants

Comments (0)

Files changed (52)

+{
+  "directory": "vendor"
+}
+syntax: glob
+
+# compiled output
+dist/
+tmp/
+
+# dependencies
+node_modules/
+vendor/
+!vendor/loader.js
+
+# misc
+.sass-cache
+connect.lock
+libpeerconnection.log
+.DS_Store
+{
+  "predef": [
+    "document",
+    "window",
+    "location",
+    "setTimeout",
+    "Ember",
+    "Em",
+    "$",
+    "define",
+    "console",
+    "require",
+    "requireModule",
+    "equal",
+    "notEqual",
+    "notStrictEqual",
+    "test",
+    "asyncTest",
+    "testBoth",
+    "testWithDefault",
+    "raises",
+    "throws",
+    "deepEqual",
+    "start",
+    "stop",
+    "ok",
+    "strictEqual",
+    "module",
+    "process",
+    "expect",
+    "visit",
+    "exists",
+    "fillIn",
+    "click",
+    "find"
+  ],
+  "node" : false,
+  "browser" : false,
+  "boss" : true,
+  "curly": false,
+  "debug": false,
+  "devel": false,
+  "eqeqeq": true,
+  "evil": true,
+  "forin": false,
+  "immed": false,
+  "laxbreak": false,
+  "newcap": true,
+  "noarg": true,
+  "noempty": false,
+  "nonew": false,
+  "nomen": false,
+  "onevar": false,
+  "plusplus": false,
+  "regexp": false,
+  "undef": true,
+  "sub": true,
+  "strict": false,
+  "white": false,
+  "eqnull": true,
+  "esnext": true
+}
+language: node_js
+node_js:
+  - 0.10
+before_script:
+  - npm install -g grunt-cli

File Gruntfile.js

+module.exports = function(grunt) {
+  var config = {
+    pkg: grunt.file.readJSON('package.json'),
+    env: process.env,
+  };
+
+  grunt.util._.extend(config, loadConfig('./tasks/options/'));
+
+  grunt.initConfig(config);
+
+  require('load-grunt-tasks')(grunt);
+  grunt.loadTasks('tasks');
+
+  grunt.registerTask('default', "Build (in debug mode) & test your application.", ['test']);
+  grunt.registerTask('build',   [
+                     'clean:build',
+                     'lock',
+                     'copy:prepare',
+                     'transpile',
+                     'jshint',
+                     'copy:stage',
+                     // Uncomment this line & `npm install --save-dev grunt-sass` for SASS support.
+                     // 'sass:compile',
+                     // Uncomment this line & `npm install --save-dev grunt-contrib-less` for LESS support.
+                     // 'less:compile'
+                     // Uncomment this line & `npm install --save-dev grunt-contrib-stylus` for stylus/nib support.
+                     // 'stylus:compile'
+                     'concat_sourcemap',
+                     'unlock' ]);
+
+  grunt.registerTask('build:debug', "Build a development-friendly version of your app.", [
+                     'build',
+                     'emberTemplates:debug',
+                     'copy:vendor' ]);
+
+  grunt.registerTask('build:dist', "Build a minified & production-ready version of your app.", [
+                     'build',
+                     'clean:release',
+                     'emberTemplates:dist',
+                     'dom_munger:distEmber',
+                     'dom_munger:distHandlebars',
+                     'useminPrepare',
+                     'concat',
+                     'uglify',
+                     'copy:dist',
+                     'rev',
+                     'usemin' ]);
+
+  grunt.registerTask('test', "Run your apps's tests once. Uses Google Chrome by default. Logs coverage output to tmp/public/coverage.", [
+                     'build:debug', 'karma:test' ]);
+
+  grunt.registerTask('test:ci', "Run your app's tests in PhantomJS. For use in continuous integration (i.e. Travis CI).", [
+                     'build:debug', 'karma:ci' ]);
+
+  grunt.registerTask('test:browsers', "Run your app's tests in multiple browsers (see tasks/options/karma.js for configuration).", [
+                     'build:debug', 'karma:browsers' ]);
+
+  grunt.registerTask('test:server', "Start a Karma test server. Automatically reruns your tests when files change and logs the results to the terminal.", [
+                     'build:debug', 'karma:server', 'connect:server', 'watch:test']);
+
+  grunt.registerTask('server', "Run your server in development mode, auto-rebuilding when files change.",
+                     ['build:debug', 'connect:server', 'watch:main']);
+  grunt.registerTask('server:dist', "Build and preview production (minified) assets.",
+                     ['build:dist', 'connect:dist:keepalive']);
+};
+
+
+// TODO: extract this out
+function loadConfig(path) {
+  var glob = require('glob');
+  var object = {};
+  var key;
+
+  glob.sync('*', {cwd: path}).forEach(function(option) {
+    key = option.replace(/\.js$/,'');
+    object[key] = require(path + option);
+  });
+
+  return object;
+}
+
+# Ember App Kit [![Build Status](https://travis-ci.org/stefanpenner/ember-app-kit.png?branch=master)](https://travis-ci.org/stefanpenner/ember-app-kit)
+The goal Ember App Kit is to be the foundation for your ambitious web applications built in Ember. It's intended to be used either on its own or as the base scaffolding for generators built on for Ember applications, including [Ember Tools](https://github.com/rpflorence/ember-tools), [generator-ember](https://github.com/yeoman/generator-ember), and a hypothetical official executable at some point in the future.
+
+This project has been extracted out of several real world applications, and is actively used. Currently it covers the basics fairly well, but much still needs to be done. As we learn and as more contributors join in it continues to evolve. So if you notice something that seems lame, it likely is, so submit an issue or PR!
+
+We welcome ideas and experiments. 
+
+### List of Active Experiments
+-  CJS aligned branch [https://github.com/stefanpenner/ember-app-kit/tree/cjs]
+-  es6-transpiler-v2 branch [https://github.com/stefanpenner/ember-app-kit/tree/es6-transpiler-v2]
+
+For usage information, please see the [getting started guide](https://github.com/stefanpenner/ember-app-kit/wiki/Getting-Started).
+
+## Features
+
+- (relatively) Sane project structure
+- ES6 module transpiler support (easy, future-proof modules)
+- Module system-aware resolver
+- Simple ember-testing example
+- Testing via QUnit, Ember Testing, and Karma
+- Linting source via JSHint (including module syntax)
+- Project compilation & minification for easy deploys
+- Catch-all index.html for easy reloading of pushState router apps
+- Optional CoffeeScript, SASS, and LESS support
+- Optional support for package management via [bower](https://github.com/bower/bower)
+
+## Future goals
+
+- Source maps for transpiled modules
+- Better support for usage in generators
+- easier to install 3rd party packages
+- faster more intelligent builds
+
+Think anything else is missing? Feel free to open an issue (or, even better, a PR)! Discussion and feedback is always appreciated.
+
+
+## Special Thanks
+
+Some ideas in ember-app-kit originated in work by Yapp Labs (@yapplabs) with McGraw-Hill Education Labs (@mhelabs)
+on [yapplabs/glazier](https://github.com/yapplabs/glazier).
+Thanks to Yapp and MHE for supporting the Ember ecosystem!

File app/adminplus.js

+/* ==========================================================
+* AdminPlus v2.0
+* common.js
+*
+* http://www.mosaicpro.biz
+* Copyright MosaicPro
+*
+* Built exclusively for sale @Envato Marketplaces
+* ========================================================== */
+
+
+$(function()
+{
+	// menu slim scroll max height
+	setTimeout(function()
+	{
+		var menu_max_height = parseInt($('#menu .slim-scroll').attr('data-scroll-height'), 10);
+		var menu_real_max_height = parseInt($('#wrapper').height(), 10);
+		$('#menu .slim-scroll').slimScroll({
+			height: (menu_max_height < menu_real_max_height ? (menu_real_max_height - 40) : menu_max_height) + "px",
+			allowPageScroll : true,
+			railDraggable: ($.fn.draggable ? true : false)
+		});
+
+		if (Modernizr.touch)
+			return;
+
+		// fixes weird bug when page loads and mouse over the sidebar (can't scroll)
+		$('#menu .slim-scroll').trigger('mouseenter').trigger('mouseleave');
+	}, 200);
+
+	/* Slim Scroll Widgets */
+	$('.widget-scroll').each(function(){
+		$(this).find('.widget-body > div').slimScroll({
+			height: $(this).attr('data-scroll-height')
+		});
+	});
+
+	/* Other Slim Scroll areas */
+	$('#content .slim-scroll').each(function(){
+		$(this).slimScroll({
+			height: $(this).attr('data-scroll-height'),
+			allowPageScroll : false,
+			railDraggable: ($.fn.draggable ? true : false)
+		});
+	});
+
+	// print
+	$('[data-toggle="print"]').click(function(e)
+	{
+		e.preventDefault();
+		window.print();
+	});
+
+	// main menu -> submenus
+	$('#menu .collapse').on('show', function()
+	{
+		$(this).parents('.hasSubmenu:first').addClass('active');
+	})
+	.on('hidden', function()
+	{
+		$(this).parents('.hasSubmenu:first').removeClass('active');
+	});
+
+	// main menu visibility toggle
+	$('.navbar.main .btn-navbar').click(function()
+	{
+		$('.container-fluid:first').toggleClass('menu-hidden');
+		$('#menu').toggleClass('hidden-phone');
+	});
+
+	// tooltips
+	$('[data-toggle="tooltip"]').tooltip();
+
+	$('.widget-activity .filters .glyphicons').click(function()
+	{
+		$('.widget-activity .filters .active').toggleClass('active');
+		$(this).toggleClass('active');
+	});
+
+	$(window).resize(function()
+	{
+		if (!$('#menu').is(':visible') && !$('.container-fluid:first').is('menu-hidden'))
+		$('.container-fluid:first').addClass('menu-hidden');
+	});
+
+	$(window).resize();
+
+	// collapsible widgets
+	$('.widget[data-toggle="collapse-widget"] .widget-body')
+	.on('show', function(){
+		$(this).parents('.widget:first').attr('data-collapse-closed', "false");
+	})
+	.on('shown', function(){
+		setTimeout(function(){ $(window).resize(); }, 500);
+	})
+	.on('hidden', function(){
+		$(this).parents('.widget:first').attr('data-collapse-closed', "true");
+	});
+
+	$('.widget[data-toggle="collapse-widget"]').each(function()
+	{
+		// append toggle button
+		$(this).find('.widget-head').append('<span class="collapse-toggle"></span>');
+
+		// make the widget body collapsible
+		$(this).find('.widget-body').addClass('collapse');
+
+		// verify if the widget should be opened
+		if ($(this).attr('data-collapse-closed') !== "true")
+		$(this).find('.widget-body').addClass('in');
+
+		// bind the toggle button
+		$(this).find('.collapse-toggle').on('click', function(){
+			$(this).parents('.widget:first').find('.widget-body').collapse('toggle');
+		});
+	});
+
+	// view source toggle buttons
+	$('.btn-source-toggle').click(function(e){
+		e.preventDefault();
+		$('.code:not(.show)').toggleClass('hide');
+	});
+
+	// show/hide toggle buttons
+	$('[data-toggle="hide"]').click(function(){
+		$($(this).attr('data-target')).toggleClass('hide');
+		if ($(this).is('.scrollTarget') && !$($(this).attr('data-target')).is('.hide'))
+		scrollTo($(this).attr('data-target'));
+	});
+
+	/* wysihtml5 */
+	$('textarea.wysihtml5').wysihtml5();
+
+	/* DataTables */
+	$('.dynamicTable:not(.tableTools)').dataTable({
+		"sPaginationType": "bootstrap",
+		"sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>",
+		"oLanguage": {
+			"sLengthMenu": "_MENU_ records per page"
+		}
+	});
+	$('.dynamicTable.tableTools').dataTable({
+		"sPaginationType": "bootstrap",
+		"sDom": "<'row-fluid'<'span5'T><'span3'l><'span4'f>r>t<'row-fluid'<'span6'i><'span6'p>>",
+		"oLanguage": {
+			"sLengthMenu": "_MENU_ per page"
+		},
+		"oTableTools": {
+			"sSwfPath": commonPath + "theme/scripts/plugins/tables/DataTables/extras/TableTools/media/swf/copy_csv_xls_pdf.swf"
+		}
+	});
+
+	/*
+	* Helper function for JQueryUI Sliders Create event
+	*/
+	function JQSliderCreate()
+	{
+		$(this)
+		.removeClass('ui-corner-all ui-widget-content')
+		.wrap('<span class="ui-slider-wrap"></span>')
+		.find('.ui-slider-handle')
+		.removeClass('ui-corner-all ui-state-default');
+	}
+
+	/*
+	* JQueryUI Slider: Default slider
+	*/
+	$( ".slider-single" ).slider({
+		create: JQSliderCreate,
+		value: 10,
+		animate: true,
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+
+	/*
+	* JQueryUI Slider: Multiple Vertical Sliders
+	*/
+	$( ".sliders-vertical > span" ).each(function()
+	{
+		var value = parseInt( $( this ).text(), 10 );
+		$( this ).empty().slider({
+			create: JQSliderCreate,
+			value: value,
+			range: "min",
+			animate: true,
+			orientation: "vertical",
+			start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+			stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+		});
+	});
+
+	/*
+	* JQueryUI Slider: Range Slider
+	*/
+	$( ".range-slider .slider" ).slider({
+		create: JQSliderCreate,
+		range: true,
+		min: 0,
+		max: 500,
+		values: [ 75, 300 ],
+		slide: function( event, ui ) {
+			$( ".range-slider .amount" ).val( "$" + ui.values[ 0 ] + " - $" + ui.values[ 1 ] );
+		},
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+	$( ".range-slider .amount" ).
+		val( "$" +
+		     $( ".range-slider .slider" ).slider( "values", 0 ) +
+		     " - $" +
+		     $( ".range-slider .slider" ).slider( "values", 1 ) );
+
+	/*
+	* JQueryUI Slider: Snap to Increments
+	*/
+	$( ".increments-slider .slider" ).slider({
+		create: JQSliderCreate,
+		value:100,
+		min: 0,
+		max: 500,
+		step: 50,
+		slide: function( event, ui ) {
+			$( ".increments-slider .amount" ).val( "$" + ui.value );
+		},
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+	$( ".increments-slider .amount" ).
+		val( "$" + $( ".increments-slider .slider" ).slider( "value" ) );
+
+	/*
+	* JQueryUI Slider: Vertical Range Slider
+	*/
+	$( ".vertical-range-slider .slider" ).slider({
+		create: JQSliderCreate,
+		orientation: "vertical",
+		range: true,
+		min: 0,
+		max: 500,
+		values: [ 100, 400 ],
+		slide: function( event, ui ) {
+			$( ".vertical-range-slider .amount" ).
+			val( "$" + ui.values[ 0 ] + " - $" + ui.values[ 1 ] );
+		},
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+	$( ".vertical-range-slider .amount" ).
+		val( "$" +
+		     $( ".vertical-range-slider .slider" ).slider( "values", 0 ) +
+		     " - $" +
+		     $( ".vertical-range-slider .slider" ).slider( "values", 1 ) );
+
+	/*
+	* JQueryUI Slider: Range fixed minimum
+	*/
+	$( ".slider-range-min .slider" ).slider({
+		create: JQSliderCreate,
+		range: "min",
+		value: 150,
+		min: 1,
+		max: 700,
+		slide: function( event, ui ) {
+			$( ".slider-range-min .amount" ).val( "$" + ui.value );
+		},
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+	$( ".slider-range-min .amount" ).
+		val( "$" + $( ".slider-range-min .slider" ).slider( "value" ) );
+
+	/*
+	* JQueryUI Slider: Range fixed maximum
+	*/
+	$( ".slider-range-max .slider" ).slider({
+		create: JQSliderCreate,
+		range: "max",
+		min: 1,
+		max: 700,
+		value: 150,
+		slide: function( event, ui ) {
+			$( ".slider-range-max .amount" ).val( "$" + ui.value );
+		},
+		start: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.disable(); },
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+	$( ".slider-range-max .amount" ).
+		val( "$" + $( ".slider-range-max .slider" ).slider( "value" ) );
+
+	/*
+	* Boostrap Extended
+	*/
+	// custom select for Boostrap using dropdowns
+	$('.selectpicker').selectpicker();
+
+	// bootstrap-toggle-buttons
+	$('.toggle-button').toggleButtons();
+
+	/*
+	* UniformJS: Sexy form elements
+	*/
+	$('.uniformjs').find("select, input, button, textarea").uniform();
+
+	// colorpicker
+	$('#colorpicker').farbtastic('#colorpickerColor');
+
+	// datepicker
+	$("#datepicker").datepicker({ showOtherMonths:true });
+	$('#datepicker-inline').datepicker({ inline: true, showOtherMonths: true });
+
+	// bookings daterange
+	$( "#dateRangeFrom" ).
+		datepicker({
+			defaultDate: "+1w",
+			changeMonth: false,
+			numberOfMonths: 2,
+			onClose: function( selectedDate ) {
+				$( "#dateRangeTo" ).datepicker( "option", "minDate", selectedDate );
+			}
+		}).
+		datepicker( "option", "maxDate", $('#dateRangeTo').val() );
+
+	$( "#dateRangeTo" ).
+		datepicker({
+			defaultDate: "+1w",
+			changeMonth: false,
+			numberOfMonths: 2,
+			onClose: function( selectedDate ) {
+				$( "#dateRangeFrom" ).datepicker( "option", "maxDate", selectedDate );
+			}
+		}).
+		datepicker( "option", "minDate", $('#dateRangeFrom').val() );
+
+	$('.checkboxs thead :checkbox').change(function()
+	{
+		if ($(this).is(':checked')) {
+			$('.checkboxs tbody :checkbox').prop('checked', true).parent().addClass('checked');
+			$('.checkboxs tbody tr.selectable').addClass('selected');
+			$('.checkboxs_actions').show();
+		}
+		else {
+			$('.checkboxs tbody :checkbox').prop('checked', false).parent().removeClass('checked');
+			$('.checkboxs tbody tr.selectable').removeClass('selected');
+			$('.checkboxs_actions').hide();
+		}
+	});
+
+	$('.checkboxs tbody').on('click', 'tr.selectable', function(e)
+	{
+		var c = $(this).find(':checkbox');
+		var s = $(e.srcElement);
+
+		if (e.srcElement.nodeName === 'INPUT') {
+			if (c.is(':checked')) {
+				$(this).addClass('selected');
+			} else {
+				$(this).removeClass('selected');
+			}
+		}
+		else if (e.srcElement.nodeName !== 'TD' && e.srcElement.nodeName !== 'TR' &&
+		         e.srcElement.nodeName !== 'DIV')
+		{
+			return true;
+		}
+		else {
+			if (c.is(':checked')) {
+				c.prop('checked', false).parent().removeClass('checked');
+				$(this).removeClass('selected');
+			}
+			else {
+				c.prop('checked', true).parent().addClass('checked');
+				$(this).addClass('selected');
+			}
+		}
+
+		if ($('.checkboxs tr.selectable :checked').size() == $('.checkboxs tr.selectable :checkbox').size()) {
+			$('.checkboxs thead :checkbox').prop('checked', true).parent().addClass('checked');
+		}
+		else {
+			$('.checkboxs thead :checkbox').prop('checked', false).parent().removeClass('checked');
+		}
+
+		if ($('.checkboxs tr.selectable :checked').size() >= 1) {
+			$('.checkboxs_actions').show();
+		}
+		else {
+			$('.checkboxs_actions').hide();
+		}
+	});
+
+	if ($('.checkboxs tbody :checked').size() == $('.checkboxs tbody :checkbox').size() &&
+	    $('.checkboxs tbody :checked').length)
+	{
+		$('.checkboxs thead :checkbox').prop('checked', true).parent().addClass('checked');
+	}
+
+	if ($('.checkboxs tbody :checked').length) {
+		$('.checkboxs_actions').show();
+	}
+
+	$('.radioboxs tbody tr.selectable').click(function(e){
+		var c = $(this).find(':radio');
+		if (e.srcElement.nodeName == 'INPUT') {
+			if (c.is(':checked')) {
+				$(this).addClass('selected');
+			} else {
+				$(this).removeClass('selected');
+			}
+		}
+		else if (e.srcElement.nodeName != 'TD' && e.srcElement.nodeName != 'TR') {
+			return true;
+		}
+		else {
+			if (c.is(':checked')) {
+				c.attr('checked', false);
+				$(this).removeClass('selected');
+			} else {
+				c.attr('checked', true);
+				$('.radioboxs tbody tr.selectable').removeClass('selected');
+				$(this).addClass('selected');
+			}
+		}
+	});
+
+	// sortable tables
+	$( ".js-table-sortable" ).sortable(
+	{
+		placeholder: "ui-state-highlight",
+		items: "tbody tr",
+		handle: ".js-sortable-handle",
+		forcePlaceholderSize: true,
+		helper: function(e, ui)
+		{
+			ui.children().each(function() {
+				$(this).width($(this).width());
+			});
+			return ui;
+		},
+		start: function(event, ui)
+		{
+			if (typeof mainYScroller !== 'undefined') mainYScroller.disable();
+			ui.placeholder.html('<td colspan="' + $(this).find('tbody tr:first td').size() + '">&nbsp;</td>');
+		},
+		stop: function() { if (typeof mainYScroller !== 'undefined') mainYScroller.enable(); }
+	});
+
+});
+
+import Resolver from 'resolver';
+
+var App = Ember.Application.create({
+  LOG_ACTIVE_GENERATION: true,
+  LOG_VIEW_LOOKUPS: true,
+  modulePrefix: 'appkit', // TODO: loaded via config
+  Resolver: Resolver
+});
+
+import routes from 'appkit/routes';
+App.Router.map(routes); // TODO: just resolve the router
+
+export default App;
+

File app/components/.gitkeep

Empty file added.

File app/controllers/.gitkeep

Empty file added.

File app/helpers/.gitkeep

Empty file added.

File app/models/.gitkeep

Empty file added.

File app/routes.js

+function Routes() {
+  // // routes/resources
+  // this.resource('posts', function() {
+  //   this.route('new');
+  // });
+}
+
+export default Routes;
+

File app/routes/.gitkeep

Empty file added.

File app/routes/index.js

+var IndexRoute = Ember.Route.extend({
+  model: function() {
+    return ['red', 'yellow', 'blue'];
+  }
+});
+
+export default IndexRoute;

File app/styles/.gitkeep

Empty file added.

File app/templates/.gitkeep

Empty file added.

File app/templates/application.hbs

+<!-- $Id$ -->
+<div class="container-fluid fixed">
+		
+	<div class="navbar main hidden-print">
+		<a href="/" class="appbrand"><span>Angelfish <span>We told you so.</span></span></a>
+			
+		<button type="button" class="btn btn-navbar">
+			<span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span>
+		</button>
+						
+		<ul class="topnav pull-right">
+			<li class="visible-desktop">
+				<ul class="notif">
+					<li><a href="" class="glyphicons envelope" data-toggle="tooltip" data-placement="bottom" data-original-title="5 new messages"><i></i> 5</a></li>
+					<li><a href="" class="glyphicons shopping_cart" data-toggle="tooltip" data-placement="bottom" data-original-title="1 new orders"><i></i> 1</a></li>
+					<li><a href="" class="glyphicons log_book" data-toggle="tooltip" data-placement="bottom" data-original-title="3 new activities"><i></i> 3</a></li>
+				</ul>
+			</li>
+			<li class="account">
+				<a data-toggle="dropdown" href="" class="glyphicons logout lock"><span class="hidden-phone text">mgranger</span><i></i></a>
+				<ul class="dropdown-menu pull-right">
+					<li><a href="my-accoutn.html?lang=en" class="glyphicons cogwheel">Settings<i></i></a></li>
+					<li><a href="my_account.html?lang=en" class="glyphicons camera">My Photos<i></i></a></li>
+					<li class="highlight profile">
+						<span>
+							<span class="heading">Profile <a href="my_account.html?lang=en" class="pull-right">edit</a></span>
+							<span class="img"></span>
+							<span class="details">
+								<a href="my_account.html?lang=en">Mosaic Pro</a>
+								contact@mosaicpro.biz
+							</span>
+							<span class="clearfix"></span>
+						</span>
+					</li>
+					<li>
+						<span>
+							<a class="btn btn-default btn-small pull-right" style="padding: 2px 10px; background: #fff;" href="login.html?lang=en">Sign Out</a>
+						</span>
+					</li>
+				</ul>
+			</li>
+		</ul>
+						
+	</div>
+		
+	<div id="wrapper">
+		<div id="menu" class="hidden-phone">
+			<div id="menuInner">
+			
+				<!-- Scrollable menu wrapper with Maximum height -->
+				<div class="slim-scroll" data-scroll-height="420px">
+				
+					<div id="search">
+						<input type="text" placeholder="Quick search ..." />
+						<button class="glyphicons search"><i></i></button>
+					</div>
+					<ul>
+						<li class="heading"><span>Info</span></li>
+					</ul>
+				</div>
+			
+			</div>
+			
+		</div>
+				
+		<div id="content">
+			<ul class="breadcrumb">
+				<li><a href="index.html?lang=en" class="glyphicons home"><i></i> Angelfish</a></li>
+				<li class="divider"></li>
+				<li>Gallery</li>
+			</ul>
+			<div class="separator bottom"></div>
+
+			{{outlet}}
+
+			<div class="separator bottom"></div>		
+		</div>
+	
+	</div>
+	
+
+	<div id="footer" class="visible-desktop">
+		<div class="wrap">
+			<ul>
+				<li><a href="" class="glyphicons circle_question_mark text" title=""><i></i><span class="hidden-phone">Server Info</span></a></li>
+			</ul>
+		</div>
+	</div>
+    				
+</div>	
+

File app/templates/components/.gitkeep

Empty file added.

File app/templates/index.hbs

+<!-- $Id$ -->
+
+				<div class="heading-buttons">
+					<h3 class="glyphicons display"><i></i> Gallery</h3>
+					<div class="buttons pull-right">
+						<a href="" class="btn btn-default btn-icon glyphicons power"><i></i> Connect</a>
+					</div>
+					<div class="clearfix" style="clear: both;"></div>
+				</div>
+				<div class="separator bottom"></div>
+
+				<div class="well">
+					<!-- Row -->
+					<div class="row-fluid">
+	
+						<!-- Thumbnails -->
+						<ul class="thumbnails">
+		
+							<!-- Column -->
+							<li class="span4">
+			
+								<!-- Thumbnail -->
+								<div class="thumbnail">
+									<img data-src="holder.js/100%x200/dark" alt="100%x200 Image Holder" />
+									<div class="caption">
+										<h4>Thumbnail heading</h4>
+										<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
+										<a href="#" class="btn btn-primary">Action</a>
+										<a href="#" class="btn btn-inverse">Action</a>
+									</div>
+								</div>
+								<!-- // Thumbnail END -->
+				
+							</li>
+							<!-- // Column END -->
+			
+							<!-- Column -->
+							<li class="span4">
+			
+								<!-- Thumbnail -->
+								<div class="thumbnail">
+									<img data-src="holder.js/100%x200/dark" alt="100%x200 Image Holder" />
+									<div class="caption">
+										<h4>Thumbnail heading</h4>
+										<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
+										<a href="#" class="btn btn-primary">Action</a> 
+										<a href="#" class="btn btn-inverse">Action</a>
+									</div>
+								</div>
+								<!-- // Thumbnail END -->
+				
+							</li>
+							<!-- // Column END -->
+			
+							<!-- Column -->
+							<li class="span4">
+			
+								<!-- Thumbnail -->
+								<div class="thumbnail">
+									<img data-src="holder.js/100%x200/dark" alt="100%x200 Image Holder" />
+									<div class="caption">
+										<h4>Thumbnail heading</h4>
+										<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
+										<a href="#" class="btn btn-primary">Action</a> 
+										<a href="#" class="btn btn-inverse">Action</a>
+									</div>
+								</div>
+								<!-- // Thumbnail END -->
+				
+							</li>
+							<!-- // Thumbnail END -->
+			
+						</ul>
+						<!-- // Thumbnails END -->
+		
+					</div>
+					<!-- // Row END -->
+				</div>
+
+				<h4 class="heading-arrow">Recent Activity</h4>
+				<div class="innerLR">
+					<div class="widget-activity">
+						<ul class="filters">
+							<li>Filter by</li>
+							<li class="glyphicons user"><i></i></li>
+							<li class="glyphicons upload"><i></i></li>
+							<li class="glyphicons delete"><i></i></li>
+							<li class="glyphicons edit"><i></i></li>
+							<li class="glyphicons shopping_cart"><i></i></li>
+						</ul>
+						<div class="clearfix"></div>
+						<ul class="activities">
+							<li class="highlight">
+								<span class="glyphicons activity-icon shopping_cart"><i></i></span>
+								<a href="">adrian</a> created a collection (<a href="">Shot 145 Source Material</a>)
+							</li>
+							<li>
+								<span class="glyphicons activity-icon upload"><i></i></span>
+								<a href="">adrian</a> uploaded <a href="">164 new assets</a> (44.6 MB)
+							</li>
+							<li>
+								<span class="glyphicons activity-icon delete"><i></i></span>
+								<a href="">jtran</a> deleted 11 assets.
+							</li>
+						</ul>
+					</div>
+				</div>
+				<div class="separator bottom"></div>
+
+				<div class="innerLR">
+					<h4 class="glyphicons clock"><i></i> Server Info</h4>
+
+					<div class="row-fluid">
+						<div class="span6">
+							<div class="separator"></div>
+							<div class="btn-group btn-group-vertical block">
+								<a class="btn btn-icon btn-default btn-block glyphicons group count"><i></i> <span>5,986</span>Server</a>
+								<a class="btn btn-icon btn-default btn-block glyphicons user_add count"><i></i> <span>98</span>New users</a>
+								<a class="btn btn-icon btn-default btn-block glyphicons shopping_cart count"><i></i> <span>305</span>Products</a>
+							</div>
+						</div>
+						<div class="span6">
+							<div class="btn-group btn-group-vertical block">
+								<a class="btn btn-icon btn-default btn-block glyphicons cargo count"><i></i> <span>687</span>Total orders</a>
+								<a class="btn btn-icon btn-default btn-block glyphicons download count"><i></i> <span>15</span>Pending orders</a>
+								<a class="btn btn-icon btn-default btn-block glyphicons download count"><i></i> <span>3</span>Pending delivery</a>
+								<a class="btn btn-icon btn-primary btn-block glyphicons fire count"><i></i> <span>5</span>Support</a>
+							</div>
+						</div>
+					</div>
+				</div>

File app/utils/.gitkeep

Empty file added.

File app/views/.gitkeep

Empty file added.
+{
+  "name": "ember-app-kit",
+  "dependencies": {
+    "ember": "http://builds.emberjs.com/stable/ember.js",
+    "ember-prod": "http://builds.emberjs.com/stable/ember.prod.js",
+    "handlebars": "1.0.0",
+    "jquery": "~1.9.1",
+    "qunit": "~1.12.0"
+  }
+}

File karma.conf.js

+// Karma configuration
+// Generated on Fri Jul 05 2013 01:57:57 GMT-0400 (EDT)
+
+module.exports = function(config) {
+  config.set({
+
+    // base path, that will be used to resolve files and exclude
+    basePath: 'tmp/public',
+
+    // list of files / patterns to load in the browser
+    files: [
+      'vendor/loader.js',
+      'vendor/jquery/jquery.js',
+      'vendor/handlebars/handlebars.js',
+      'vendor/ember/index.js',
+      'assets/templates.js',
+      'assets/app.js',
+      'tests/test_helper.js',
+      'tests/tests.js',
+      'tests/test_loader.js'
+    ],
+
+    frameworks: ['qunit'],
+
+    plugins: [
+      'karma-qunit',
+      'karma-coverage',
+      'karma-phantomjs-launcher',
+      'karma-chrome-launcher',
+      'karma-firefox-launcher',
+      //'karma-safari-launcher'  // npm install karma-safari-launcher
+    ],
+
+    preprocessors: {
+      'assets/*.js': 'coverage'
+    },
+
+    // list of files to exclude
+    exclude: [],
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress', 'junit'
+    reporters: 'coverage',
+
+    coverageReporter: {
+      type : ['text'],
+      dir : 'coverage/'
+    },
+
+    // web server port
+    port: 9876,
+
+    // cli runner port
+    runnerPort: 9100,
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+    // level of logging
+    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: false,
+
+    // Start these browsers, currently available:
+    // - Chrome
+    // - ChromeCanary
+    // - Firefox
+    // - Opera
+    // - Safari (only Mac)
+    // - PhantomJS
+    // - IE (only Windows)
+    browsers: ['Chrome'],
+
+    // If browser does not capture in given timeout [ms], kill it
+    captureTimeout: 60000,
+
+    // Continuous Integration mode
+    // if true, it capture browsers, run tests and exit
+    singleRun: false
+  });
+};

File package.json

+{
+  "name": "app-kit",
+  "namespace": "appkit",
+  "version": "0.0.0",
+  "main": "index.js",
+  "directories": {
+    "doc": "doc",
+    "test": "test"
+  },
+  "scripts": {
+    "start": "grunt server",
+    "build": "grunt build:debug",
+    "test": "grunt test:ci"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/stefanpenner/app-kit.git"
+  },
+  "author": "",
+  "license": "BSD",
+  "devDependencies": {
+    "grunt-contrib-connect": "~0.3.0",
+    "grunt": "~0.4.1",
+    "grunt-contrib-watch": "~0.4.4",
+    "grunt-contrib-copy": "~0.4.1",
+    "grunt-es6-module-transpiler": "~0.4.1",
+    "glob": "~3.2.1",
+    "load-grunt-tasks": "~0.1.0",
+    "grunt-contrib-concat": "~0.3.0",
+    "grunt-contrib-clean": "~0.4.1",
+    "grunt-contrib-jshint": "~0.6.2",
+    "grunt-usemin": "~0.1.12",
+    "grunt-contrib-uglify": "~0.2.2",
+    "grunt-rev": "~0.1.0",
+    "grunt-ember-templates": "~>0.4.13",
+    "karma": "~0.9.4",
+    "grunt-karma": "~0.5",
+    "karma-qunit": "~0.0.3",
+    "karma-coverage": "~0.0.3",
+    "karma-phantomjs-launcher": "~0.0.2",
+    "lockfile": "~>0.3.0",
+    "grunt-concat-sourcemap": "~0.3.0",
+    "grunt-dom-munger": "~2.0.1"
+  },
+  "dependencies": {
+    "loom": "~2.0.0",
+    "loom-generators-ember": "0.0.1"
+  }
+}

File public/assets/.gitkeep

Empty file added.

File public/assets/app.css

Empty file added.

File public/index.html

+<!DOCTYPE html>
+<!--[if lt IE 7]> <html class="lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>    <html class="lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>    <html class="lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html> <!--<![endif]-->
+<head>
+	<title>Angelfish</title>
+	
+	<!-- Meta -->
+	<meta charset="UTF-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
+	<meta name="apple-mobile-web-app-capable" content="yes">
+	<meta name="apple-mobile-web-app-status-bar-style" content="black">
+	
+	<!-- build:css css/app.css -->
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/css/bootstrap.min.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/css/bootstrap-responsive.min.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/extend/jasny-bootstrap/css/jasny-bootstrap.min.css">
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/extend/jasny-bootstrap/css/jasny-bootstrap-responsive.min.css">
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/extend/bootstrap-wysihtml5/css/bootstrap-wysihtml5-0.0.2.css">
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/forms/select2/select2.css"/>
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/notifications/notyfy/jquery.notyfy.css"/>
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/notifications/notyfy/themes/default.css"/>
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/notifications/Gritter/css/jquery.gritter.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/system/jquery-ui-1.9.2.custom/css/smoothness/jquery-ui-1.9.2.custom.min.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/css/glyphicons.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/extend/bootstrap-select/bootstrap-select.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/bootstrap/extend/bootstrap-toggle-buttons/static/stylesheets/bootstrap-toggle-buttons.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/forms/pixelmatrix-uniform/css/uniform.default.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/other/google-code-prettify/prettify.css" type="text/css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/scripts/plugins/color/jquery-miniColors/jquery.miniColors.css" />
+	<link rel="stylesheet" href="/vendor/adminplus/theme/css/style.css" />
+	<!-- endbuild -->
+	
+	<!-- build:js(.) assets/vendor.min.js -->
+  <script src="/vendor/jquery/jquery.js"></script>
+  <script src="/vendor/handlebars/handlebars.js"></script>
+  <script src="/vendor/ember/index.js"></script>
+  <script src="/vendor/ember-data/ember-data.js"></script>
+  <script src="/vendor/loader.js"></script>
+  <!-- endbuild -->
+
+	<!-- build:js(.) assets/adminplus.min.js -->
+	<script src="/vendor/adminplus/theme/scripts/plugins/system/jquery-1.8.2.min.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/system/modernizr.custom.76094.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/system/jquery-ui-1.9.2.custom/js/jquery-ui-1.9.2.custom.min.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/system/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/color/jquery-miniColors/jquery.miniColors.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/forms/select2/select2.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/other/jquery-slimScroll/jquery.slimscroll.min.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/other/jquery.ba-resize.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/forms/pixelmatrix-uniform/jquery.uniform.min.js"></script>
+	<script src="/vendor/adminplus/bootstrap/js/bootstrap.min.js"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootstrap-select/bootstrap-select.js"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootstrap-toggle-buttons/static/js/jquery.toggle.buttons.js"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootstrap-hover-dropdown/twitter-bootstrap-hover-dropdown.min.js"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/jasny-bootstrap/js/jasny-bootstrap.min.js" type="text/javascript"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/jasny-bootstrap/js/bootstrap-fileupload.js" type="text/javascript"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootbox.js" type="text/javascript"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootstrap-wysihtml5/js/wysihtml5-0.3.0_rc2.min.js" type="text/javascript"></script>
+	<script src="/vendor/adminplus/bootstrap/extend/bootstrap-wysihtml5/js/bootstrap-wysihtml5-0.0.2.js" type="text/javascript"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/notifications/Gritter/js/jquery.gritter.min.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/notifications/notyfy/jquery.notyfy.js"></script>
+	<script src="/vendor/adminplus/theme/scripts/plugins/other/holder/holder.js"></script>
+	<!-- endbuild -->
+	
+  <!-- build:js(tmp/public) assets/app.min.js -->
+  <script src="/assets/app.js"></script>
+  <script src="/assets/templates.js"></script>
+  <script src="/assets/adminplus.js"></script>
+  <!-- endbuild -->
+	
+	<!-- Colors -->
+	<script>
+	var primaryColor = '#4a8bc2',
+			dangerColor = '#b55151',
+			successColor = '#609450',
+			warningColor = '#ab7a4b',
+			inverseColor = '#45484d';
+	</script>
+	
+	<!--[if IE]><script type="text/javascript" src="/vendor/adminplus/theme/scripts/plugins/other/excanvas/excanvas.js"></script><![endif]-->
+	<!--[if lt IE 8]><script type="text/javascript" src="/vendor/adminplus/theme/scripts/plugins/other/json2.js"></script><![endif]-->
+</head>
+<body>
+	
+	<script type="text/x-handlebars">
+    window.App = requireModule('appkit/app');
+  </script>
+	
+</body>
+</html>

File tasks/locking.js

+var lockFile = require('lockfile');
+
+module.exports = function(grunt) {
+  grunt.registerTask('lock', 'Set semaphore for connect server to wait on.', function() {
+    grunt.file.mkdir('tmp');
+    lockFile.lockSync('tmp/connect.lock');
+  });
+
+  grunt.registerTask('unlock', 'Release semaphore that connect server waits on.', function() {
+    console.log("unlocking");
+    lockFile.unlockSync('tmp/connect.lock');
+  });
+};

File tasks/options/clean.js

+module.exports = {
+  "build": ['tmp'],
+  "release": ['dist'],
+}

File tasks/options/coffee.js

+// CoffeeScript compilation. This must be enabled by modification
+// of Gruntfile.js.
+//
+// The `bare` option is used since this file will be transpiled
+// anyway. In CoffeeScript files, you need to escape out for
+// some ES6 features like import and export. For example:
+//
+// `import 'appkit/models/user' as User`
+//
+// Posts = Em.Object.extend
+//   init: (userId) ->
+//     @set 'user', User.findById(userId)
+//
+// `export = Posts`
+//
+
+module.exports = {
+  "test": {
+    options: {
+      bare: true
+    },
+    files: [{
+      expand: true,
+      cwd: 'tests/',
+      src: ['**/*.coffee', '!vendor/**/*.coffee'],
+      dest: 'tmp/javascript/tests',
+      ext: '.js'
+    }]
+  },
+  "app": {
+    options: {
+      bare: true
+    },
+    files: [{
+      expand: true,
+      cwd: 'app/',
+      src: '**/*.coffee',
+      dest: 'tmp/javascript/app',
+      ext: '.js'
+    }]
+  }
+};

File tasks/options/concat_sourcemap.js

+module.exports = {
+  app: {
+    src: ['tmp/transpiled/app/**/*.js'],
+    dest: 'tmp/public/assets/app.js',
+    options: {
+      sourcesContent: true
+    },
+  },
+
+  test: {
+    src: 'tmp/transpiled/tests/**/*.js',
+    dest: 'tmp/public/tests/tests.js',
+    options: {
+      sourcesContent: true
+    }
+  }
+};

File tasks/options/connect.js

+var lockFile = require('lockfile'),
+    fs = require('fs'),
+    url = require('url');
+
+module.exports = {
+  server: {
+    options: {
+      port: process.env.PORT || 8000,
+      hostname: '0.0.0.0',
+      base: 'tmp/public',
+      // Use this option to have the catch-all return a different
+      // page than index.html on any url not matching an asset.
+      //   wildcard: 'not_index.html'
+      middleware: middleware
+    }
+  },
+  dist: {
+    options: {
+      port: process.env.PORT || 8000,
+      hostname: '0.0.0.0',
+      base: 'dist/',
+      middleware: middleware
+    }
+  }
+};
+
+// works with tasks/locking.js
+function lock(req, res, next) {
+  (function retry() {
+    if (lockFile.checkSync('tmp/connect.lock')) {
+      setTimeout(retry, 30);
+    } else {
+      next();
+    }
+  }());
+}
+
+function wildcardResponseIsValid(request) {
+  var urlSegments = request.url.split('.'),
+      extension   = urlSegments[urlSegments.length-1];
+  return (
+    ['GET', 'HEAD'].indexOf(request.method.toUpperCase()) > -1 &&
+    (urlSegments.length == 1 || extension.indexOf('htm') == 0 || extension.length > 5)
+  )
+}
+
+function buildWildcardMiddleware(options) {
+  return function(request, response, next) {
+    if (!wildcardResponseIsValid(request)) { return next(); }
+
+    var wildcard     = (options.wildcard || 'index.html'),
+        wildcardPath = options.base + "/" + wildcard;
+
+    fs.readFile(wildcardPath, function(err, data){
+      if (err) { return next('ENOENT' == err.code ? null : err); }
+
+      response.writeHead(200, { 'Content-Type': 'text/html' });
+      response.end(data);
+    });
+  }
+}
+
+function middleware(connect, options) {
+  return [
+    lock,
+    connect['static'](options.base),
+    connect.directory(options.base),
+    // Remove this middleware to disable catch-all routing.
+    buildWildcardMiddleware(options)
+  ];
+}

File tasks/options/copy.js

+module.exports = {
+  // These copy tasks happen before transpile or hinting. They
+  // prepare the build pipeline by moving JavaScript files to
+  // tmp/javascript.
+  //
+  "prepare": {
+    files: [{
+      expand: true,
+      cwd: 'app/',
+      src: '**/*.js',
+      dest: 'tmp/javascript/app'
+    },
+    {
+      expand: true,
+      cwd: 'tests/',
+      src: ['**/*.js', '!test_helper.js', '!test_loader.js', '!vendor/**/*.js'],
+      dest: 'tmp/javascript/tests/'
+    }]
+  },
+  // Stage moves files to their final destinations after the rest
+  // of the build cycle has run.
+  //
+  "stage": {
+    files: [{
+      expand: true,
+      cwd: 'tests/',
+      src: ['index.html', 'test_helper.js', 'test_loader.js', 'vendor/**/*'],
+      dest: 'tmp/public/tests/'
+    },
+    {
+      expand: true,
+      cwd: 'public/',
+      src: ['**'],
+      dest: 'tmp/public/'
+    }]
+  },
+  "vendor": {
+    src: [
+      'vendor/**/*.js',
+      'vendor/**/*.css',
+      'vendor/**/*.{eot,otf,svg,ttf,woff}',
+      'vendor/**/*.{png,jpg}'
+    ],
+    dest: 'tmp/public/'
+  },
+  "dist": {
+    files: [{
+      expand: true,
+      cwd: 'tmp/public',
+      src: ['**', '!coverage'],
+      dest: 'dist/'
+    }]
+  },
+};

File tasks/options/dom_munger.js

+module.exports = {
+  distEmber: {
+    options: {
+      //Point the index.html file at ember-prod js instead of dev version
+      update: {
+        selector:'script[src="/vendor/ember/index.js"]',
+        attribute:'src',
+        value:'/vendor/ember-prod/index.js'
+      }
+    },
+    src: 'tmp/public/index.html'
+  },
+  distHandlebars: {
+    options: {
+      update: {
+        selector:'script[src="/vendor/handlebars/handlebars.js"]',
+        attribute:'src',
+        value:'/vendor/handlebars/handlebars.runtime.js'
+      }
+    },
+    src: 'tmp/public/index.html'
+  }
+};

File tasks/options/emberTemplates.js

+module.exports = {
+  options: {
+    templateBasePath: /app\/templates\//,
+    templateFileExtensions: /\.(hbs|hjs|handlebars)/
+  },
+  debug: {
+    options: {
+      precompile: false
+    },
+    src: "app/templates/**/*.{hbs,hjs,handlebars}",
+    dest: "tmp/public/assets/templates.js"
+  },
+  dist: {
+    src: "<%= emberTemplates.debug.src %>",
+    dest: "<%= emberTemplates.debug.dest %>"
+  }
+};

File tasks/options/jshint.js

+module.exports = {
+  all: {
+    src: [
+      'Gruntfile.js',
+      'app/**/*.js'
+    ]
+  },
+  options: {
+    jshintrc: '.jshintrc',
+    force: true
+  }
+};

File tasks/options/karma.js

+module.exports = {
+  options: {
+    configFile: 'karma.conf.js',
+    browsers: ['Chrome'],
+    reporters: ['coverage', 'dots']
+  },
+  ci: {
+    singleRun: true,
+    browsers: ['PhantomJS']
+  },
+  test: {
+    singleRun: true
+  },
+  server: {
+    background: true,
+    coverageReporter: {
+      type : ['html'],
+      dir : 'coverage/'
+    }
+  },
+  browsers: {
+    singleRun: true,
+    browsers: ['Chrome',
+               'ChromeCanary',
+               'Firefox',
+               // 'Safari',  // enable plugin in karma.conf.js to use
+               'PhantomJS']
+  }
+};

File tasks/options/less.js

+module.exports = {
+  compile: {
+    files: {
+      'tmp/public/assets/app.css': 'app/styles/**/*.less'
+    }
+  }
+};

File tasks/options/rev.js

+module.exports = {
+  dist: {
+    files: {
+      src: [
+        'dist/assets/app.min.js',
+        'dist/assets/vendor.min.js',
+        'dist/assets/app.css',
+        'dist/assets/vendor.css'
+      ]
+    }
+  }
+}

File tasks/options/sass.js

+module.exports = {
+  compile: {
+    files: {
+      'tmp/public/assets/app.css': 'app/styles/**/*.scss'
+    }
+  }
+};

File tasks/options/stylus.js

+module.exports = {
+  compile: {
+    files: {
+      'tmp/public/assets/app.css': 'app/styles/**/*.styl'
+    }
+  }
+};

File tasks/options/transpile.js

+var grunt = require('grunt');
+
+module.exports = {
+  "tests": {
+    type: 'amd',
+    moduleName: function(path) {
+      return grunt.config.process('<%= pkg.namespace %>/tests/') + path;
+    },
+    files: [{
+      expand: true,
+      cwd: 'tmp/javascript/tests/',
+      src: '**/*.js',
+      dest: 'tmp/transpiled/tests/'
+    }]
+  },
+  "app": {
+    type: 'amd',
+    moduleName: function(path) {
+      return grunt.config.process('<%= pkg.namespace %>/') + path;
+    },
+    files: [{
+      expand: true,
+      cwd: 'tmp/javascript/app/',
+      src: '**/*.js',
+      dest: 'tmp/transpiled/app/'
+    }]
+  }
+};

File tasks/options/usemin.js

+module.exports = {
+  html: ['dist/index.html'],
+};

File tasks/options/useminPrepare.js

+module.exports = {
+  html: 'tmp/public/index.html',
+  options: {
+    dest: 'dist/'
+  }
+};

File tasks/options/watch.js

+module.exports = {
+  main: {
+    files: ['app/**/*', 'public/**/*', 'tests/**/*'],
+    tasks: ['build:debug']
+  },
+  test: {
+    files: ['app/**/*', 'public/**/*', 'tests/**/*'],
+    tasks: ['build:debug', 'karma:server:run']
+  },
+  options: {
+    debounceDelay: 200
+  }
+};

File tests/acceptance/index_test.js

+import Index from 'appkit/routes/index';
+import App from 'appkit/app';
+
+module("Acceptances - Index", {
+  setup: function(){
+    App.reset();
+  }
+});
+
+test("index renders", function(){
+  expect(3);
+
+  visit('/').then(function(){
+    ok(exists("h2:contains('Welcome to Ember.js')"));
+
+    var list = find("ul li");
+    equal(list.length, 3);
+    equal(list.text(), "redyellowblue");
+  });
+});

File tests/index.html

+<!DOCTYPE html>
+<html>
+<head>
+  <title>App Kit</title>
+  <link rel="stylesheet" href="/assets/app.css"    media="all">
+  <link rel="stylesheet" href="/vendor/qunit/qunit/qunit.css"   media="all">
+  <style>
+    #ember-testing-container {
+      position: absolute;
+      background: white;
+      bottom: 0;
+      right: 0;
+      width: 640px;
+      height: 384px;
+      overflow: auto;
+      z-index: 9999;
+      border: 1px solid #ccc;
+    }
+    #ember-testing {
+      zoom: 50%;
+    }
+  </style>
+
+  <script src="/vendor/jquery/jquery.js"></script>
+  <script src="/vendor/handlebars/handlebars.js"></script>
+  <script src="/vendor/ember/index.js"></script>
+  <script src="/vendor/loader.js"></script>
+
+  <script src="/assets/app.js"></script>
+  <script src="/assets/templates.js"></script>
+
+  <script src="/vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+  <div id="qunit"></div>
+  <div id="qunit-fixture"></div>
+  <script src="/tests/test_helper.js"></script>
+  <script src="/tests/tests.js"></script>
+  <script src="/tests/test_loader.js"></script>
+</body>
+</html>

File tests/test_helper.js

+document.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');
+
+Ember.testing = true;
+
+var App = requireModule('appkit/app');
+
+App.rootElement = '#ember-testing';
+App.setupForTesting();
+App.injectTestHelpers();
+
+function exists(selector) {
+  return !!find(selector).length;
+}
+
+function equal(actual, expected, message) {
+  message = message || QUnit.jsDump.parse(expected) + " expected but was " + QUnit.jsDump.parse(actual);
+  QUnit.equal.call(this, expected, actual, message);
+}
+
+window.exists = exists;
+window.equal = equal;
+
+Ember.Container.prototype.stub = function(fullName, instance) {
+  instance.destroy = instance.destroy || function() {};
+  this.cache.dict[fullName] = instance;
+};

File tests/test_loader.js

+// TODO: load based on params
+Ember.keys(define.registry).filter(function(key) {
+  return (/\_test/).test(key);
+}).forEach(requireModule);

File tests/unit/.gitkeep

Empty file added.

File tests/unit/routes/index_test.js

+import Index from 'appkit/routes/index';
+import App from 'appkit/app';
+
+var route;
+
+module("Unit - IndexRoute", {
+  setup: function(){
+    route = App.__container__.lookup('route:index');
+  }
+});
+
+test("it exists", function(){
+  ok(route);
+  ok(route instanceof Ember.Route);
+});
+
+test("#model", function(){
+  deepEqual(route.model(), ['red', 'yellow', 'blue']);
+});