diff --git a/package.json b/package.json
index 690963a1d39f80f3e8d5b2589acf3ac956f7b875..13a4b8c1f0dd219702d475c71207e4b18c4dfd87 100644
--- a/package.json
+++ b/package.json
@@ -38,16 +38,18 @@
     "babel-plugin-transform-object-rest-spread": "^6.22.0",
     "babel-preset-es2015": "^6.22.0",
     "babel-preset-react": "^6.22.0",
+    "buffer-loader": "0.0.1",
     "chromedriver": "2.33.1",
     "classnames": "2.2.5",
     "copy-webpack-plugin": "^4.0.1",
     "css-loader": "^0.28.7",
     "enzyme": "^3.1.0",
-    "enzyme-adapter-react-16": "1.0.2",
+    "enzyme-adapter-react-16": "1.0.3",
     "eslint": "^4.7.1",
     "eslint-config-scratch": "^5.0.0",
     "eslint-plugin-import": "^2.7.0",
     "eslint-plugin-react": "^7.2.1",
+    "file-loader": "1.1.5",
     "get-float-time-domain-data": "0.1.0",
     "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
     "html-webpack-plugin": "^2.30.0",
@@ -66,6 +68,7 @@
     "postcss-simple-vars": "^4.0.0",
     "prop-types": "^15.5.10",
     "raf": "^3.4.0",
+    "raw-loader": "0.5.1",
     "react": "16.0.0",
     "react-contextmenu": "2.8.0",
     "react-dom": "16.0.0",
@@ -83,17 +86,17 @@
     "redux-throttle": "0.1.1",
     "rimraf": "^2.6.1",
     "scratch-audio": "latest",
-    "scratch-paint": "latest",
     "scratch-blocks": "latest",
     "scratch-l10n": "^2.0.0",
+    "scratch-paint": "latest",
     "scratch-render": "latest",
-    "scratch-storage": "^0.2.0",
+    "scratch-storage": "^0.3.0",
     "scratch-vm": "latest",
     "selenium-webdriver": "3.5.0",
     "startaudiocontext": "1.2.1",
     "style-loader": "^0.19.0",
     "svg-to-image": "1.1.3",
-    "svg-url-loader": "^2.1.0",
+    "text-encoding": "0.6.4",
     "wav-encoder": "1.3.0",
     "web-audio-test-api": "^0.5.2",
     "webpack": "^3.6.0",
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index bb336c40d7687ced45ce96dc511fab866a20812d..9f9b84743fb3a861f3b0ba2d36fb55e9d25b28be 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -150,7 +150,7 @@
 }
 
 .extension-button-container {
-    width: 60px;
+    width: 3.25rem;
     height: 3.25rem;
     position: absolute;
     bottom: 0;
diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css
index f13554a027d46eabb3371396e29a6ff59b2cd478..9c784e9560d0ba06f371ea06941c29b627963d53 100644
--- a/src/components/stage/stage.css
+++ b/src/components/stage/stage.css
@@ -1,4 +1,5 @@
 @import "../../css/units.css";
+@import "../../css/colors.css";
 
 .stage {
     /*
@@ -7,8 +8,6 @@
     */
     display: block;
 
-    border-radius: $space;
-
     /* @todo: This is for overriding the value being set somewhere. Where is it being set? */
     background-color: transparent;
 }
@@ -31,6 +30,10 @@
 
 .stage-wrapper {
     position: relative;
+    border-radius: $space;
+    border: 1px solid $ui-pane-border;
+    /* Keep the canvas inside the border radius */
+    overflow: hidden;
 }
 
 .monitor-wrapper, .color-picker-wrapper {
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index 9ee7b5f4e10ebb27a27898be7b91fd2c52469271..2f147447d13280cbc63dd825840bb4f33b955708 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -7,7 +7,6 @@ import BackdropLibrary from '../../containers/backdrop-library.jsx';
 import CostumeLibrary from '../../containers/costume-library.jsx';
 import SoundLibrary from '../../containers/sound-library.jsx';
 import SpriteLibrary from '../../containers/sprite-library.jsx';
-import ExtensionLibrary from '../../containers/extension-library.jsx';
 
 import SpriteSelectorComponent from '../sprite-selector/sprite-selector.jsx';
 import StageSelector from '../../containers/stage-selector.jsx';
@@ -22,7 +21,6 @@ import styles from './target-pane.css';
  */
 const TargetPane = ({
     editingTarget,
-    extensionLibraryVisible,
     backdropLibraryVisible,
     costumeLibraryVisible,
     soundLibraryVisible,
@@ -40,7 +38,6 @@ const TargetPane = ({
     onRequestCloseCostumeLibrary,
     onRequestCloseSoundLibrary,
     onRequestCloseSpriteLibrary,
-    onRequestCloseExtensionLibrary,
     onSelectSprite,
     stage,
     sprites,
@@ -78,12 +75,6 @@ const TargetPane = ({
                 onSelect={onSelectSprite}
             />}
             <div>
-                {extensionLibraryVisible ? (
-                    <ExtensionLibrary
-                        vm={vm}
-                        onRequestClose={onRequestCloseExtensionLibrary}
-                    />
-                ) : null}
                 {spriteLibraryVisible ? (
                     <SpriteLibrary
                         vm={vm}
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 345d21e26f5a7d4e79f5a336b930be989c51bc42..471dc6cd08643341c290f66227b723ef28dbffb1 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -8,10 +8,12 @@ import VMScratchBlocks from '../lib/blocks';
 import VM from 'scratch-vm';
 import Prompt from './prompt.jsx';
 import BlocksComponent from '../components/blocks/blocks.jsx';
+import ExtensionLibrary from './extension-library.jsx';
 
 import {connect} from 'react-redux';
 import {updateToolbox} from '../reducers/toolbox';
 import {activateColorPicker} from '../reducers/color-picker';
+import {closeExtensionLibrary} from '../reducers/modals';
 
 const addFunctionListener = (object, property, callback) => {
     const oldFn = object[property];
@@ -29,6 +31,7 @@ class Blocks extends React.Component {
         bindAll(this, [
             'attachVM',
             'detachVM',
+            'handleCategorySelected',
             'handlePromptStart',
             'handlePromptCallback',
             'handlePromptClose',
@@ -53,12 +56,13 @@ class Blocks extends React.Component {
     componentDidMount () {
         this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker;
 
-        const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options);
+        const workspaceConfig = defaultsDeep({},
+            Blocks.defaultOptions,
+            this.props.options,
+            {toolbox: this.props.toolboxXML}
+        );
         this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
 
-        // Load the toolbox from the GUI (otherwise we get the scratch-blocks default toolbox)
-        this.workspace.updateToolbox(this.props.toolboxXML);
-
         // @todo change this when blockly supports UI events
         addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange);
         addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange);
@@ -69,7 +73,8 @@ class Blocks extends React.Component {
         return (
             this.state.prompt !== nextState.prompt ||
             this.props.isVisible !== nextProps.isVisible ||
-            this.props.toolboxXML !== nextProps.toolboxXML
+            this.props.toolboxXML !== nextProps.toolboxXML ||
+            this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible
         );
     }
     componentDidUpdate (prevProps) {
@@ -87,7 +92,6 @@ class Blocks extends React.Component {
             this.workspace.setVisible(true);
             this.props.vm.refreshWorkspace();
             window.dispatchEvent(new Event('resize'));
-            this.workspace.toolbox_.refreshSelection();
         } else {
             this.workspace.setVisible(false);
         }
@@ -172,13 +176,11 @@ class Blocks extends React.Component {
             this.onWorkspaceMetricsChange();
         }
 
-        this.ScratchBlocks.Events.disable();
-        this.workspace.clear();
-
+        // Remove and reattach the workspace listener (but allow flyout events)
+        this.workspace.removeChangeListener(this.props.vm.blockListener);
         const dom = this.ScratchBlocks.Xml.textToDom(data.xml);
-        this.ScratchBlocks.Xml.domToWorkspace(dom, this.workspace);
-        this.ScratchBlocks.Events.enable();
-        this.workspace.toolbox_.refreshSelection();
+        this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace);
+        this.workspace.addChangeListener(this.props.vm.blockListener);
 
         if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
             const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id];
@@ -193,7 +195,8 @@ class Blocks extends React.Component {
         const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML();
         const toolboxXML = makeToolboxXML(dynamicBlocksXML);
         this.props.onExtensionAdded(toolboxXML);
-        const categoryName = blocksInfo[0].json.category;
+    }
+    handleCategorySelected (categoryName) {
         this.workspace.toolbox_.setSelectedCategoryByName(categoryName);
     }
     setBlocks (blocks) {
@@ -212,11 +215,13 @@ class Blocks extends React.Component {
     render () {
         /* eslint-disable no-unused-vars */
         const {
+            extensionLibraryVisible,
             options,
             vm,
             isVisible,
             onActivateColorPicker,
             onExtensionAdded,
+            onRequestCloseExtensionLibrary,
             toolboxXML,
             ...props
         } = this.props;
@@ -236,15 +241,24 @@ class Blocks extends React.Component {
                         onOk={this.handlePromptCallback}
                     />
                 ) : null}
+                {extensionLibraryVisible ? (
+                    <ExtensionLibrary
+                        vm={vm}
+                        onCategorySelected={this.handleCategorySelected}
+                        onRequestClose={onRequestCloseExtensionLibrary}
+                    />
+                ) : null}
             </div>
         );
     }
 }
 
 Blocks.propTypes = {
+    extensionLibraryVisible: PropTypes.bool,
     isVisible: PropTypes.bool,
     onActivateColorPicker: PropTypes.func,
     onExtensionAdded: PropTypes.func,
+    onRequestCloseExtensionLibrary: PropTypes.func,
     options: PropTypes.shape({
         media: PropTypes.string,
         zoom: PropTypes.shape({
@@ -302,6 +316,7 @@ Blocks.defaultProps = {
 };
 
 const mapStateToProps = state => ({
+    extensionLibraryVisible: state.modals.extensionLibrary,
     toolboxXML: state.toolbox.toolboxXML
 });
 
@@ -309,6 +324,9 @@ const mapDispatchToProps = dispatch => ({
     onActivateColorPicker: callback => dispatch(activateColorPicker(callback)),
     onExtensionAdded: toolboxXML => {
         dispatch(updateToolbox(toolboxXML));
+    },
+    onRequestCloseExtensionLibrary: () => {
+        dispatch(closeExtensionLibrary());
     }
 });
 
diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx
index 343d0747d8a273c9eaca2d5bfbb86dfee5fa2284..09fd65ff35d9f3af78019f622fa0ed3d58b19d0b 100644
--- a/src/containers/extension-library.jsx
+++ b/src/containers/extension-library.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import VM from 'scratch-vm';
 
-import extensionLibraryContent from '../lib/libraries/extensions.json';
+import extensionLibraryContent from '../lib/libraries/extensions/index';
 
 import LibraryComponent from '../components/library/library.jsx';
 import extensionIcon from '../components/sprite-selector/icon--sprite.svg';
@@ -19,7 +19,13 @@ class ExtensionLibrary extends React.PureComponent {
         // eslint-disable-next-line no-alert
         const url = item.extensionURL || prompt('Enter the URL of the extension');
         if (url) {
-            this.props.vm.extensionManager.loadExtensionURL(url);
+            if (this.props.vm.extensionManager.isExtensionLoaded(url)) {
+                this.props.onCategorySelected(item.name);
+            } else {
+                this.props.vm.extensionManager.loadExtensionURL(url).then(() => {
+                    this.props.onCategorySelected(item.name);
+                });
+            }
         }
     }
     render () {
@@ -40,6 +46,7 @@ class ExtensionLibrary extends React.PureComponent {
 }
 
 ExtensionLibrary.propTypes = {
+    onCategorySelected: PropTypes.func,
     onRequestClose: PropTypes.func,
     visible: PropTypes.bool,
     vm: PropTypes.instanceOf(VM).isRequired // eslint-disable-line react/no-unused-prop-types
diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx
index ca033c06170577fe3fb2f4db76e66ac8a2d45628..6448f3e9e2b8901f392517728ccac4f8bdc74190 100644
--- a/src/containers/paint-editor-wrapper.jsx
+++ b/src/containers/paint-editor-wrapper.jsx
@@ -55,7 +55,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => {
         name: costume && costume.name,
         rotationCenterX: costume && costume.rotationCenterX,
         rotationCenterY: costume && costume.rotationCenterY,
-        svgId: editingTarget && `${editingTarget}${selectedCostumeIndex}`
+        svgId: editingTarget && `${editingTarget}${costume.skinId}`
     };
 };
 
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 3a68847c657d15eae94475e5efdc7923b68a1d48..ba0438fae049144130cbcae35e4e8abf7fbdee3b 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -7,7 +7,6 @@ import {
     openSpriteLibrary,
     closeBackdropLibrary,
     closeCostumeLibrary,
-    closeExtensionLibrary,
     closeSoundLibrary,
     closeSpriteLibrary
 } from '../reducers/modals';
@@ -97,8 +96,7 @@ const mapStateToProps = state => ({
     soundLibraryVisible: state.modals.soundLibrary,
     spriteLibraryVisible: state.modals.spriteLibrary,
     costumeLibraryVisible: state.modals.costumeLibrary,
-    backdropLibraryVisible: state.modals.backdropLibrary,
-    extensionLibraryVisible: state.modals.extensionLibrary
+    backdropLibraryVisible: state.modals.backdropLibrary
 });
 const mapDispatchToProps = dispatch => ({
     onNewSpriteClick: e => {
@@ -111,9 +109,6 @@ const mapDispatchToProps = dispatch => ({
     onRequestCloseCostumeLibrary: () => {
         dispatch(closeCostumeLibrary());
     },
-    onRequestCloseExtensionLibrary: () => {
-        dispatch(closeExtensionLibrary());
-    },
     onRequestCloseSoundLibrary: () => {
         dispatch(closeSoundLibrary());
     },
diff --git a/src/examples/compatibility-testing.jsx b/src/examples/compatibility-testing.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3fa8e71d94e34e4722ec1a1f329c9df46de2688c
--- /dev/null
+++ b/src/examples/compatibility-testing.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {connect} from 'react-redux';
+
+import AppStateHOC from '../lib/app-state-hoc.jsx';
+import Controls from '../containers/controls.jsx';
+import Stage from '../containers/stage.jsx';
+import Box from '../components/box/box.jsx';
+import GUI from '../containers/gui.jsx';
+import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx';
+
+const mapStateToProps = state => ({vm: state.vm});
+
+const VMStage = connect(mapStateToProps)(Stage);
+const VMControls = connect(mapStateToProps)(Controls);
+
+const DEFAULT_PROJECT_ID = '10015059';
+
+class Player extends React.Component {
+    constructor (props) {
+        super(props);
+        this.updateProject = this.updateProject.bind(this);
+
+        this.state = {
+            projectId: window.location.hash.substring(1) || DEFAULT_PROJECT_ID
+        };
+    }
+    componentDidMount () {
+        window.addEventListener('hashchange', this.updateProject);
+        if (!window.location.hash.substring(1)) {
+            window.location.hash = DEFAULT_PROJECT_ID;
+        }
+    }
+    componentWillUnmount () {
+        window.addEventListener('hashchange', this.updateProject);
+    }
+    updateProject () {
+        this.setState({projectId: window.location.hash.substring(1)});
+    }
+    render () {
+        const width = 480;
+        const height = 360;
+        return (
+            <div style={{display: 'flex'}}>
+                <GUI
+                    {...this.props}
+                    width={width}
+                >
+                    <Box height={40}>
+                        <VMControls
+                            style={{
+                                marginRight: 10,
+                                height: 40
+                            }}
+                        />
+                    </Box>
+                    <VMStage
+                        height={height}
+                        width={width}
+                    />
+                </GUI>
+                <iframe
+                    allowFullScreen
+                    allowTransparency
+                    frameBorder="0"
+                    height="402"
+                    src={`https://scratch.mit.edu/projects/embed/${this.state.projectId}/?autostart=true`}
+                    width="485"
+                />
+            </div>
+        );
+    }
+}
+
+const App = AppStateHOC(ProjectLoaderHOC(Player));
+
+const appTarget = document.createElement('div');
+document.body.appendChild(appTarget);
+
+ReactDOM.render(<App />, appTarget);
diff --git a/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg b/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg
new file mode 100755
index 0000000000000000000000000000000000000000..d449b3d15b955647f2198116743d0f4df619b24b
Binary files /dev/null and b/src/lib/default-project/09dc888b0b7df19f70d81588ae73420e.svg differ
diff --git a/src/lib/default-project/3696356a03a8d938318876a593572843.svg b/src/lib/default-project/3696356a03a8d938318876a593572843.svg
new file mode 100755
index 0000000000000000000000000000000000000000..0ecb2de81d9e76af3920bab9afd30cec1fa9f8d6
Binary files /dev/null and b/src/lib/default-project/3696356a03a8d938318876a593572843.svg differ
diff --git a/src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png b/src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png
new file mode 100755
index 0000000000000000000000000000000000000000..da373d2cf3ab7c4c617d307eec5a044ae43917ee
Binary files /dev/null and b/src/lib/default-project/5c81a336fab8be57adc039a8a2b33ca9.png differ
diff --git a/src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png b/src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png
new file mode 100755
index 0000000000000000000000000000000000000000..b395ac94c246fd4de8368a156f92010baa2abe21
Binary files /dev/null and b/src/lib/default-project/739b5e2a2435f6e1ec2993791b423146.png differ
diff --git a/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav b/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav
new file mode 100755
index 0000000000000000000000000000000000000000..fc3b2724a9c7cfef378eeb65499d44236ad2add8
Binary files /dev/null and b/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav differ
diff --git a/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav b/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav
new file mode 100755
index 0000000000000000000000000000000000000000..45742d5ef6f09d05b0f0788cb055ffe54abfd9ad
Binary files /dev/null and b/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav differ
diff --git a/src/lib/default-project/index.js b/src/lib/default-project/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc1ace99984c8fc8982f59dcba5fdcb3f3bb7386
--- /dev/null
+++ b/src/lib/default-project/index.js
@@ -0,0 +1,49 @@
+import {TextEncoder} from 'text-encoding';
+import projectJson from './project.json';
+
+/* eslint-disable import/no-unresolved */
+import popWav from '!buffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
+import meowWav from '!buffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
+import backdrop from '!buffer-loader!./739b5e2a2435f6e1ec2993791b423146.png';
+import penLayer from '!buffer-loader!./5c81a336fab8be57adc039a8a2b33ca9.png';
+import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
+import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
+/* eslint-enable import/no-unresolved */
+
+const encoder = new TextEncoder();
+export default [{
+    id: 0,
+    assetType: 'Project',
+    dataFormat: 'JSON',
+    data: JSON.stringify(projectJson)
+}, {
+    id: '83a9787d4cb6f3b7632b4ddfebf74367',
+    assetType: 'Sound',
+    dataFormat: 'WAV',
+    data: popWav
+}, {
+    id: '83c36d806dc92327b9e7049a565c6bff',
+    assetType: 'Sound',
+    dataFormat: 'WAV',
+    data: meowWav
+}, {
+    id: '739b5e2a2435f6e1ec2993791b423146',
+    assetType: 'ImageBitmap',
+    dataFormat: 'PNG',
+    data: backdrop
+}, {
+    id: '5c81a336fab8be57adc039a8a2b33ca9',
+    assetType: 'ImageBitmap',
+    dataFormat: 'PNG',
+    data: penLayer
+}, {
+    id: '09dc888b0b7df19f70d81588ae73420e',
+    assetType: 'ImageVector',
+    dataFormat: 'SVG',
+    data: encoder.encode(costume1)
+}, {
+    id: '3696356a03a8d938318876a593572843',
+    assetType: 'ImageVector',
+    dataFormat: 'SVG',
+    data: encoder.encode(costume2)
+}];
diff --git a/src/lib/empty-project.json b/src/lib/default-project/project.json
similarity index 100%
rename from src/lib/empty-project.json
rename to src/lib/default-project/project.json
diff --git a/src/lib/libraries/extensions.json b/src/lib/libraries/extensions.json
deleted file mode 100644
index 80edbcb55d70494600cc79ed4f58a76fc38b7b60..0000000000000000000000000000000000000000
--- a/src/lib/libraries/extensions.json
+++ /dev/null
@@ -1,9 +0,0 @@
-[
-    {
-        "name": "Pen",
-        "extensionURL": "pen",
-        "md5": "1a2ec605e73000897797f0c851134f5b.png",
-        "description": "Draw with your sprites.",
-        "featured": true
-    }
-]
diff --git a/src/lib/libraries/extensions/index.js b/src/lib/libraries/extensions/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a03a896bba145c04175765e3b2b7ad9fa41557f
--- /dev/null
+++ b/src/lib/libraries/extensions/index.js
@@ -0,0 +1,19 @@
+import penImage from './pen.png';
+import wedoImage from './wedo.png';
+
+export default [
+    {
+        name: 'Pen',
+        extensionURL: 'pen',
+        iconURL: penImage,
+        description: 'Draw with your sprites.',
+        featured: true
+    },
+    {
+        name: 'Lego WeDo 2.0',
+        extensionURL: 'wedo2',
+        iconURL: wedoImage,
+        description: 'Build with motors and sensors.',
+        featured: true
+    }
+];
diff --git a/src/lib/libraries/extensions/pen.png b/src/lib/libraries/extensions/pen.png
new file mode 100644
index 0000000000000000000000000000000000000000..f02064c425e89cbac6962b36ee0b90e50d145e03
Binary files /dev/null and b/src/lib/libraries/extensions/pen.png differ
diff --git a/src/lib/libraries/extensions/wedo.png b/src/lib/libraries/extensions/wedo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8519256752af7d5cbbbe86b74df1cce7fe5614d
Binary files /dev/null and b/src/lib/libraries/extensions/wedo.png differ
diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js
index 174319ec4dc0a235667c14ffffc4d28bf12e87df..c822ef332c1ac69ca98a39daaa551a238c055c89 100644
--- a/src/lib/make-toolbox-xml.js
+++ b/src/lib/make-toolbox-xml.js
@@ -1,78 +1,6 @@
-const separator = '<sep gap="45"/>';
+const categorySeparator = '<sep gap="36"/>';
 
-const top = `
-    <category name="Top" colour="#FFFFFF" secondaryColour="#CCCCCC">
-        <block type="event_whenflagclicked"/>
-        <block type="event_whenkeypressed">
-        </block>
-        <block type="event_whenthisspriteclicked"/>
-        <block type="motion_movesteps">
-            <value name="STEPS">
-                <shadow type="math_number">
-                    <field name="NUM">10</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="motion_turnright">
-            <value name="DEGREES">
-                <shadow type="math_number">
-                    <field name="NUM">15</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="motion_ifonedgebounce"/>
-        <block type="sound_playuntildone">
-            <value name="SOUND_MENU">
-                <shadow type="sound_sounds_menu"/>
-            </value>
-        </block>
-        <block type="looks_sayforsecs">
-            <value name="MESSAGE">
-                <shadow type="text">
-                    <field name="TEXT">Hello!</field>
-                </shadow>
-            </value>
-            <value name="SECS">
-                <shadow type="math_number">
-                    <field name="NUM">2</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="looks_changeeffectby">
-            <value name="CHANGE">
-                <shadow type="math_number">
-                    <field name="NUM">10</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="control_repeat">
-            <value name="TIMES">
-                <shadow type="math_whole_number">
-                    <field name="NUM">10</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="control_wait">
-            <value name="DURATION">
-                <shadow type="math_positive_number">
-                    <field name="NUM">1</field>
-                </shadow>
-            </value>
-        </block>
-        <block type="operator_random">
-            <value name="FROM">
-                <shadow type="math_number">
-                    <field name="NUM">1</field>
-                </shadow>
-            </value>
-            <value name="TO">
-                <shadow type="math_number">
-                    <field name="NUM">10</field>
-                </shadow>
-            </value>
-        </block>
-    </category>
-`;
+const blockSeparator = '<sep gap="36"/>'; // At default scale, about 28px
 
 const motion = `
     <category name="Motion" colour="#4C97FF" secondaryColour="#3373CC">
@@ -97,6 +25,7 @@ const motion = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="motion_pointindirection">
             <value name="DIRECTION">
                 <shadow type="math_angle">
@@ -110,6 +39,7 @@ const motion = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="motion_gotoxy">
             <value name="X">
                 <shadow id="movex" type="math_number">
@@ -156,6 +86,7 @@ const motion = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="motion_changexby">
             <value name="DX">
                 <shadow type="math_number">
@@ -184,11 +115,15 @@ const motion = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="motion_ifonedgebounce"/>
+        ${blockSeparator}
         <block type="motion_setrotationstyle"/>
-        <block type="motion_xposition"/>
-        <block type="motion_yposition"/>
-        <block type="motion_direction"/>
+        ${blockSeparator}
+        <block id="xposition" type="motion_xposition"/>
+        <block id="yposition" type="motion_yposition"/>
+        <block id="direction" type="motion_direction"/>
+        ${categorySeparator}
     </category>
 `;
 
@@ -232,8 +167,10 @@ const looks = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="looks_show"/>
         <block type="looks_hide"/>
+        ${blockSeparator}
         <block type="looks_switchcostumeto">
             <value name="COSTUME">
                 <shadow type="looks_costume"/>
@@ -251,6 +188,7 @@ const looks = `
                 <shadow type="looks_backdrops"/>
             </value>
         </block>
+        ${blockSeparator}
         <block type="looks_changeeffectby">
             <value name="CHANGE">
                 <shadow type="math_number">
@@ -266,6 +204,7 @@ const looks = `
             </value>
         </block>
         <block type="looks_cleargraphiceffects"/>
+        ${blockSeparator}
         <block type="looks_changesizeby">
             <value name="CHANGE">
                 <shadow type="math_number">
@@ -280,6 +219,7 @@ const looks = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="looks_gotofront"/>
         <block type="looks_gobacklayers">
             <value name="NUM">
@@ -288,10 +228,12 @@ const looks = `
                 </shadow>
             </value>
         </block>
-        <block type="looks_costumeorder"/>
-        <block type="looks_backdroporder"/>
-        <block type="looks_backdropname"/>
-        <block type="looks_size"/>
+        ${blockSeparator}
+        <block id="costumeorder" type="looks_costumeorder"/>
+        <block id="backdroporder" type="looks_backdroporder"/>
+        <block id="backdropname" type="looks_backdropname"/>
+        <block id="size" type="looks_size"/>
+        ${categorySeparator}
     </category>
 `;
 
@@ -308,6 +250,7 @@ const sound = `
             </value>
         </block>
         <block type="sound_stopallsounds"/>
+        ${blockSeparator}
         <block type="sound_playdrumforbeats">
             <value name="DRUM">
                 <shadow type="sound_drums_menu"/>
@@ -325,6 +268,7 @@ const sound = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="sound_playnoteforbeats">
             <value name="NOTE">
                 <shadow type="math_number">
@@ -342,6 +286,7 @@ const sound = `
                 <shadow type="sound_instruments_menu"/>
             </value>
         </block>
+        ${blockSeparator}
         <block type="sound_changeeffectby">
             <value name="VALUE">
                 <shadow type="math_number">
@@ -357,6 +302,7 @@ const sound = `
             </value>
         </block>
         <block type="sound_cleareffects"/>
+        ${blockSeparator}
         <block type="sound_changevolumeby">
             <value name="VOLUME">
                 <shadow type="math_number">
@@ -371,7 +317,8 @@ const sound = `
                 </shadow>
             </value>
         </block>
-        <block type="sound_volume"/>
+        <block id="volume" type="sound_volume"/>
+        ${blockSeparator}
         <block type="sound_changetempoby">
             <value name="TEMPO">
                 <shadow type="math_number">
@@ -386,7 +333,8 @@ const sound = `
                 </shadow>
             </value>
         </block>
-        <block type="sound_tempo"/>
+        <block id="tempo" type="sound_tempo"/>
+        ${categorySeparator}
     </category>
 `;
 
@@ -398,6 +346,7 @@ const events = `
         <block type="event_whenthisspriteclicked"/>
         <block type="event_whenbackdropswitchesto">
         </block>
+        ${blockSeparator}
         <block type="event_whengreaterthan">
             <value name="VALUE">
                 <shadow type="math_number">
@@ -405,6 +354,7 @@ const events = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="event_whenbroadcastreceived">
         </block>
         <block type="event_broadcast">
@@ -417,6 +367,7 @@ const events = `
                 <shadow type="event_broadcast_menu"/>
             </value>
         </block>
+        ${categorySeparator}
     </category>
 `;
 
@@ -429,6 +380,7 @@ const control = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="control_repeat">
             <value name="TIMES">
                 <shadow type="math_whole_number">
@@ -437,11 +389,14 @@ const control = `
             </value>
         </block>
         <block type="control_forever"/>
+        ${blockSeparator}
         <block type="control_if"/>
         <block type="control_if_else"/>
         <block type="control_wait_until"/>
         <block type="control_repeat_until"/>
+        ${blockSeparator}
         <block type="control_stop"/>
+        ${blockSeparator}
         <block type="control_start_as_clone"/>
         <block type="control_create_clone_of">
             <value name="CLONE_OPTION">
@@ -449,6 +404,7 @@ const control = `
             </value>
         </block>
         <block type="control_delete_this_clone"/>
+        ${categorySeparator}
     </category>
 `;
 
@@ -477,6 +433,7 @@ const sensing = `
                 <shadow type="sensing_distancetomenu"/>
             </value>
         </block>
+        ${blockSeparator}
         <block type="sensing_askandwait">
             <value name="QUESTION">
                 <shadow type="text">
@@ -484,7 +441,8 @@ const sensing = `
                 </shadow>
             </value>
         </block>
-        <block type="sensing_answer"/>
+        <block id="answer" type="sensing_answer"/>
+        ${blockSeparator}
         <block type="sensing_keypressed">
             <value name="KEY_OPTION">
                 <shadow type="sensing_keyoptions"/>
@@ -493,23 +451,28 @@ const sensing = `
         <block type="sensing_mousedown"/>
         <block type="sensing_mousex"/>
         <block type="sensing_mousey"/>
-        <block type="sensing_loudness"/>
-        <block type="sensing_timer"/>
+        ${blockSeparator}
+        <block id="loudness" type="sensing_loudness"/>
+        ${blockSeparator}
+        <block id="timer" type="sensing_timer"/>
         <block type="sensing_resettimer"/>
-        <block type="sensing_of">
+        ${blockSeparator}
+        <block id="of" type="sensing_of">
             <value name="PROPERTY">
-                <shadow type="sensing_of_property_menu"/>
+                <shadow id="sensing_of_property_menu" type="sensing_of_property_menu"/>
             </value>
             <value name="OBJECT">
-                <shadow type="sensing_of_object_menu"/>
+                <shadow id="sensing_of_object_menu" type="sensing_of_object_menu"/>
             </value>
         </block>
-        <block type="sensing_current">
+        ${blockSeparator}
+        <block id="current" type="sensing_current">
             <value name="CURRENTMENU">
-                <shadow type="sensing_currentmenu"/>
+                <shadow id="sensing_currentmenu" type="sensing_currentmenu"/>
             </value>
         </block>
         <block type="sensing_dayssince2000"/>
+        ${categorySeparator}
     </category>
 `;
 
@@ -563,6 +526,7 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="operator_random">
             <value name="FROM">
                 <shadow type="math_number">
@@ -575,6 +539,7 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="operator_lt">
             <value name="OPERAND1">
                 <shadow type="text">
@@ -611,9 +576,11 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="operator_and"/>
         <block type="operator_or"/>
         <block type="operator_not"/>
+        ${blockSeparator}
         <block type="operator_join">
             <value name="STRING1">
                 <shadow type="text">
@@ -645,6 +612,19 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        <block type="operator_contains" id="operator_contains">
+          <value name="STRING1">
+            <shadow type="text">
+              <field name="TEXT">hello</field>
+            </shadow>
+          </value>
+          <value name="STRING2">
+            <shadow type="text">
+              <field name="TEXT">world</field>
+            </shadow>
+          </value>
+        </block>
+        ${blockSeparator}
         <block type="operator_mod">
             <value name="NUM1">
                 <shadow type="math_number">
@@ -664,6 +644,7 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        ${blockSeparator}
         <block type="operator_mathop">
             <value name="NUM">
                 <shadow type="math_number">
@@ -671,6 +652,7 @@ const operators = `
                 </shadow>
             </value>
         </block>
+        ${categorySeparator}
     </category>
 `;
 
@@ -687,11 +669,10 @@ const xmlClose = '</xml>';
  * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
  */
 const makeToolboxXML = function (categoriesXML) {
-    const gap = [separator];
+    const gap = [categorySeparator];
 
     const everything = [
         xmlOpen,
-        top, gap,
         motion, gap,
         looks, gap,
         sound, gap,
@@ -707,7 +688,6 @@ const makeToolboxXML = function (categoriesXML) {
     }
 
     everything.push(xmlClose);
-
     return everything.join('\n');
 };
 
diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx
index 5468e7a09b54c292c9e107183fff80550fb0c29d..23ad4694ad0fbbe34340f7eda68698ae3405d07d 100644
--- a/src/lib/project-loader-hoc.jsx
+++ b/src/lib/project-loader-hoc.jsx
@@ -1,26 +1,7 @@
 import React from 'react';
-import xhr from 'xhr';
 
 import log from './log';
-import emptyProject from './empty-project.json';
-
-class ProjectLoaderConstructor {
-    get DEFAULT_PROJECT_DATA () {
-        return emptyProject;
-    }
-
-    load (id, callback) {
-        callback = callback || (err => log.error(err));
-        xhr({
-            uri: `https://projects.scratch.mit.edu/internalapi/project/${id}/get/`
-        }, (err, res, body) => {
-            if (err) return callback(err);
-            callback(null, body);
-        });
-    }
-}
-
-const ProjectLoader = new ProjectLoaderConstructor();
+import storage from './storage';
 
 /* Higher Order Component to provide behavior for loading projects by id from
  * the window's hash (#this part in the url)
@@ -35,13 +16,23 @@ const ProjectLoaderHOC = function (WrappedComponent) {
             this.updateProject = this.updateProject.bind(this);
             this.state = {
                 projectId: null,
-                projectData: this.fetchProjectId().length ? null : JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA)
+                projectData: null
             };
         }
         componentDidMount () {
             window.addEventListener('hashchange', this.updateProject);
             this.updateProject();
         }
+        componentDidUpdate (prevProps, prevState) {
+            if (this.state.projectId !== prevState.projectId) {
+                storage
+                    .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON)
+                    .then(projectAsset => projectAsset && this.setState({
+                        projectData: projectAsset.data.toString()
+                    }))
+                    .catch(err => log.error(err));
+            }
+        }
         componentWillUnmount () {
             window.removeEventListener('hashchange', this.updateProject);
         }
@@ -49,18 +40,9 @@ const ProjectLoaderHOC = function (WrappedComponent) {
             return window.location.hash.substring(1);
         }
         updateProject () {
-            const projectId = this.fetchProjectId();
+            let projectId = this.fetchProjectId();
             if (projectId !== this.state.projectId) {
-                if (projectId.length < 1) {
-                    return this.setState({
-                        projectId: projectId,
-                        projectData: JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA)
-                    });
-                }
-                ProjectLoader.load(projectId, (err, body) => {
-                    if (err) return log.error(err);
-                    this.setState({projectData: body});
-                });
+                if (projectId.length < 1) projectId = 0;
                 this.setState({projectId: projectId});
             }
         }
@@ -80,6 +62,5 @@ const ProjectLoaderHOC = function (WrappedComponent) {
 
 
 export {
-    ProjectLoaderHOC as default,
-    ProjectLoader
+    ProjectLoaderHOC as default
 };
diff --git a/src/lib/storage.js b/src/lib/storage.js
index e1dc91bd6277148f3102a47aca8b7afd2a5401c3..6d858e6c20e57f919cbaa6dfc3eb2080b9f915ce 100644
--- a/src/lib/storage.js
+++ b/src/lib/storage.js
@@ -1,5 +1,7 @@
 import ScratchStorage from 'scratch-storage';
 
+import defaultProjectAssets from './default-project';
+
 const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu';
 const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu';
 
@@ -23,7 +25,15 @@ class Storage extends ScratchStorage {
             [this.AssetType.ImageVector, this.AssetType.ImageBitmap, this.AssetType.Sound],
             asset => `${ASSET_SERVER}/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
         );
+        defaultProjectAssets.forEach(asset => this.cache(
+            this.AssetType[asset.assetType],
+            this.DataFormat[asset.dataFormat],
+            asset.data,
+            asset.id
+        ));
     }
 }
 
-export default Storage;
+const storage = new Storage();
+
+export default storage;
diff --git a/src/reducers/vm.js b/src/reducers/vm.js
index 5d0e6f91f0cf707557316d63778e406c58f606c8..dd70f3f4fd756e17e76655ff149559cefd7ffbee 100644
--- a/src/reducers/vm.js
+++ b/src/reducers/vm.js
@@ -1,9 +1,9 @@
 import VM from 'scratch-vm';
-import Storage from '../lib/storage';
+import storage from '../lib/storage';
 
 const SET_VM = 'scratch-gui/vm/SET_VM';
 const defaultVM = new VM();
-defaultVM.attachStorage(new Storage());
+defaultVM.attachStorage(storage);
 const initialState = defaultVM;
 
 const reducer = function (state, action) {
diff --git a/test/integration/examples.test.js b/test/integration/examples.test.js
index e2b0c8ab41827803c4ad7b08d216416e84d20053..7fffa903a4bb6f99f08ab18285869ce7bbd05f17 100644
--- a/test/integration/examples.test.js
+++ b/test/integration/examples.test.js
@@ -72,6 +72,7 @@ describe('blocks example', () => {
         await clickText('Sensing');
         await clickText('Operators');
         await clickText('Data');
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         await clickText('Create variable...');
         let el = await findByXpath("//input[@placeholder='']");
         await el.sendKeys('score');
diff --git a/test/integration/test.js b/test/integration/test.js
index efd0732f2ed9e1b7700e2a4f859aa25148028917..86729cf277017b5cb99f9b858514633583564d47 100644
--- a/test/integration/test.js
+++ b/test/integration/test.js
@@ -23,6 +23,7 @@ const blocksTabScope = "*[@id='react-tabs-1']";
 const costumesTabScope = "*[@id='react-tabs-3']";
 const soundsTabScope = "*[@id='react-tabs-5']";
 const reportedValueScope = '*[@class="blocklyDropDownContent"]';
+const modalScope = '*[@class="ReactModalPortal"]';
 
 describe('costumes, sounds and variables', () => {
     beforeAll(() => {
@@ -135,9 +136,18 @@ describe('costumes, sounds and variables', () => {
         await driver.get(`file://${uri}`);
         await clickText('Blocks');
         await clickText('Extensions');
-        await clickText('Pen'); // Modal closes
-        await clickText('Pen', blocksTabScope); // Click the new category
+        await clickText('Pen', modalScope); // Modal closes
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         await clickText('stamp', blocksTabScope); // Click the "stamp" block
+
+        // Make sure trying to load the extension again scrolls back down
+        await clickText('Motion', blocksTabScope); // To scroll the list back to the top
+        await clickText('Extensions');
+        await clickText('Pen', modalScope); // Modal closes
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
+        await clickText('stamp', blocksTabScope); // Would fail if didn't scroll back
+
+
         const logs = await getLogs(errorWhitelist);
         await expect(logs).toEqual([]);
     });
diff --git a/test/unit/util/project-loader-hoc.test.jsx b/test/unit/util/project-loader-hoc.test.jsx
index d61352d3630c51a81c5da5d612da89ab65038a2a..3771f37692563ebef894a719138ece5fdc4813b0 100644
--- a/test/unit/util/project-loader-hoc.test.jsx
+++ b/test/unit/util/project-loader-hoc.test.jsx
@@ -1,5 +1,6 @@
 import React from 'react';
-import ProjectLoaderHOC, {ProjectLoader} from '../../../src/lib/project-loader-hoc.jsx';
+import ProjectLoaderHOC from '../../../src/lib/project-loader-hoc.jsx';
+import storage from '../../../src/lib/storage';
 import {mount} from 'enzyme';
 
 describe('ProjectLoaderHOC', () => {
@@ -7,34 +8,41 @@ describe('ProjectLoaderHOC', () => {
         const Component = ({projectData}) => <div>{projectData}</div>;
         const WrappedComponent = ProjectLoaderHOC(Component);
         window.location.hash = '#winning';
-        ProjectLoader.load = jest.fn((id, cb) => cb(null, null));
+        const originalLoad = storage.load;
+        storage.load = jest.fn(() => Promise.resolve(null));
         const mounted = mount(<WrappedComponent />);
-        ProjectLoader.load.mockRestore();
+        storage.load = originalLoad;
         window.location.hash = '';
-        expect(mounted.find('div').exists()).toEqual(false);
+        const mountedDiv = mounted.find('div');
+        expect(mountedDiv.exists()).toEqual(false);
     });
 
     test('when there is no hash, it loads the default project', () => {
         const Component = ({projectData}) => <div>{projectData}</div>;
         const WrappedComponent = ProjectLoaderHOC(Component);
         window.location.hash = '';
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve(id));
         const mounted = mount(<WrappedComponent />);
-        expect(mounted.find('div').text()).toEqual(JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA));
+        expect(mounted.state().projectId).toEqual(0);
+        expect(storage.load).toHaveBeenCalledWith(
+            storage.AssetType.Project, 0, storage.DataFormat.JSON
+        );
+        storage.load = originalLoad;
     });
 
     test('when there is a hash, it tries to load that project', () => {
         const Component = ({projectData}) => <div>{projectData}</div>;
         const WrappedComponent = ProjectLoaderHOC(Component);
         window.location.hash = '#winning';
-        ProjectLoader.load = jest.fn((id, cb) => cb(null, id));
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
         const mounted = mount(<WrappedComponent />);
-        mounted.update();
-        ProjectLoader.load.mockRestore();
-        window.location.hash = '';
-        expect(mounted
-            .find('div')
-            .text()
-        ).toEqual('winning');
+        expect(mounted.state().projectId).toEqual('winning');
+        expect(storage.load).toHaveBeenLastCalledWith(
+            storage.AssetType.Project, 'winning', storage.DataFormat.JSON
+        );
+        storage.load = originalLoad;
     });
 
     test('when hash change happens, the project data state is changed', () => {
@@ -42,10 +50,12 @@ describe('ProjectLoaderHOC', () => {
         const WrappedComponent = ProjectLoaderHOC(Component);
         window.location.hash = '';
         const mounted = mount(<WrappedComponent />);
-        const before = mounted.find('div').text();
-        ProjectLoader.load = jest.fn((id, cb) => cb(null, id));
-        window.location.hash = `#winning`;
+        expect(mounted.state().projectId).toEqual(0);
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
+        window.location.hash = '#winning';
         mounted.instance().updateProject();
-        expect(mounted.find('div').text()).not.toEqual(before);
+        expect(mounted.state().projectId).toEqual('winning');
+        storage.load = originalLoad;
     });
 });
diff --git a/webpack.config.js b/webpack.config.js
index 6c486cc8aaacdf7336c262e602ceaadb47c5fdff..e9b97f9efa740c41a3abdbd0128f7260ad77bf48 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -21,6 +21,7 @@ module.exports = {
         lib: ['react', 'react-dom'],
         gui: './src/index.jsx',
         blocksonly: './src/examples/blocks-only.jsx',
+        compatibilitytesting: './src/examples/compatibility-testing.jsx',
         player: './src/examples/player.jsx'
     },
     output: {
@@ -66,8 +67,8 @@ module.exports = {
             }]
         },
         {
-            test: /\.svg$/,
-            loader: 'svg-url-loader?noquotes'
+            test: /\.(svg|png|wav)$/,
+            loader: 'file-loader'
         }]
     },
     plugins: [
@@ -90,6 +91,12 @@ module.exports = {
             filename: 'blocks-only.html',
             title: 'Scratch 3.0 GUI: Blocks Only Example'
         }),
+        new HtmlWebpackPlugin({
+            chunks: ['lib', 'compatibilitytesting'],
+            template: 'src/index.ejs',
+            filename: 'compatibility-testing.html',
+            title: 'Scratch 3.0 GUI: Compatibility Testing'
+        }),
         new HtmlWebpackPlugin({
             chunks: ['lib', 'player'],
             template: 'src/index.ejs',