=== modified file 'Makefile'
--- Makefile	2013-04-30 14:56:08 +0000
+++ Makefile	2013-04-30 19:27:26 +0000
@@ -320,6 +320,7 @@
 	build-debug/juju-ui/subapps \
 	build-debug/juju-ui/views \
 	build-debug/juju-ui/widgets \
+	build-debug/juju-ui/plugins \
 	build-debug/juju-ui/assets/javascripts \
 	build-debug/juju-ui/templates.js
 
@@ -368,6 +369,7 @@
 	ln -sf "$(PWD)/app/subapps" build-debug/juju-ui/
 	ln -sf "$(PWD)/app/views" build-debug/juju-ui/
 	ln -sf "$(PWD)/app/widgets" build-debug/juju-ui/
+	ln -sf "$(PWD)/app/plugins" build-debug/juju-ui/
 	ln -sf "$(PWD)/app/assets/javascripts/yui/yui/yui-debug.js" \
 		build-debug/juju-ui/assets/all-yui.js
 	ln -sf "$(PWD)/build-shared/juju-ui/templates.js" build-debug/juju-ui/

=== modified file 'app/modules-debug.js'
--- app/modules-debug.js	2013-04-30 15:56:54 +0000
+++ app/modules-debug.js	2013-04-30 19:27:26 +0000
@@ -114,6 +114,11 @@
           fullpath: '/juju-ui/assets/javascripts/sub-app.js'
         },
 
+        // Plugins
+        'textarea-autosize': {
+          fullpath: '/juju-ui/plugins/textarea-autosize.js'
+        },
+
         // Views
         'juju-landscape': {
           fullpath: '/juju-ui/views/landscape.js'

=== added directory 'app/plugins'
=== added file 'app/plugins/textarea-autosize.js'
--- app/plugins/textarea-autosize.js	1970-01-01 00:00:00 +0000
+++ app/plugins/textarea-autosize.js	2013-04-30 19:27:26 +0000
@@ -0,0 +1,191 @@
+'use strict';
+
+/**
+ A plugin for textareas that causes them to automatically resize when users
+ enter additional text.  The textarea will expand vertically without adding
+ scrollbars.  An enhancement would be to specify a maximum height after which
+ scrollbars are added.
+
+ Usage: Y.all(textareas).plug(Y.Autosize)
+ */
+
+YUI.add('textarea-autosize', function(Y) {
+
+  var ns = Y.namespace('juju.plugins');
+
+  ns.TextareaAutosize = Y.Base.create('textarea-autosize', Y.Plugin.Base, [], {
+
+    active: false,
+    boxOffset: 0,
+    minHeight: undefined,
+    mirrorElement: Y.Node.create(
+        '<textarea data-autosize="true" tabindex="-1" ' +
+        'style="position:absolute; top:-999px; left:0; ' +
+        'right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; ' +
+        '-webkit-box-sizing:content-box; box-sizing:content-box; ' +
+        'word-wrap:break-word; height:0 !important; ' +
+        'min-height:0 !important; overflow:hidden;"/>'),
+
+    stylesToMirror: [
+      'fontFamily',
+      'fontSize',
+      'fontWeight',
+      'fontStyle',
+      'letterSpacing',
+      'textTransform',
+      'wordSpacing',
+      'textIndent',
+      'lineHeight'
+    ],
+
+    /**
+     @method initializer
+     */
+    initializer: function() {
+      var textarea = this.get('host');
+      //textarea.on('input', this._autoSizeHandler, this);
+      textarea.on('keydown', this._autoSizeHandler, this);
+      textarea.on('valuechange', this._autoSizeHandler, this);
+      this.minHeight = this._getMinHeight();
+      this._initMirror();
+    },
+
+    /**
+     Get the minimum height for the textarea.
+     @method _getMinHeight
+     @private
+     @return {Int} the min height.
+     */
+    _getMinHeight: function() {
+      var borderBox = 'border-box',
+          textarea = this.get('host'),
+          // XXX bac: is clientHeight what we want here?
+          height = textarea.get('clientHeight');
+
+      if (textarea.getStyle('box-sizing') === borderBox ||
+          textarea.getStyle('-moz-box-sizing') === borderBox ||
+          textarea.getStyle('-webkit-box-sizing') === borderBox) {
+        // XXX: scrollHeight is the same as outerHeight?
+        this.boxOffset = textarea.get('scrollHeight') - height;
+      }
+
+      return Math.max(
+          this._toInt(textarea.getStyle('minHeight')) - this.boxOffset,
+          height);
+    },
+
+    /**
+     Handle input events for the textarea and perform the resizing if
+     required.
+
+     @method _autoSizeHandler
+     @private
+     */
+    _autoSizeHandler: function(evt) {
+      this._adjust();
+    },
+
+    /**
+     Initialize the invisible mirrorElement.
+
+     @method _initMirror
+     @private
+     */
+    _initMirror: function() {
+      var textarea = this.get('host');
+      if (!this.mirrorElement.ancestor('body')) {
+        Y.one('body').append(this.mirrorElement);
+        this.mirrorElement.set('value', '\n\n\n');
+        this.mirrorElement.set('scrollTop', 9e4);
+      }
+
+      this.mirrorElement.set('className', textarea.get('className'));
+      Y.Array.each(this.stylesToMirror, function(v) {
+        this.mirrorElement.setStyle(v, textarea.getStyle(v));
+      }, this);
+    },
+
+    /**
+     Helper to convert strings to a base 10 int.
+
+     @method _toInt
+     @private
+     @param {String} v String to be parsed.
+     @return {Int} integer value or 'NaN'.
+     */
+    _toInt: function(s) {
+      return parseInt(s, 10);
+    },
+
+    /**
+     Adjust the textarea based on user input.  This method is called on every
+     keystroke so it needs to be speedy.
+
+     XXX bac: the original JQuery version mentioned trying to use bare
+     Javascript.  The conversion uses lots of YUI because it was the most
+     straightforward.  Does this need to be reverted to bare JS?
+
+     @method _adjust
+     @private
+     */
+    _adjust: function() {
+      var height,
+          overflow,
+          original;
+
+      // the active flag keeps IE from tripping all over itself.  Otherwise
+      // actions in the adjust function will cause IE to call adjust again.
+      if (!this.active) {
+        var textarea = this.get('host');
+        this.active = true;
+        // this.mirrorElement.value = textarea.value + options.append;
+        this.mirrorElement.set('value', textarea.get('value'));
+        this.mirrorElement.setStyle('overflowY',
+            textarea.getStyle('overflowY'));
+        original = this._toInt(textarea.getStyle('height'));
+
+        // Update the width in case the original textarea width has changed
+        // A floor of 0 is needed because IE8 returns a negative
+        // value for hidden textareas, raising an error.
+        this.mirrorElement.setStyle('width',
+            Math.max(textarea.getStyle('width'), 0));
+
+        // Get the height of the mirror.
+        height = this._toInt(this.mirrorElement.get('scrollHeight'));
+        var maxHeight = this._toInt(textarea.getStyle('maxHeight'));
+        // Opera returns '-1px' when max-height is set to 'none'.
+        maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
+        if (height > maxHeight) {
+          height = maxHeight;
+          overflow = 'scroll';
+        } else if (height < this.minHeight) {
+          height = this.minHeight;
+        }
+        height += this.boxOffset;
+        textarea.setStyle('overflowY', overflow || 'hidden');
+
+        if (original !== height) {
+          // XXX: all of this code assumes sizes are in px in the
+          // CSS.  Will break if specified in other units.
+          textarea.setStyle('height', height + 'px');
+        }
+      }
+      // XXX: The original had a very short timeout to avoid IE wetting
+      // itself. Unsure if it is still needed.
+      this.active = false;
+    }
+  },{
+
+    NS: 'TextareaAutosize'
+  }
+  );
+},
+
+'0.1.0', {
+  requires: [
+    'node',
+    'event',
+    'base-build',
+    'plugin'
+  ]
+});

=== modified file 'test/index.html'
--- test/index.html	2013-04-25 20:03:36 +0000
+++ test/index.html	2013-04-30 19:27:26 +0000
@@ -75,6 +75,7 @@
   <script src="test_sub_app.js"></script>
   <script src="test_tabview.js"></script>
   <script src="test_templates.js"></script>
+  <script src="test_textarea_autosize.js"></script>
   <script src="test_topology.js"></script>
   <script src="test_topology_relation.js"></script>
   <script src="test_unit_view.js"></script>

=== added file 'test/test_textarea_autosize.js'
--- test/test_textarea_autosize.js	1970-01-01 00:00:00 +0000
+++ test/test_textarea_autosize.js	2013-04-30 19:27:26 +0000
@@ -0,0 +1,90 @@
+'use strict';
+
+describe('textarea autosize plugin', function() {
+  var Y, container, textarea;
+
+  before(function(done) {
+    Y = YUI(GlobalConfig).use([
+      'textarea-autosize',
+      'node-event-simulate'],
+    function(Y) {
+      done();
+    });
+  });
+
+  beforeEach(function() {
+    container = Y.Node.create('<div id="container"></div>');
+    textarea = Y.Node.create('<textarea class="autosize"></textarea>');
+    container.append(textarea);
+    Y.one(document.body).prepend(container);
+  });
+
+  afterEach(function() {
+    textarea.remove().destroy(true);
+    container.remove().destroy(true);
+  });
+
+  var setAndTrigger = function(textarea, v) {
+    textarea.set('value', v);
+    textarea.focus();
+    textarea.simulate('keydown', {keyCode: 83});
+  };
+
+  it('plugs into a textarea', function() {
+    var node = Y.one('textarea.autosize');
+    node.plug(Y.juju.plugins.TextareaAutosize);
+    assert.isDefined(node.TextareaAutosize);
+  });
+
+  it('plugs into lots of textareas', function() {
+    var textarea2 = Y.Node.create('<textarea class="autosize"></textarea>');
+    var textarea3 = Y.Node.create('<textarea></textarea>');
+    container.append(textarea2);
+    container.append(textarea3);
+    var nodes = Y.all('textarea');
+    nodes.plug(Y.juju.plugins.TextareaAutosize);
+    assert.isDefined(textarea.TextareaAutosize);
+    assert.isDefined(textarea2.TextareaAutosize);
+    assert.isDefined(textarea3.TextareaAutosize);
+  });
+
+  it('plugs into lots of textareas, but selectively', function() {
+    var textarea2 = Y.Node.create('<textarea class="autosize"></textarea>');
+    var textarea3 = Y.Node.create('<textarea></textarea>');
+    container.append(textarea2);
+    container.append(textarea3);
+    // Textareas with autosize class.
+    var nodes = Y.all('textarea.autosize');
+    nodes.plug(Y.juju.plugins.TextareaAutosize);
+    assert.isDefined(textarea.TextareaAutosize);
+    assert.isDefined(textarea2.TextareaAutosize);
+    assert.isUndefined(textarea3.TextareaAutosize);
+  });
+
+  it('calculates the minHeight', function() {
+    var node = Y.one('textarea.autosize');
+    node.plug(Y.juju.plugins.TextareaAutosize);
+    var original = textarea.TextareaAutosize._getMinHeight();
+    setAndTrigger(textarea, 'how\nnow\nbrown\ncow\nand\nstuff');
+    var bigger = textarea.TextareaAutosize._getMinHeight();
+    // The size should have grown.
+    assert.isTrue(bigger > original);
+    setAndTrigger(textarea, 'one line');
+    var smaller = textarea.TextareaAutosize._getMinHeight();
+    assert.isTrue(smaller < bigger);
+  });
+
+  it('sets the height on the textarea', function() {
+    var node = Y.one('textarea.autosize');
+    node.plug(Y.juju.plugins.TextareaAutosize);
+    var original = parseInt(textarea.getStyle('height'), 10);
+    setAndTrigger(textarea, 'how\nnow\nbrown\ncow\nand\nstuff');
+    var bigger = parseInt(textarea.getStyle('height'), 10);
+    // The height should have grown...
+    assert.isTrue(bigger > original);
+    setAndTrigger(textarea, 'one line');
+    var smaller = parseInt(textarea.getStyle('height'), 10);
+    // ..and then shrunk.
+    assert.isTrue(smaller < bigger);
+  });
+});

