Fix error banner conflict
diff --git a/Jenkinsfile b/Jenkinsfile
index 50bc00b..7fe54bb 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -18,11 +18,13 @@
  */
 
 node(label: 'ubuntu') {
-    properties([
-        pipelineTriggers([
-            issueCommentTrigger('.*test this please.*')
+    if (env.CHANGE_ID) {
+        properties([
+            pipelineTriggers([
+                issueCommentTrigger('.*test this please.*')
+            ])
         ])
-    ])
+    }
 
     catchError {
         def environmentDockerImage
@@ -46,15 +48,6 @@
                     sh 'mvn clean install -Duser.home=/var/maven -Duser.name=jenkins'
                 }
             }
-
-            // Conditional stage to deploy artifacts, when not building a PR
-            if (env.CHANGE_ID == null) {
-                stage('Deploy artifacts') {
-                    environmentDockerImage.inside('-i --name brooklyn-${DOCKER_TAG} -v ${WORKSPACE}/.m2:/var/maven/.m2 --mount type=bind,source="${HOME}/.m2/settings.xml",target=/var/maven/.m2/settings.xml,readonly -v ${WORKSPACE}:/usr/build -w /usr/build') {
-                        sh 'mvn deploy -DskipTests -Duser.home=/var/maven -Duser.name=jenkins'
-                    }
-                }
-            }
         }
     }
 
diff --git a/pom.xml b/pom.xml
index fd202a4..8c0e62d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,8 +25,7 @@
         <groupId>org.apache.brooklyn</groupId>
         <artifactId>brooklyn-parent</artifactId>
         <version>1.0.0-SNAPSHOT</version>  <!-- BROOKLYN_VERSION -->
-        <!-- do not link to sibling directory as that breaks builds where this code is embedded, e.g. for branding;
-             we require that brooklyn-server is built first (this is needed anyway for the modularity java code) -->
+        <relativePath>${brooklyn.ui.relativePath.to.brooklyn.server.parent}</relativePath>
     </parent>
 
     <groupId>org.apache.brooklyn.ui</groupId>
@@ -85,6 +84,8 @@
         <brooklyn.version>1.0.0-SNAPSHOT</brooklyn.version><!-- BROOKLYN_VERSION -->
         <build.version>${revision}</build.version>
         <build.name>Apache Brooklyn</build.name>
+        <brooklyn.ui.relativePath.to.brooklyn.server.parent>../brooklyn-server/parent/</brooklyn.ui.relativePath.to.brooklyn.server.parent>
+
         <buildnumber-maven-plugin.version>1.4</buildnumber-maven-plugin.version>
 
         <!-- versions from brooklyn server which have a different var name here -->
@@ -98,7 +99,7 @@
         <maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
         <maven-resources-plugin.version>3.0.1</maven-resources-plugin.version>
         <maven-war-plugin.version>3.0.0</maven-war-plugin.version>
-        <frontend-maven-plugin.version>1.3</frontend-maven-plugin.version>
+        <frontend-maven-plugin.version>1.9.0</frontend-maven-plugin.version>
         <pax-web.version>7.2.3</pax-web.version>
         <pax-web-extender-whiteboard.version>${pax-web.version}</pax-web-extender-whiteboard.version>
 
diff --git a/ui-modules/app-inspector/app/components/entity-tree/entity-tree.directive.js b/ui-modules/app-inspector/app/components/entity-tree/entity-tree.directive.js
index 76183e7..f6d3426 100644
--- a/ui-modules/app-inspector/app/components/entity-tree/entity-tree.directive.js
+++ b/ui-modules/app-inspector/app/components/entity-tree/entity-tree.directive.js
@@ -43,11 +43,14 @@
     return {
         restrict: 'E',
         template: entityTreeTemplate,
-        controller: ['$scope', '$state', 'applicationApi', 'iconService', 'brWebNotifications', controller],
+        scope: {
+           sortReverse: '=',
+        },
+        controller: ['$scope', '$state', 'applicationApi', 'entityApi', 'iconService', 'brWebNotifications', controller],
         controllerAs: 'vm'
     };
 
-    function controller($scope, $state, applicationApi, iconService, brWebNotifications) {
+    function controller($scope, $state, applicationApi, entityApi, iconService, brWebNotifications) {
         $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
 
         let vm = this;
diff --git a/ui-modules/app-inspector/app/components/entity-tree/entity-tree.html b/ui-modules/app-inspector/app/components/entity-tree/entity-tree.html
index a6c1a01..67c0ce1 100644
--- a/ui-modules/app-inspector/app/components/entity-tree/entity-tree.html
+++ b/ui-modules/app-inspector/app/components/entity-tree/entity-tree.html
@@ -16,7 +16,7 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<entity-node ng-repeat="application in vm.applications track by application.id" entity="application" application-id="application.id"></entity-node>
+<entity-node ng-repeat="application in vm.applications | orderBy: sortReverse? '-creationTimeUtc': 'creationTimeUtc' track by application.id" entity="application" application-id="application.id"></entity-node>
 <p class="expand-tree-message text-center" ng-if="vm.applications.length > 0"><small><kbd>shift</kbd> + <kbd>{{navigator.appVersion.indexOf("Mac") !== -1 ? '⌘' : '⊞'}}</kbd> + click to expand all children</small></p>
 <div class="empty-tree text-muted text-center" ng-if="vm.applications.length === 0">
     <hr />
diff --git a/ui-modules/app-inspector/app/views/main/main.controller.js b/ui-modules/app-inspector/app/views/main/main.controller.js
index 07280f8..c691a10 100644
--- a/ui-modules/app-inspector/app/views/main/main.controller.js
+++ b/ui-modules/app-inspector/app/views/main/main.controller.js
@@ -27,11 +27,16 @@
     controllerAs: 'ctrl'
 };
 
+const savedSortReverse = 'app-inspector-sort-reverse';
+
 export function mainController($scope, $q, brWebNotifications) {
     $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
 
     let ctrl = this;
 
+    ctrl.sortReverse = localStorage && localStorage.getItem(savedSortReverse) !== null ?
+        JSON.parse(localStorage.getItem(savedSortReverse)) :
+        true;
     brWebNotifications.supported.then(() => {
         ctrl.isNotificationsSupported = true;
     }).catch(() => {
@@ -48,6 +53,17 @@
         ctrl.isNotificationsBlocked = permission === 'denied';
     });
 
+    ctrl.toggleSortOrder = () => {
+        ctrl.sortReverse = !ctrl.sortReverse;
+        if (localStorage) {
+            try {
+                localStorage.setItem(savedSortReverse, JSON.stringify(ctrl.sortReverse));
+            } catch (ex) {
+                $log.error('Cannot save app sort preferences: ' + ex.message);
+            }
+        }
+    }
+
     ctrl.toggleNotifications = () => {
         brWebNotifications.isEnabled().then(() => {
             return brWebNotifications.setEnable(false);
diff --git a/ui-modules/app-inspector/app/views/main/main.less b/ui-modules/app-inspector/app/views/main/main.less
index d14fed7..527554f 100644
--- a/ui-modules/app-inspector/app/views/main/main.less
+++ b/ui-modules/app-inspector/app/views/main/main.less
@@ -20,11 +20,9 @@
 
   .entity-tree-header {
     display: flex;
+    justify-content: space-between;
 
-    .entity-tree-title {
-      flex-grow: 1;
-    }
-    .entity-tree-action {
+    .entity-tree-title, .entity-tree-sort-action, .entity-tree-action {
       flex-shrink: 1;
       vertical-align: middle;
       margin-left: 0.5em;
diff --git a/ui-modules/app-inspector/app/views/main/main.template.html b/ui-modules/app-inspector/app/views/main/main.template.html
index 0911647..c7a90cd 100644
--- a/ui-modules/app-inspector/app/views/main/main.template.html
+++ b/ui-modules/app-inspector/app/views/main/main.template.html
@@ -22,29 +22,36 @@
             <br-card>
                 <br-card-content class="entity-tree">
                     <div class="entity-tree-header">
-                        <h2 class="entity-tree-title">Applications</h2>
-
-                        <button class="btn btn-link entity-tree-action"
+                        <div class="entity-tree-header-section">
+                            <h2 class="entity-tree-title">Applications</h2>
+                            <button class="btn btn-sm btn-default entity-tree-sort-action" ng-click="ctrl.toggleSortOrder($event)">
+                                <div>
+                                    <span class="glyphicon" ng-class="ctrl.sortReverse ? 'fa fa-sort-amount-desc' : 'fa fa-sort-amount-asc'"></span>
+                                </div>
+                            </button>
+                        </div>
+                        <div class="entity-tree-header-section">
+                            <button class="btn btn-link entity-tree-action"
                                 ng-class="{'btn-sm': !ctrl.isNotificationsBlocked, 'btn-xs': ctrl.isNotificationsBlocked}"
                                 ng-if="ctrl.isNotificationsSupported"
                                 ng-disabled="ctrl.isNotificationsBlocked"
                                 ng-click="ctrl.toggleNotifications()">
-                            <i class="fa fa-bell notifications" ng-if="!ctrl.isNotificationsBlocked"
-                                ng-class="{'active': ctrl.isNotificationsEnabled}"></i>
-                            <span class="fa-stack" ng-if="ctrl.isNotificationsBlocked"
-                                  uib-tooltip="Notifications are currently blocked. You can allow them in your browser settings"
-                                  tooltip-placement="left">
-                                <i class="fa fa-bell fa-stack-1x"></i>
-                                <i class="fa fa-ban fa-stack-2x text-danger"></i>
-                            </span>
-                        </button>
-
-                        <a href="/brooklyn-ui-blueprint-composer" class="btn btn-sm btn-default entity-tree-action">
-                            <i class="fa fa-plus"></i>
-                        </a>
+                                <i class="fa fa-bell notifications" ng-if="!ctrl.isNotificationsBlocked"
+                                   ng-class="{'active': ctrl.isNotificationsEnabled}"></i>
+                                <span class="fa-stack" ng-if="ctrl.isNotificationsBlocked"
+                                      uib-tooltip="Notifications are currently blocked. You can allow them in your browser settings"
+                                      tooltip-placement="left">
+                                    <i class="fa fa-bell fa-stack-1x"></i>
+                                    <i class="fa fa-ban fa-stack-2x text-danger"></i>
+                                </span>
+                            </button>
+                            <a href="/brooklyn-ui-blueprint-composer" class="btn btn-sm btn-default entity-tree-action">
+                                <i class="fa fa-plus"></i>
+                            </a>
+                        </div>
                     </div>
 
-                    <entity-tree></entity-tree>
+                    <entity-tree sort-reverse="ctrl.sortReverse"></entity-tree>
                 </br-card-content>
             </br-card>
 
diff --git a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
index 5e43ab7..eec14e5 100644
--- a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
@@ -700,7 +700,7 @@
         });
 
         relationships = Array.from(entity.config.values())
-            .filter(config => config[DSL_ENTITY_SPEC] && config[DSL_ENTITY_SPEC] instanceof Entity)
+            .filter(config => config && config[DSL_ENTITY_SPEC] && config[DSL_ENTITY_SPEC] instanceof Entity)
             .map(config => config[DSL_ENTITY_SPEC])
             .reduce((relationships, spec) => {
             return relationships.concat(getRelationships(spec));
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
index 3cc94b9..00abd58 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
@@ -91,6 +91,9 @@
     .entity-type-header {
       font-size: 105%;
       margin-right: 1em;
+      // allow this to wrap, as it's important to be able to read it;
+      // break anywhere in case it's of the form my.type.HasGotAVeryVeryVeryLongName
+      word-break: break-all;
     }
     .label.version {
       vertical-align: 2px;
diff --git a/ui-modules/blueprint-composer/app/views/main/main.controller.js b/ui-modules/blueprint-composer/app/views/main/main.controller.js
index 886b4a1..fc1ac1a 100644
--- a/ui-modules/blueprint-composer/app/views/main/main.controller.js
+++ b/ui-modules/blueprint-composer/app/views/main/main.controller.js
@@ -131,9 +131,28 @@
         
         yaml = edit.type.plan.data;
     }
+    
+    vm.isGraphicalMode = () => {
+        return $state.includes(graphicalState.name);
+    };
+    vm.isYamlMode = () => {
+        return $state.includes(yamlState.name);
+    };
 
     if (yaml) {
-        blueprintService.setFromYaml(yaml);
+        if (vm.isYamlMode()) {
+            // don't set blueprint; yaml mode will take from "initial yaml" 
+            blueprintService.reset();
+            $scope.initialYaml = yaml;
+        } else {
+            try {
+                blueprintService.setFromYaml(yaml);
+            } catch (e) {
+                console.warn("YAML supplied for editing is not valid for a blueprint. It will be ignored unless opened in the YAML editor:", e);
+                blueprintService.reset();
+                $scope.initialYaml = yaml;
+            }
+        }
     } else {
         blueprintService.reset();
     }
@@ -147,10 +166,6 @@
         deployApplication();
     };
 
-    vm.isGraphicalMode = () => {
-        return $state.includes(graphicalState.name);
-    };
-
     vm.getAllActions = () => {
         return actionService.getActions();
     }
diff --git a/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js b/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
index bfd47f2..f9390ae 100644
--- a/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
+++ b/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
@@ -39,6 +39,11 @@
         brSnackbar.create(`Cannot load blueprint: ${ex.message}`);
         vm.yaml = '';
     }
+    if ($scope.initialYaml && !vm.yaml) {
+        // either yaml was supplied and yaml mode requested, skipping blueprint setup,
+        // or the yaml was invalid, an error logged, and this was recorded
+        vm.yaml = $scope.initialYaml; 
+    }
 
     if (!CodeMirror.lint.hasOwnProperty('yaml-composer')) {
         CodeMirror.registerGlobalHelper('lint', 'yaml-composer', mode => mode.name === 'yaml', (text, options, cm) => {
diff --git a/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js b/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
index 996a156..9309e50 100644
--- a/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
+++ b/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
@@ -71,6 +71,11 @@
         return disabled.indexOf(vm.provider) > -1;
     };
 
+    vm.isEndpointRequired = function () {
+        let required = ['jclouds:azurecompute-arm'];
+        return required.indexOf(vm.provider) > -1;
+    }
+
     vm.save = function () {
         let config = angular.copy(vm.config);
         if (vm.region) {
diff --git a/ui-modules/location-manager/app/views/wizard/cloud/cloud.template.html b/ui-modules/location-manager/app/views/wizard/cloud/cloud.template.html
index 418e2fb..7d22072 100644
--- a/ui-modules/location-manager/app/views/wizard/cloud/cloud.template.html
+++ b/ui-modules/location-manager/app/views/wizard/cloud/cloud.template.html
@@ -67,7 +67,10 @@
 
                     <div class="form-group" ng-class="{'has-error': form.endpoint.$invalid && form.endpoint.$touched}">
                         <label class="control-label" for="endpoint">Cloud endpoint</label>
-                        <input ng-model="vm.endpoint" type="text" class="form-control" id="endpoint" name="endpoint" ng-disabled="vm.isEndpointDisabled()" placeholder="If using a private cloud, the URL to connect to it is required">
+                        <input ng-model="vm.endpoint" type="text" class="form-control" id="endpoint" name="endpoint" ng-required="vm.isEndpointRequired()" ng-disabled="vm.isEndpointDisabled()" placeholder="If using a private cloud, the URL to connect to it is required">
+                        <p class="help-block" ng-show="form.$submitted || form.endpoint.$touched">
+                            <span ng-show="form.endpoint.$error.required">You must specify an endpoint with this provider</span>
+                        </p>
                     </div>
 
                     <div class="form-group" ng-class="{'has-error': form.identity.$invalid && form.identity.$touched}">
diff --git a/ui-modules/utils/catalog-uploader/catalog-uploader.html b/ui-modules/utils/catalog-uploader/catalog-uploader.html
index 7288484..0ee103d 100644
--- a/ui-modules/utils/catalog-uploader/catalog-uploader.html
+++ b/ui-modules/utils/catalog-uploader/catalog-uploader.html
@@ -23,7 +23,7 @@
             <div class="col-sm-12 text-center">
                 <p><i class="fa fa-3x fa-upload"></i></p>
                 <p>
-                    <input type="file" name="files" id="files" multiple onchange="angular.element(this).scope().filesChanged(this)" />
+                    <input type="file" name="files" id="files" multiple custom-on-change="filesChanged" />
                     <label for="files" ng-click="choose()"><strong>Choose files</strong><span class="drag-upload"> or drag & drop them here</span>.</label>
                 </p>
             </div>
diff --git a/ui-modules/utils/catalog-uploader/catalog-uploader.js b/ui-modules/utils/catalog-uploader/catalog-uploader.js
index 9e4c47f..d7480a3 100644
--- a/ui-modules/utils/catalog-uploader/catalog-uploader.js
+++ b/ui-modules/utils/catalog-uploader/catalog-uploader.js
@@ -33,6 +33,7 @@
  */
 angular.module(MODULE_NAME, [catalogApi])
     .service('brooklynCatalogUploader', ['$q', 'catalogApi', catalogUploaderService])
+    .directive('customOnChange', customOnChangeDirective)
     .directive('brooklynCatalogUploader', ['$compile', 'brooklynCatalogUploader', catalogUploaderDirective]);
 
 export default MODULE_NAME;
@@ -98,8 +99,8 @@
             element.removeClass('br-drag-active');
         };
 
-        scope.filesChanged = (target)=> {
-            scope.upload(target.files);
+        scope.filesChanged = (event)=> {
+            scope.upload(event.target.files);
         };
 
         scope.upload = (files)=> {
@@ -120,7 +121,9 @@
         };
 
         scope.getCatalogItemUrl = (item)=> {
-            return item.supertypes.includes('org.apache.brooklyn.api.location.Location')
+            let itemTraits = item.tags? item.tags.find(item => item.hasOwnProperty("traits")) : {"traits":[]};
+            return (item.supertypes ? item.supertypes : itemTraits.traits)
+                .includes('org.apache.brooklyn.api.location.Location')
                 ? `/brooklyn-ui-location-manager/#!/location?symbolicName=${item.symbolicName}&version=${item.version}`
                 : `/brooklyn-ui-catalog/#!/bundles/${item.containingBundle.split(':')[0]}/${item.containingBundle.split(':')[1]}/types/${item.symbolicName}/${item.version}`;
         };
@@ -208,3 +211,18 @@
         return defer.promise;
     }
 }
+
+export function customOnChangeDirective() {
+    return {
+        restrict: 'A',
+        link: function (scope, element, attrs) {
+            element.on('change', x => {
+                var onChangeHandler = scope.$eval(attrs.customOnChange);
+                onChangeHandler(x);
+            });
+            element.on('$destroy', function() {
+                element.off();
+            });
+        }
+    };
+}
\ No newline at end of file
diff --git a/ui-modules/utils/quick-launch/quick-launch.js b/ui-modules/utils/quick-launch/quick-launch.js
index e4b1a67..40ff963 100644
--- a/ui-modules/utils/quick-launch/quick-launch.js
+++ b/ui-modules/utils/quick-launch/quick-launch.js
@@ -74,7 +74,7 @@
             $scope.appHasWizard = parsedPlan!=null && !checkForLocationTags(parsedPlan);
             $scope.yamlViewDisplayed = !$scope.appHasWizard;
             $scope.entityToDeploy = {
-                type: $scope.app.symbolicName
+                type: $scope.app.symbolicName + ($scope.app.version ? ':' + $scope.app.version : ''),
             };
             if ($scope.app.config) {
                 $scope.configMap = $scope.app.config.reduce((result, config) => {
@@ -177,21 +177,87 @@
             return yaml.safeDump(newApp);
         }
 
-        function buildComposerYaml() {
+        function buildComposerYaml(validate) {
             if ($scope.yamlViewDisplayed) {
                 return angular.copy($scope.editorYaml);
             } else {
-                let newApp = {
-                    name: $scope.model.name || $scope.app.displayName,
-                };
-                if ($scope.model.location) {
-                    newApp.location = $scope.model.location;
+                let planText = $scope.app.plan.data || "{}";
+                let result = {};
+                
+                // this is set if we're able to parse the plan's text definition, and then:
+                // - we've had to override a field from the plan's text definition, because a value is set _and_ different; or
+                // - the plan's text definition is indented or JSON rather than YAML (not outdented yaml)
+                // and in either case we use the result _object_ ... 
+                // unless we didn't actually change anything, in which case this is ignored
+                let cannotUsePlanText = false;
+                
+                if (validate) {
+                    result = yaml.safeLoad(planText);
+                    if (typeof result !== 'object') {
+                        throw "The plan is not a YAML map, but of type "+(typeof result);
+                    }
+                    if (!result.services) {
+                        throw "The plan does not have any services.";
+                    }
+                    for (const [k,v] of Object.entries(result) ) {
+                       if (planText.indexOf(k)!=0 && planText.indexOf('\n'+k+':')<0) {
+                          // plan is not outdented yaml, can't use its text mode
+                          cannotUsePlanText = true;
+                          break;
+                       }
+                    }
                 }
-                if ($scope.entityToDeploy[BROOKLYN_CONFIG]) {
-                    newApp[BROOKLYN_CONFIG] = $scope.entityToDeploy[BROOKLYN_CONFIG]
+                
+                let newApp = {};
+                
+                let newName = $scope.model.name || $scope.app.displayName;
+                if (newName && newName != result.name) {
+                    newApp.name = newName;
+                    if (result.name) {
+                        delete result.name;
+                        cannotUsePlanText = true;
+                    }
                 }
-                // TODO if plan data has config in the root (unlikely) this will have errors
-                return yaml.safeDump(newApp) + "\n" + $scope.app.plan.data;
+                
+                let newLocation = $scope.model.location;
+                if (newLocation && newLocation != result.location) {
+                    newApp.location = newLocation;
+                    if (result.location) {
+                        delete result.location;
+                        cannotUsePlanText = true;
+                    }
+                }
+
+                let newConfig = $scope.entityToDeploy[BROOKLYN_CONFIG];
+                if (newConfig) {
+                    if (result[BROOKLYN_CONFIG]) {
+                        let oldConfig = result[BROOKLYN_CONFIG];
+                        let mergedConfig = angular.copy(oldConfig);
+                        for (const [k,v] of Object.entries(newConfig) ) {
+                            if (mergedConfig[k] != v) {
+                                cannotUsePlanText = true;
+                                mergedConfig[k] = v;
+                            }
+                        }
+                        if (cannotUsePlanText) {
+                            newApp[BROOKLYN_CONFIG] = mergedConfig;
+                            delete result[BROOKLYN_CONFIG];
+                        }
+                    } else {
+                        newApp[BROOKLYN_CONFIG] = newConfig;
+                    }
+                }
+                
+                // prefer to use the actual yaml input, but if it's not possible
+                let tryMergeByConcatenate = 
+                    Object.keys(newApp).length ?
+                        (yaml.safeDump(newApp) + "\n" + ((validate && cannotUsePlanText) ? yaml.safeDump(result) : planText))
+                        : planText;
+                if (validate) {
+                    // don't think there's any way we'd wind up with invalid yaml but check to be sure
+                    yaml.safeLoad(tryMergeByConcatenate);
+                }
+                return tryMergeByConcatenate;
             }
         }
 
@@ -205,8 +271,19 @@
         }
 
         function openComposer() {
-            window.location.href = '/brooklyn-ui-blueprint-composer/#!/graphical?'+
-                'yaml='+encodeURIComponent(buildComposerYaml());
+            try {
+              window.location.href = '/brooklyn-ui-blueprint-composer/#!/graphical?'+
+                'yaml='+encodeURIComponent(buildComposerYaml(true));
+            } catch (error) {
+              console.warn("Opening composer in YAML text editor mode because we cannot generate a model for this configuration:", error);
+              window.location.href = '/brooklyn-ui-blueprint-composer/#!/yaml?'+
+                'yaml='+encodeURIComponent(
+                    "# This plan may have items which require attention so is being opened in YAML text editor mode.\n"+
+                    "# The YAML was autogenerated by merging the plan with any values provided in UI, but issues were\n"+
+                    "# detected that mean it might not be correct. Please check the blueprint below carefully.\n"+
+                    "\n"+
+                    buildComposerYaml(false));
+            }
         }
 
         function clearError() {
diff --git a/ui-modules/utils/server-status/server-status.template.html b/ui-modules/utils/server-status/server-status.template.html
index c9a692f..906fcb4 100644
--- a/ui-modules/utils/server-status/server-status.template.html
+++ b/ui-modules/utils/server-status/server-status.template.html
@@ -70,7 +70,7 @@
             </tbody>
         </table>
     </div>
-    <div ng-switch-when="NO-CONNECTION">
+    <div ng-switch-when="NO-CONNECTION|OTHER_ERROR" ng-switch-when-separator="|">
         <p>Cannot connect to API server. Please check your network connection. Try closing and re-opening the window and login again in the app. If the problem persists, please contact your administrator.</p>
     </div>
     <div ng-switch-when="UNHEALTHY">
@@ -83,7 +83,6 @@
             <button type="submit" class="btn btn-success"><i class="fa fa-sign-out fa-fw"></i> Logout</button>
         </form>
     </div>
-    </div>
     <div ng-switch-when="SESSION_INVALIDATED|SESSION_AGE_EXCEEDED" ng-switch-when-separator="|">
         <p>Your last session has expired.</p>
         <p>Please login again.</p>
diff --git a/ui-modules/utils/yaml-editor/addon/lint/lint-yaml-brooklyn.js b/ui-modules/utils/yaml-editor/addon/lint/lint-yaml-brooklyn.js
index 2d5e424..e4cf85c 100644
--- a/ui-modules/utils/yaml-editor/addon/lint/lint-yaml-brooklyn.js
+++ b/ui-modules/utils/yaml-editor/addon/lint/lint-yaml-brooklyn.js
@@ -29,38 +29,31 @@
 import catalogVersionSchema from '../schemas/catalog-version.json';
 import rootSchema from '../schemas/root.json';
 
-CodeMirror.registerGlobalHelper('lint', 'yamlBlueprint', (mode, cm) => (mode.name === 'yaml' && mode.type === 'blueprint'), (text, options, cm) => {
-    let validator = new Validator();
+let blueprintValidator = new Validator();
+let catalogValidator = new Validator();
+let rootValidator = new Validator();
 
+[ blueprintValidator, catalogValidator, rootValidator ].forEach(validator => {
     validator.addSchema(JSON.parse(blueprintSchema), '/Blueprint');
     validator.addSchema(JSON.parse(blueprintEntitySchema), '/Blueprint/Entity');
     validator.addSchema(JSON.parse(blueprintLocationSchema), '/Blueprint/Location');
-
-    return lint(validator, blueprintSchema, text, options, cm);
 });
-CodeMirror.registerGlobalHelper('lint', 'yamlCatalog', (mode, cm) => (mode.name === 'yaml' && mode.type === 'catalog'), (text, options, cm) => {
-    let validator = new Validator();
 
+[ catalogValidator, rootValidator ].forEach(validator => {
     validator.addSchema(JSON.parse(catalogSchema), '/Catalog');
     validator.addSchema(JSON.parse(catalogItemReferenceSchema), '/Catalog/Item/Reference');
     validator.addSchema(JSON.parse(catalogItemInlineSchema), '/Catalog/Item/Inline');
     validator.addSchema(JSON.parse(catalogVersionSchema), '/Catalog/Version');
-
-    return lint(validator, catalogSchema, text, options, cm);
 });
-CodeMirror.registerGlobalHelper('lint', 'yamlBrooklyn', (mode, cm) => (mode.name === 'yaml' && mode.type === 'brooklyn'), (text, options, cm) => {
-    let validator = new Validator();
 
-    validator.addSchema(JSON.parse(blueprintSchema), '/Blueprint');
-    validator.addSchema(JSON.parse(blueprintEntitySchema), '/Blueprint/Entity');
-    validator.addSchema(JSON.parse(blueprintLocationSchema), '/Blueprint/Location');
-    validator.addSchema(JSON.parse(catalogSchema), '/Catalog');
-    validator.addSchema(JSON.parse(catalogItemReferenceSchema), '/Catalog/Item/Reference');
-    validator.addSchema(JSON.parse(catalogItemInlineSchema), '/Catalog/Item/Inline');
-    validator.addSchema(JSON.parse(catalogVersionSchema), '/Catalog/Version');
-
-    return lint(validator, rootSchema, text, options, cm);
-});
+CodeMirror.registerGlobalHelper('lint', 'yamlBlueprint', (mode, cm) => (mode.name === 'yaml' && mode.type === 'blueprint'), 
+    (text, options, cm) => lint(blueprintValidator, blueprintSchema, text, options, cm));
+    
+CodeMirror.registerGlobalHelper('lint', 'yamlCatalog', (mode, cm) => (mode.name === 'yaml' && mode.type === 'catalog'), 
+    (text, options, cm) => lint(catalogValidator, catalogSchema, text, options, cm));
+    
+CodeMirror.registerGlobalHelper('lint', 'yamlBrooklyn', (mode, cm) => (mode.name === 'yaml' && mode.type === 'brooklyn'), 
+    (text, options, cm) => lint(rootValidator, rootSchema, text, options, cm));
 
 function lint(validator, baseSchema, text, options, cm) {
     let issues = [];