From 481807c47cf46fa72107a91e24a9a179615412ad Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sat, 8 Jun 2019 11:10:29 -0700
Subject: [PATCH 01/12] run test action

---
 src/editor/commands/index.ts | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 2d000bb9..ad8f39be 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -2,15 +2,15 @@ import * as vscode from 'vscode'
 import start from './start'
 import ReactPanel from '../views/createWebview'
 
-// import runTest from './runTest'
+import runTest from './runTest'
 // import loadSolution from './loadSolution'
 // import quit from './quit'
 
 const COMMANDS = {
   // TUTORIAL_SETUP: 'coderoad.tutorial_setup',
   START: 'coderoad.start',
-  OPEN_WEBVIEW: 'coderoad.open_webview'
-  // RUN_TEST: 'coderoad.test_run',
+  OPEN_WEBVIEW: 'coderoad.open_webview',
+  RUN_TEST: 'coderoad.test_run',
   // LOAD_SOLUTION: 'coderoad.solution_load',
   // QUIT: 'coderoad.quit',
 }
@@ -18,13 +18,15 @@ const COMMANDS = {
 
 export default (context: vscode.ExtensionContext): void => {
   const commands = {
-    [COMMANDS.START]: async function startCommand(): Promise<void> {
-      return start(context)
+    [COMMANDS.START]: () => {
+      start(context)
     },
     [COMMANDS.OPEN_WEBVIEW]: () => {
       ReactPanel.createOrShow(context.extensionPath);
     },
-    // [COMMANDS.RUN_TEST]: runTest,
+    [COMMANDS.RUN_TEST]: () => {
+      runTest()
+    },
     // [COMMANDS.LOAD_SOLUTION]: loadSolution,
     // [COMMANDS.QUIT]: () => quit(context.subscriptions),
   }

From d00a485520f4e121797b8531695aa02e47d3d28a Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sat, 8 Jun 2019 12:06:17 -0700
Subject: [PATCH 02/12] progress refactoring webview, editor, machine

---
 src/editor/ReactWebView.ts        | 146 ++++++++++++++++++++++++++++++
 src/editor/commands/start.ts      |   4 +-
 src/editor/index.ts               |  75 +++++++++++++++
 src/editor/init.ts                |  33 -------
 src/editor/views/createWebview.ts | 141 -----------------------------
 src/editor/views/index.ts         |   7 --
 src/editor/views/onReceive.ts     |   6 --
 src/editor/views/utils/nonce.ts   |   8 --
 src/extension.ts                  |  11 ++-
 src/state/index.ts                |  57 +++++++-----
 src/state/message.ts              |  13 +++
 src/typings/index.d.ts            |  10 +-
 12 files changed, 288 insertions(+), 223 deletions(-)
 create mode 100644 src/editor/ReactWebView.ts
 create mode 100644 src/editor/index.ts
 delete mode 100644 src/editor/init.ts
 delete mode 100644 src/editor/views/createWebview.ts
 delete mode 100644 src/editor/views/index.ts
 delete mode 100644 src/editor/views/onReceive.ts
 delete mode 100644 src/editor/views/utils/nonce.ts
 create mode 100644 src/state/message.ts

diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts
new file mode 100644
index 00000000..56247edc
--- /dev/null
+++ b/src/editor/ReactWebView.ts
@@ -0,0 +1,146 @@
+import * as vscode from 'vscode'
+import * as CR from 'typings'
+import * as path from 'path'
+
+function getNonce(): string {
+    let text = ''
+    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+    for (let i = 0; i < 32; i++) {
+        text += possible.charAt(Math.floor(Math.random() * possible.length))
+    }
+    return text
+}
+
+// TODO: move column into createOrShow
+
+
+/**
+ * Manages React webview panels
+ */
+class ReactWebView {
+    /**
+     * Track the currently panel. Only allow a single panel to exist at a time.
+     */
+    public static currentPanel: ReactWebView | undefined = undefined
+
+    // @ts-ignore
+    private panel: vscode.WebviewPanel
+    private extensionPath: string
+    private disposables: vscode.Disposable[] = []
+    private onReceive: any // TODO: properly type
+
+    public constructor(extensionPath: string, onReceive: any) {
+        this.extensionPath = extensionPath
+        this.onReceive = onReceive
+    }
+
+    public async createOrShow(extensionPath: string, column: number = vscode.ViewColumn.One): Promise<void> {
+        const hasActiveEditor = vscode.window.activeTextEditor
+
+        if (!hasActiveEditor) {
+            throw new Error('Should have an open file on launch')
+        }
+
+        // If we already have a panel, show it.
+        // Otherwise, create a new panel.
+        if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) {
+            ReactWebView.currentPanel.panel.reveal(column)
+        } else {
+            const viewType = 'CodeRoad'
+            const title = 'CodeRoad'
+            const config = {
+                // Enable javascript in the webview
+                enableScripts: true,
+
+                // And restric the webview to only loading content from our extension's `media` directory.
+                localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))],
+
+                // prevents destroying the window when it is in the background
+                retainContextWhenHidden: true,
+            }
+            // Create and show a new webview panel
+            this.panel = vscode.window.createWebviewPanel(viewType, title, column, config)
+
+            // Set the webview's initial html content
+            this.panel.webview.html = this.getHtmlForWebview()
+
+            // Listen for when the panel is disposed
+            // This happens when the user closes the panel or when the panel is closed programatically
+            this.panel.onDidDispose(() => this.dispose(), null, this.disposables)
+
+            // Handle messages from the webview
+            this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables)
+        }
+    }
+
+    public async postMessage(action: CR.Action): Promise<void> {
+        // Send a message to the webview webview.
+        // You can send any JSON serializable data.
+        const success = await this.panel.webview.postMessage(action)
+        if (!success) {
+            throw new Error(`Message post failure: ${JSON.stringify(action)}`)
+        }
+    }
+
+    public dispose(): void {
+        ReactWebView.currentPanel = undefined
+
+        // Clean up our resources
+        this.panel.dispose()
+
+        while (this.disposables.length) {
+            const x = this.disposables.pop()
+            if (x) {
+                x.dispose()
+            }
+        }
+    }
+
+    private getHtmlForWebview(): string {
+
+        // eslint-disable-next-line
+        const manifest = require(path.join(this.extensionPath, 'build', 'asset-manifest.json'))
+        const mainScript = manifest.files['main.js']
+        // grab first chunk
+        const chunk = Object.keys(manifest.files).filter(f => f.match(/^static\/js\/.+\.js$/))[0]
+        const chunkScript = manifest.files[chunk]
+        const mainStyle = manifest.files['main.css']
+
+        const scriptPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainScript))
+        const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' })
+        const chunkPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', chunkScript))
+        const chunkUri = chunkPathOnDisk.with({ scheme: 'vscode-resource' })
+        const stylePathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainStyle))
+        const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' })
+
+        // Use a nonce to whitelist which scripts can be run
+        const nonce = getNonce()
+        const nonce2 = getNonce()
+        const nonce3 = getNonce()
+
+        return `<!DOCTYPE html>
+			<html lang="en">
+			<head>
+				<meta charset="utf-8">
+				<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
+                <meta name="theme-color" content="#000000">
+                <title>React App</title>
+                <link rel="manifest" href="./manifest.json" />
+				<link rel="stylesheet" type="text/css" href="${styleUri}">
+				<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}' 'nonce-${nonce2}' 'nonce-${nonce3}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
+                <base href="${vscode.Uri.file(path.join(this.extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
+                <style></style>
+			</head>
+
+			<body>
+				<noscript>You need to enable JavaScript to run this app.</noscript>
+                <div id="root">Loading...</div>
+                <script nonce=${nonce} src="./webpackBuild.js"></script>
+                <script nonce=${nonce2} src="${chunkUri}"></script>
+                <script nonce="${nonce3}" src="${scriptUri}"></script>
+			</body>
+            </html>`
+    }
+}
+
+export default ReactWebView
diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts
index 84c4a77a..48b44ddb 100644
--- a/src/editor/commands/start.ts
+++ b/src/editor/commands/start.ts
@@ -18,9 +18,9 @@ export default async function start(context: vscode.ExtensionContext): Promise<v
     console.log('TUTORIAL_START')
 
     // setup connection to workspace
-    await setWorkspaceRoot()
+    // await setWorkspaceRoot()
     // set workspace context path
-    await setStorage(context.workspaceState)
+    // await setStorage(context.workspaceState)
 
     // initialize state machine
     activateMachine()
diff --git a/src/editor/index.ts b/src/editor/index.ts
new file mode 100644
index 00000000..3b9b40d1
--- /dev/null
+++ b/src/editor/index.ts
@@ -0,0 +1,75 @@
+import * as vscode from 'vscode'
+import * as CR from '../typings'
+import { setStorage } from './storage'
+import ReactWebView from './ReactWebView'
+
+class Editor {
+    // extension context set on activation
+    // @ts-ignore
+    private context: vscode.ExtensionContext
+    private workspaceRoot: string | undefined
+    private machine: CR.StateMachine
+    private webview: any
+
+    private COMMANDS = {
+        START: 'coderoad.start',
+        OPEN_WEBVIEW: 'coderoad.open_webview',
+        RUN_TEST: 'coderoad.test_run',
+    }
+
+    constructor(machine: CR.StateMachine) {
+        this.machine = machine
+    }
+
+    private commandStart() {
+        // set workspace root
+        const { rootPath } = vscode.workspace
+        if (!rootPath) {
+            throw new Error('Requires a workspace. Please open a folder')
+        }
+        this.workspaceRoot = rootPath
+
+        // set local storage workspace
+        setStorage(this.context.workspaceState)
+
+        // activate machine
+        this.machine.activate()
+        this.webview = new ReactWebView(this.context.extensionPath, this.machine.onReceive)
+    }
+
+    private activateCommands() {
+        const { COMMANDS } = this
+        const commands = {
+            [COMMANDS.START]: () => {
+                this.commandStart()
+            },
+            [COMMANDS.OPEN_WEBVIEW]: () => {
+                this.webview.createOrShow(this.context.extensionPath);
+            },
+        }
+        for (const cmd in commands) {
+            const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
+            this.context.subscriptions.push(command)
+        }
+    }
+    public activate(context: vscode.ExtensionContext): void {
+        console.log('ACTIVATE!')
+        this.context = context
+        // commands
+        this.activateCommands()
+
+        // setup tasks or views here
+
+    }
+    public deactivate(): void {
+        console.log('DEACTIVATE!')
+        // cleanup subscriptions/tasks
+        for (const disposable of this.context.subscriptions) {
+            disposable.dispose()
+        }
+        // shut down state machine
+        this.machine.deactivate()
+    }
+}
+
+export default Editor
\ No newline at end of file
diff --git a/src/editor/init.ts b/src/editor/init.ts
deleted file mode 100644
index dbc9f64a..00000000
--- a/src/editor/init.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// The module 'vscode' contains the VS Code extensibility API
-// Import the module and reference it with the alias vscode in your code below
-import * as vscode from 'vscode'
-
-import { deactivate as deactivateMachine } from '../state'
-import createCommands from './commands'
-import createViews from './views'
-
-// this method is called when your extension is activated
-// your extension is activated the very first time the command is executed
-export function activate(context: vscode.ExtensionContext) {
-    console.log('ACTIVATE!')
-
-    // commands
-    createCommands(context)
-
-    // views
-    createViews(context)
-
-    // tasks
-    // add tasks here
-}
-
-// this method is called when your extension is deactivated
-export function deactivate(context: vscode.ExtensionContext): void {
-    // cleanup subscriptions/tasks
-    console.log('deactivate context', context)
-    for (const disposable of context.subscriptions) {
-        disposable.dispose()
-    }
-    // shut down state machine
-    deactivateMachine()
-}
diff --git a/src/editor/views/createWebview.ts b/src/editor/views/createWebview.ts
deleted file mode 100644
index 321403a0..00000000
--- a/src/editor/views/createWebview.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as vscode from 'vscode'
-import * as CR from 'typings'
-import * as path from 'path'
-
-import getNonce from './utils/nonce'
-import onReceive from './onReceive'
-
-/**
- * Manages React webview panels
- */
-class ReactPanel {
-    /**
-     * Track the currently panel. Only allow a single panel to exist at a time.
-     */
-    public static currentPanel: ReactPanel | undefined = undefined
-
-    private readonly _panel: vscode.WebviewPanel
-    private readonly _extensionPath: string
-    private _disposables: vscode.Disposable[] = []
-
-    public static async createOrShow(extensionPath: string): Promise<void> {
-        // const hasActiveEditor = vscode.window.activeTextEditor
-
-        // if (!hasActiveEditor) {
-        //     throw new Error('Should have an open file on launch')
-        // }
-        const column = vscode.ViewColumn.One
-
-        // If we already have a panel, show it.
-        // Otherwise, create a new panel.
-        if (ReactPanel.currentPanel) {
-            console.log('--- HAS CURRENT PANEL ---')
-            ReactPanel.currentPanel._panel.reveal(column)
-        } else {
-            ReactPanel.currentPanel = new ReactPanel(extensionPath, column)
-        }
-    }
-
-    private constructor(extensionPath: string, column: vscode.ViewColumn) {
-        this._extensionPath = extensionPath
-
-        const viewType = 'CodeRoad'
-        const title = 'CodeRoad'
-        const config = {
-            // Enable javascript in the webview
-            enableScripts: true,
-
-            // And restric the webview to only loading content from our extension's `media` directory.
-            localResourceRoots: [vscode.Uri.file(path.join(this._extensionPath, 'build'))],
-
-            // prevents destroying the window when it is in the background
-            retainContextWhenHidden: true,
-        }
-
-        // Create and show a new webview panel
-        this._panel = vscode.window.createWebviewPanel(viewType, title, column, config)
-
-        // Set the webview's initial html content
-        this._panel.webview.html = this._getHtmlForWebview()
-
-        // Listen for when the panel is disposed
-        // This happens when the user closes the panel or when the panel is closed programatically
-        this._panel.onDidDispose(() => this.dispose(), null, this._disposables)
-
-        // Handle messages from the webview
-        this._panel.webview.onDidReceiveMessage(onReceive, null, this._disposables)
-    }
-
-    public async postMessage(action: CR.Action): Promise<void> {
-        // Send a message to the webview webview.
-        // You can send any JSON serializable data.
-        const success = await this._panel.webview.postMessage(action)
-        if (!success) {
-            throw new Error(`Message post failure: ${JSON.stringify(action)}`)
-        }
-    }
-
-    public dispose(): void {
-        ReactPanel.currentPanel = undefined
-
-        // Clean up our resources
-        this._panel.dispose()
-
-        while (this._disposables.length) {
-            const x = this._disposables.pop()
-            if (x) {
-                x.dispose()
-            }
-        }
-    }
-
-    private _getHtmlForWebview(): string {
-
-        // eslint-disable-next-line
-        const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'))
-        const mainScript = manifest.files['main.js']
-        // grab first chunk
-        const chunk = Object.keys(manifest.files).filter(f => f.match(/^static\/js\/.+\.js$/))[0]
-        const chunkScript = manifest.files[chunk]
-        const mainStyle = manifest.files['main.css']
-
-        const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainScript))
-        const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' })
-        const chunkPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', chunkScript))
-        const chunkUri = chunkPathOnDisk.with({ scheme: 'vscode-resource' })
-        const stylePathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainStyle))
-        const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' })
-
-        // Use a nonce to whitelist which scripts can be run
-        const nonce = getNonce()
-        const nonce2 = getNonce()
-        const nonce3 = getNonce()
-
-        const output = `<!DOCTYPE html>
-			<html lang="en">
-			<head>
-				<meta charset="utf-8">
-				<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
-                <meta name="theme-color" content="#000000">
-                <title>React App</title>
-                <link rel="manifest" href="./manifest.json" />
-				<link rel="stylesheet" type="text/css" href="${styleUri}">
-				<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}' 'nonce-${nonce2}' 'nonce-${nonce3}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
-                <base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
-                <style></style>
-			</head>
-
-			<body>
-				<noscript>You need to enable JavaScript to run this app.</noscript>
-                <div id="root">Loading...</div>
-                <script nonce=${nonce} src="./webpackBuild.js"></script>
-                <script nonce=${nonce2} src="${chunkUri}"></script>
-                <script nonce="${nonce3}" src="${scriptUri}"></script>
-			</body>
-            </html>`
-        console.log(output)
-        return output
-    }
-}
-
-export default ReactPanel
diff --git a/src/editor/views/index.ts b/src/editor/views/index.ts
deleted file mode 100644
index 60a7d871..00000000
--- a/src/editor/views/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as vscode from 'vscode'
-
-const createViews = (context: vscode.ExtensionContext) => {
-  // TODO: create views
-}
-
-export default createViews
diff --git a/src/editor/views/onReceive.ts b/src/editor/views/onReceive.ts
deleted file mode 100644
index ccd69eea..00000000
--- a/src/editor/views/onReceive.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import * as CR from 'typings'
-
-export default async (action: CR.Action): Promise<void> => {
-    console.log('action', action)
-    // TODO: call state machine
-}
diff --git a/src/editor/views/utils/nonce.ts b/src/editor/views/utils/nonce.ts
deleted file mode 100644
index 313819e1..00000000
--- a/src/editor/views/utils/nonce.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function getNonce(): string {
-  let text = ''
-  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-  for (let i = 0; i < 32; i++) {
-    text += possible.charAt(Math.floor(Math.random() * possible.length))
-  }
-  return text
-}
diff --git a/src/extension.ts b/src/extension.ts
index 40646445..80fd9c64 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,2 +1,11 @@
-export { activate, deactivate } from './editor/init'
+import StateMachine from './state'
+import Editor from './editor'
+
+// state machine that governs application logic
+const Machine = new StateMachine()
+// vscode editor
+const VSCodeEditor = new Editor(Machine)
+
+export const activate = VSCodeEditor.activate
+export const deactivate = VSCodeEditor.deactivate
 
diff --git a/src/state/index.ts b/src/state/index.ts
index 376b66ef..ab5f7686 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -1,31 +1,40 @@
-import { interpret } from 'xstate'
+import { interpret, Interpreter } from 'xstate'
+import * as CR from '../typings'
 import machine from './machine'
 
-const machineOptions = {
-    logger: console.log,
-    devTools: true,
-    deferEvents: true,
-    execute: true
-}
 // machine interpreter
 // https://xstate.js.org/docs/guides/interpretation.html
-const service = interpret(machine, machineOptions)
-    // logging
-    .onTransition(state => {
-        console.log('state', state)
-        if (state.changed) {
-            console.log('transition')
-            console.log(state.value)
-        }
-    })
-
-export function activate() {
-    // initialize
-    service.start()
-}
 
-export function deactivate() {
-    service.stop()
+class StateMachine {
+    private machineOptions = {
+        logger: console.log,
+        devTools: true,
+        deferEvents: true,
+        execute: true
+    }
+    private service: Interpreter<CR.MachineContext, CR.MachineStateSchema, CR.MachineEvent>
+    constructor() {
+        this.service = interpret(machine, this.machineOptions)
+            // logging
+            .onTransition(state => {
+                console.log('state', state)
+                if (state.changed) {
+                    console.log('transition')
+                    console.log(state.value)
+                }
+            })
+    }
+    activate() {
+        // initialize
+        this.service.start()
+    }
+    deactivate() {
+        this.service.stop()
+    }
+    onReceive(action: CR.Action) {
+        console.log('RECEIVED ACTION')
+        console.log(action)
+    }
 }
 
-export default service
+export default StateMachine
diff --git a/src/state/message.ts b/src/state/message.ts
new file mode 100644
index 00000000..2393abde
--- /dev/null
+++ b/src/state/message.ts
@@ -0,0 +1,13 @@
+import panel from '../views/Panel'
+import * as CR from '../typings'
+
+export const onSend = (action: CR.Action) => {
+  if (!panel || !panel.currentPanel) {
+    throw new Error('No valid panel available')
+  }
+  panel.currentPanel.postMessage(action)
+}
+
+export const onReceive = (action: CR.Action) => {
+  console.log(`RECEIVED: ${JSON.stringify(action)}`)
+}
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 0c16d246..4c8a9bf3 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -1,3 +1,5 @@
+import { onReceive } from "state/message";
+
 export interface TutorialLevel {
     stageList: string[]
     content: {
@@ -159,4 +161,10 @@ export interface MachineStateSchema {
             }
         }
     }
-}
\ No newline at end of file
+}
+
+export interface StateMachine {
+    activate(): void
+    deactivate(): void
+    onReceive(action: Action): void
+}

From 3261c339f5fa50d74b7152d6933fd32cf7088262 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sat, 8 Jun 2019 19:39:41 -0700
Subject: [PATCH 03/12] refactor order of launches

---
 src/editor/ReactWebView.ts   |  8 +---
 src/editor/commands/index.ts | 66 +++++++++++++++---------------
 src/editor/commands/start.ts | 78 ++++++++++++++++++------------------
 src/editor/index.ts          | 24 ++++++-----
 src/state/message.ts         | 10 ++---
 5 files changed, 93 insertions(+), 93 deletions(-)

diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts
index 56247edc..e19c8f56 100644
--- a/src/editor/ReactWebView.ts
+++ b/src/editor/ReactWebView.ts
@@ -34,13 +34,7 @@ class ReactWebView {
         this.onReceive = onReceive
     }
 
-    public async createOrShow(extensionPath: string, column: number = vscode.ViewColumn.One): Promise<void> {
-        const hasActiveEditor = vscode.window.activeTextEditor
-
-        if (!hasActiveEditor) {
-            throw new Error('Should have an open file on launch')
-        }
-
+    public async createOrShow(column: number = vscode.ViewColumn.One): Promise<void> {
         // If we already have a panel, show it.
         // Otherwise, create a new panel.
         if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) {
diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index ad8f39be..5fabd1fe 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -1,38 +1,38 @@
-import * as vscode from 'vscode'
-import start from './start'
-import ReactPanel from '../views/createWebview'
+// import * as vscode from 'vscode'
+// import start from './start'
+// // import ReactPanel from '../views/createWebview'
 
-import runTest from './runTest'
-// import loadSolution from './loadSolution'
-// import quit from './quit'
+// import runTest from './runTest'
+// // import loadSolution from './loadSolution'
+// // import quit from './quit'
 
-const COMMANDS = {
-  // TUTORIAL_SETUP: 'coderoad.tutorial_setup',
-  START: 'coderoad.start',
-  OPEN_WEBVIEW: 'coderoad.open_webview',
-  RUN_TEST: 'coderoad.test_run',
-  // LOAD_SOLUTION: 'coderoad.solution_load',
-  // QUIT: 'coderoad.quit',
-}
+// const COMMANDS = {
+//   // TUTORIAL_SETUP: 'coderoad.tutorial_setup',
+//   START: 'coderoad.start',
+//   OPEN_WEBVIEW: 'coderoad.open_webview',
+//   RUN_TEST: 'coderoad.test_run',
+//   // LOAD_SOLUTION: 'coderoad.solution_load',
+//   // QUIT: 'coderoad.quit',
+// }
 
 
-export default (context: vscode.ExtensionContext): void => {
-  const commands = {
-    [COMMANDS.START]: () => {
-      start(context)
-    },
-    [COMMANDS.OPEN_WEBVIEW]: () => {
-      ReactPanel.createOrShow(context.extensionPath);
-    },
-    [COMMANDS.RUN_TEST]: () => {
-      runTest()
-    },
-    // [COMMANDS.LOAD_SOLUTION]: loadSolution,
-    // [COMMANDS.QUIT]: () => quit(context.subscriptions),
-  }
+// export default (context: vscode.ExtensionContext): void => {
+//   const commands = {
+//     [COMMANDS.START]: () => {
+//       start(context)
+//     },
+//     [COMMANDS.OPEN_WEBVIEW]: () => {
+//       // ReactPanel.createOrShow(context.extensionPath);
+//     },
+//     [COMMANDS.RUN_TEST]: () => {
+//       runTest()
+//     },
+//     // [COMMANDS.LOAD_SOLUTION]: loadSolution,
+//     // [COMMANDS.QUIT]: () => quit(context.subscriptions),
+//   }
 
-  for (const cmd in commands) {
-    const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
-    context.subscriptions.push(command)
-  }
-}
+//   for (const cmd in commands) {
+//     const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
+//     context.subscriptions.push(command)
+//   }
+// }
diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts
index 48b44ddb..a7e80ae3 100644
--- a/src/editor/commands/start.ts
+++ b/src/editor/commands/start.ts
@@ -1,45 +1,45 @@
-import * as vscode from 'vscode'
-import { setWorkspaceRoot } from '../../services/node'
-import { setStorage } from '../../editor/storage'
-import { activate as activateMachine, default as machine } from '../../state'
-import * as storage from '../../services/storage'
-import * as git from '../../services/git'
-import * as CR from 'typings'
+// import * as vscode from 'vscode'
+// import { setWorkspaceRoot } from '../../services/node'
+// import { setStorage } from '../../editor/storage'
+// import { activate as activateMachine, default as machine } from '../../state'
+// import * as storage from '../../services/storage'
+// import * as git from '../../services/git'
+// import * as CR from 'typings'
 
-let initialTutorial: CR.Tutorial | undefined
-let initialProgress: CR.Progress = {
-    levels: {},
-    stages: {},
-    steps: {},
-    complete: false,
-}
+// let initialTutorial: CR.Tutorial | undefined
+// let initialProgress: CR.Progress = {
+//     levels: {},
+//     stages: {},
+//     steps: {},
+//     complete: false,
+// }
 
-export default async function start(context: vscode.ExtensionContext): Promise<void> {
-    console.log('TUTORIAL_START')
+// export default async function start(context: vscode.ExtensionContext): Promise<void> {
+//     console.log('TUTORIAL_START')
 
-    // setup connection to workspace
-    // await setWorkspaceRoot()
-    // set workspace context path
-    // await setStorage(context.workspaceState)
+//     // setup connection to workspace
+//     // await setWorkspaceRoot()
+//     // set workspace context path
+//     // await setStorage(context.workspaceState)
 
-    // initialize state machine
-    activateMachine()
+//     // initialize state machine
+//     activateMachine()
 
-    console.log('ACTION: start')
+//     console.log('ACTION: start')
 
-    // verify that the user has a tutorial & progress
-    // verify git is setup with a coderoad remote
-    const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([
-        storage.getTutorial(),
-        storage.getProgress(),
-        git.gitVersion(),
-        git.gitCheckRemoteExists(),
-    ])
-    initialTutorial = tutorial
-    initialProgress = progress
-    const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
-    console.log('canContinue', canContinue)
-    // if a tutorial exists, "CONTINUE"
-    // otherwise start from "NEW"
-    machine.send(canContinue ? 'CONTINUE' : 'NEW')
-}
\ No newline at end of file
+//     // verify that the user has a tutorial & progress
+//     // verify git is setup with a coderoad remote
+//     const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([
+//         storage.getTutorial(),
+//         storage.getProgress(),
+//         git.gitVersion(),
+//         git.gitCheckRemoteExists(),
+//     ])
+//     initialTutorial = tutorial
+//     initialProgress = progress
+//     const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
+//     console.log('canContinue', canContinue)
+//     // if a tutorial exists, "CONTINUE"
+//     // otherwise start from "NEW"
+//     machine.send(canContinue ? 'CONTINUE' : 'NEW')
+// }
\ No newline at end of file
diff --git a/src/editor/index.ts b/src/editor/index.ts
index 3b9b40d1..870ea187 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -21,7 +21,7 @@ class Editor {
         this.machine = machine
     }
 
-    private commandStart() {
+    private commandStart = (): void => {
         // set workspace root
         const { rootPath } = vscode.workspace
         if (!rootPath) {
@@ -33,18 +33,24 @@ class Editor {
         setStorage(this.context.workspaceState)
 
         // activate machine
-        this.machine.activate()
         this.webview = new ReactWebView(this.context.extensionPath, this.machine.onReceive)
+        this.machine.activate()
+
+        console.log('command start webview')
+        console.log(this.webview)
     }
 
-    private activateCommands() {
-        const { COMMANDS } = this
+    private activateCommands = (): void => {
+        console.log('this.COMMANDS', this.COMMANDS)
         const commands = {
-            [COMMANDS.START]: () => {
+            [this.COMMANDS.START]: () => {
+                console.log('start')
                 this.commandStart()
             },
-            [COMMANDS.OPEN_WEBVIEW]: () => {
-                this.webview.createOrShow(this.context.extensionPath);
+            [this.COMMANDS.OPEN_WEBVIEW]: () => {
+                console.log('open webview')
+                console.log(this.webview)
+                this.webview.createOrShow();
             },
         }
         for (const cmd in commands) {
@@ -52,7 +58,7 @@ class Editor {
             this.context.subscriptions.push(command)
         }
     }
-    public activate(context: vscode.ExtensionContext): void {
+    public activate = (context: vscode.ExtensionContext): void => {
         console.log('ACTIVATE!')
         this.context = context
         // commands
@@ -61,7 +67,7 @@ class Editor {
         // setup tasks or views here
 
     }
-    public deactivate(): void {
+    public deactivate = (): void => {
         console.log('DEACTIVATE!')
         // cleanup subscriptions/tasks
         for (const disposable of this.context.subscriptions) {
diff --git a/src/state/message.ts b/src/state/message.ts
index 2393abde..70c2909f 100644
--- a/src/state/message.ts
+++ b/src/state/message.ts
@@ -1,11 +1,11 @@
-import panel from '../views/Panel'
+// import panel from '../views/Panel'
 import * as CR from '../typings'
 
 export const onSend = (action: CR.Action) => {
-  if (!panel || !panel.currentPanel) {
-    throw new Error('No valid panel available')
-  }
-  panel.currentPanel.postMessage(action)
+  // if (!panel || !panel.currentPanel) {
+  //   throw new Error('No valid panel available')
+  // }
+  // panel.currentPanel.postMessage(action)
 }
 
 export const onReceive = (action: CR.Action) => {

From 6d8b993d332134b8ab6b10ec364cc9ab86bf026d Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sat, 8 Jun 2019 20:25:21 -0700
Subject: [PATCH 04/12] further editor refactoring

---
 src/editor/commands/index.ts | 75 ++++++++++++++++++++----------------
 src/editor/commands/quit.ts  |  5 ---
 src/editor/index.ts          | 53 ++++++++-----------------
 src/extension.ts             | 14 ++++---
 src/services/git/index.ts    |  5 ++-
 src/services/node/index.ts   | 19 +--------
 src/services/testResult.ts   |  1 -
 src/state/index.ts           |  3 +-
 8 files changed, 71 insertions(+), 104 deletions(-)
 delete mode 100644 src/editor/commands/quit.ts

diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 5fabd1fe..94f0c7d9 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -1,38 +1,47 @@
-// import * as vscode from 'vscode'
-// import start from './start'
-// // import ReactPanel from '../views/createWebview'
+import * as vscode from 'vscode'
+import { join } from 'path'
+import { setStorage } from '../storage'
+import ReactWebView from '../ReactWebView'
+import * as CR from '../../typings'
 
-// import runTest from './runTest'
-// // import loadSolution from './loadSolution'
-// // import quit from './quit'
+const COMMANDS = {
+    START: 'coderoad.start',
+    OPEN_WEBVIEW: 'coderoad.open_webview',
+    OPEN_FILE: 'coderoad.open_file',
+    RUN_TEST: 'coderoad.test_run',
+}
 
-// const COMMANDS = {
-//   // TUTORIAL_SETUP: 'coderoad.tutorial_setup',
-//   START: 'coderoad.start',
-//   OPEN_WEBVIEW: 'coderoad.open_webview',
-//   RUN_TEST: 'coderoad.test_run',
-//   // LOAD_SOLUTION: 'coderoad.solution_load',
-//   // QUIT: 'coderoad.quit',
-// }
+interface CreateCommandProps {
+    context: vscode.ExtensionContext,
+    machine: CR.StateMachine
+}
 
+// React panel webview
+let webview: any;
 
-// export default (context: vscode.ExtensionContext): void => {
-//   const commands = {
-//     [COMMANDS.START]: () => {
-//       start(context)
-//     },
-//     [COMMANDS.OPEN_WEBVIEW]: () => {
-//       // ReactPanel.createOrShow(context.extensionPath);
-//     },
-//     [COMMANDS.RUN_TEST]: () => {
-//       runTest()
-//     },
-//     // [COMMANDS.LOAD_SOLUTION]: loadSolution,
-//     // [COMMANDS.QUIT]: () => quit(context.subscriptions),
-//   }
+export const createCommands = ({ context, machine }: CreateCommandProps) => ({
+    [COMMANDS.START]: () => {
+        // set local storage workspace
+        setStorage(context.workspaceState)
 
-//   for (const cmd in commands) {
-//     const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
-//     context.subscriptions.push(command)
-//   }
-// }
+        // activate machine
+        webview = new ReactWebView(context.extensionPath, machine.onReceive)
+        machine.activate()
+    },
+    [COMMANDS.OPEN_WEBVIEW]: () => {
+        webview.createOrShow();
+    },
+    [COMMANDS.OPEN_FILE]: async (relativeFilePath: string) => {
+        try {
+            const workspaceRoot = vscode.workspace.rootPath
+            if (!workspaceRoot) {
+                throw new Error('No workspace root path')
+            }
+            const absoluteFilePath = join(workspaceRoot, relativeFilePath)
+            const doc = await vscode.workspace.openTextDocument(absoluteFilePath)
+            await vscode.window.showTextDocument(doc, vscode.ViewColumn.One)
+        } catch (error) {
+            console.log(`Failed to open file ${relativeFilePath}`, error)
+        }
+    }
+})
\ No newline at end of file
diff --git a/src/editor/commands/quit.ts b/src/editor/commands/quit.ts
deleted file mode 100644
index 06c2254b..00000000
--- a/src/editor/commands/quit.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as vscode from 'vscode'
-
-export default () => {
-  // TODO: quit
-}
diff --git a/src/editor/index.ts b/src/editor/index.ts
index 870ea187..5bacfebf 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -1,58 +1,35 @@
 import * as vscode from 'vscode'
 import * as CR from '../typings'
-import { setStorage } from './storage'
-import ReactWebView from './ReactWebView'
+import { createCommands } from './commands'
+
+interface Props {
+    machine: CR.StateMachine,
+    setWorkspaceRoot(rootPath: string): void
+}
 
 class Editor {
     // extension context set on activation
     // @ts-ignore
     private context: vscode.ExtensionContext
-    private workspaceRoot: string | undefined
     private machine: CR.StateMachine
-    private webview: any
 
-    private COMMANDS = {
-        START: 'coderoad.start',
-        OPEN_WEBVIEW: 'coderoad.open_webview',
-        RUN_TEST: 'coderoad.test_run',
-    }
-
-    constructor(machine: CR.StateMachine) {
+    constructor({ machine, setWorkspaceRoot }: Props) {
         this.machine = machine
-    }
 
-    private commandStart = (): void => {
-        // set workspace root
-        const { rootPath } = vscode.workspace
+        // set workspace root for node executions
+        const { workspace } = vscode
+        const { rootPath } = workspace
         if (!rootPath) {
             throw new Error('Requires a workspace. Please open a folder')
         }
-        this.workspaceRoot = rootPath
-
-        // set local storage workspace
-        setStorage(this.context.workspaceState)
-
-        // activate machine
-        this.webview = new ReactWebView(this.context.extensionPath, this.machine.onReceive)
-        this.machine.activate()
-
-        console.log('command start webview')
-        console.log(this.webview)
+        setWorkspaceRoot(rootPath)
     }
 
     private activateCommands = (): void => {
-        console.log('this.COMMANDS', this.COMMANDS)
-        const commands = {
-            [this.COMMANDS.START]: () => {
-                console.log('start')
-                this.commandStart()
-            },
-            [this.COMMANDS.OPEN_WEBVIEW]: () => {
-                console.log('open webview')
-                console.log(this.webview)
-                this.webview.createOrShow();
-            },
-        }
+        const commands = createCommands({
+            context: this.context,
+            machine: this.machine,
+        })
         for (const cmd in commands) {
             const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
             this.context.subscriptions.push(command)
diff --git a/src/extension.ts b/src/extension.ts
index 80fd9c64..62a91e02 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,11 +1,15 @@
+import { setWorkspaceRoot } from './services/node'
 import StateMachine from './state'
 import Editor from './editor'
 
 // state machine that governs application logic
-const Machine = new StateMachine()
-// vscode editor
-const VSCodeEditor = new Editor(Machine)
+export const machine = new StateMachine()
 
-export const activate = VSCodeEditor.activate
-export const deactivate = VSCodeEditor.deactivate
+// vscode editor
+export const editor = new Editor({
+    machine,
+    setWorkspaceRoot,
+})
 
+export const activate = editor.activate
+export const deactivate = editor.deactivate
diff --git a/src/services/git/index.ts b/src/services/git/index.ts
index 347ef142..46cc654f 100644
--- a/src/services/git/index.ts
+++ b/src/services/git/index.ts
@@ -1,5 +1,6 @@
+import * as vscode from 'vscode'
 import * as CR from 'typings'
-import { exec, exists, openFile } from '../node'
+import { exec, exists } from '../node'
 
 const gitOrigin = 'coderoad'
 
@@ -40,7 +41,7 @@ export async function gitLoadCommits(actions: CR.TutorialAction): Promise<void>
 
   if (files) {
     for (const filePath of files) {
-      openFile(filePath)
+      vscode.commands.executeCommand('coderoad.open_webview', filePath)
     }
   }
 }
diff --git a/src/services/node/index.ts b/src/services/node/index.ts
index 0a5234d9..04aa976e 100644
--- a/src/services/node/index.ts
+++ b/src/services/node/index.ts
@@ -1,6 +1,4 @@
-import { workspace } from 'vscode'
 import * as fs from 'fs'
-import * as vscode from 'vscode'
 import { join } from 'path'
 import { exec as cpExec } from 'child_process'
 import { promisify } from 'util'
@@ -11,11 +9,7 @@ let workspaceRoot: string
 
 // set workspace root
 // other function will use this to target the correct cwd
-export async function setWorkspaceRoot(): Promise<void> {
-  const { rootPath } = workspace
-  if (!rootPath) {
-    throw new Error('Requires a workspace. Please open a folder')
-  }
+export function setWorkspaceRoot(rootPath: string): void {
   workspaceRoot = rootPath
 }
 
@@ -28,17 +22,6 @@ export const exec = (cmd: string): Promise<{ stdout: string; stderr: string }> =
 // collect all paths together
 export const exists = (...paths: string[]): boolean => fs.existsSync(join(workspaceRoot, ...paths))
 
-export const openFile = async (relativeFilePath: string): Promise<void> => {
-  try {
-    const absoluteFilePath = join(workspaceRoot, relativeFilePath)
-    const doc = await vscode.workspace.openTextDocument(absoluteFilePath)
-    await vscode.window.showTextDocument(doc, vscode.ViewColumn.One)
-  } catch (error) {
-    console.log(`Failed to open file ${relativeFilePath}`, error)
-  }
-}
-
-
 // export async function clear(): Promise<void> {
 //   // remove all files including ignored
 //   // NOTE: Linux only
diff --git a/src/services/testResult.ts b/src/services/testResult.ts
index b2e3fb0f..056646d1 100644
--- a/src/services/testResult.ts
+++ b/src/services/testResult.ts
@@ -2,7 +2,6 @@ import * as CR from 'typings'
 import * as vscode from 'vscode'
 import * as storage from './storage'
 
-
 export async function onSuccess(position: CR.Position) {
   console.log('onSuccess', position)
   vscode.window.showInformationMessage('SUCCESS')
diff --git a/src/state/index.ts b/src/state/index.ts
index ab5f7686..516f1358 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -17,9 +17,8 @@ class StateMachine {
         this.service = interpret(machine, this.machineOptions)
             // logging
             .onTransition(state => {
-                console.log('state', state)
                 if (state.changed) {
-                    console.log('transition')
+                    console.log('next state')
                     console.log(state.value)
                 }
             })

From 0a3e3956adbb02e8ad5093b1165cd3a6e5f88f23 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sat, 8 Jun 2019 20:30:26 -0700
Subject: [PATCH 05/12] cleanup component structure

---
 web-app/src/App.css                | 33 ------------------------------
 web-app/src/App.test.tsx           |  9 --------
 web-app/src/App.tsx                | 14 -------------
 web-app/src/Routes.tsx             |  5 +++++
 web-app/src/index.tsx              |  7 ++++---
 web-app/src/logo.svg               |  7 -------
 web-app/src/{ => styles}/index.css |  0
 7 files changed, 9 insertions(+), 66 deletions(-)
 delete mode 100644 web-app/src/App.css
 delete mode 100644 web-app/src/App.test.tsx
 delete mode 100644 web-app/src/App.tsx
 create mode 100644 web-app/src/Routes.tsx
 delete mode 100644 web-app/src/logo.svg
 rename web-app/src/{ => styles}/index.css (100%)

diff --git a/web-app/src/App.css b/web-app/src/App.css
deleted file mode 100644
index b41d297c..00000000
--- a/web-app/src/App.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.App {
-  text-align: center;
-}
-
-.App-logo {
-  animation: App-logo-spin infinite 20s linear;
-  height: 40vmin;
-  pointer-events: none;
-}
-
-.App-header {
-  background-color: #282c34;
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: calc(10px + 2vmin);
-  color: white;
-}
-
-.App-link {
-  color: #61dafb;
-}
-
-@keyframes App-logo-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}
diff --git a/web-app/src/App.test.tsx b/web-app/src/App.test.tsx
deleted file mode 100644
index a754b201..00000000
--- a/web-app/src/App.test.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './App';
-
-it('renders without crashing', () => {
-  const div = document.createElement('div');
-  ReactDOM.render(<App />, div);
-  ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx
deleted file mode 100644
index 3b8aacc4..00000000
--- a/web-app/src/App.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import './App.css';
-
-const App: React.FC = () => {
-  return (
-    <div className="App">
-      <header className="App-header">
-        Hello World
-      </header>
-    </div>
-  );
-}
-
-export default App;
diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
new file mode 100644
index 00000000..a9953b31
--- /dev/null
+++ b/web-app/src/Routes.tsx
@@ -0,0 +1,5 @@
+import * as React from 'react'
+
+export default () => {
+    return <div>Hello World</div>
+}
\ No newline at end of file
diff --git a/web-app/src/index.tsx b/web-app/src/index.tsx
index b7e93595..ba003317 100644
--- a/web-app/src/index.tsx
+++ b/web-app/src/index.tsx
@@ -1,6 +1,7 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
+import Routes from './Routes';
 
-ReactDOM.render(<App />, document.getElementById('root') as HTMLElement)
+import './styles/index.css';
+
+ReactDOM.render(<Routes />, document.getElementById('root') as HTMLElement)
diff --git a/web-app/src/logo.svg b/web-app/src/logo.svg
deleted file mode 100644
index 6b60c104..00000000
--- a/web-app/src/logo.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
-    <g fill="#61DAFB">
-        <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
-        <circle cx="420.9" cy="296.5" r="45.7"/>
-        <path d="M520.5 78.1z"/>
-    </g>
-</svg>
diff --git a/web-app/src/index.css b/web-app/src/styles/index.css
similarity index 100%
rename from web-app/src/index.css
rename to web-app/src/styles/index.css

From ec2e0e0d6a428d1ca6c101315127dff3b480b31b Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 10:56:51 -0700
Subject: [PATCH 06/12] absolute path typings

---
 src/editor/commands/index.ts              |  2 +-
 src/editor/index.ts                       |  2 +-
 src/state/message.ts                      |  2 +-
 tsconfig.json                             |  2 +-
 {src/typings => typings}/context.d.ts     |  0
 {src/typings => typings}/index.d.ts       |  2 --
 web-app/src/components/Continue/index.tsx |  2 +-
 web-app/src/components/Level/index.tsx    |  2 +-
 web-app/src/components/Stage/index.tsx    |  2 +-
 web-app/src/components/Step/index.tsx     |  2 +-
 web-app/src/components/Summary/index.tsx  |  2 +-
 web-app/tsconfig.json                     | 18 ++++--------------
 web-app/tsconfig.paths.json               | 21 +++++++++++++++++++++
 13 files changed, 34 insertions(+), 25 deletions(-)
 rename {src/typings => typings}/context.d.ts (100%)
 rename {src/typings => typings}/index.d.ts (98%)
 create mode 100644 web-app/tsconfig.paths.json

diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 94f0c7d9..2b96940e 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -2,7 +2,7 @@ import * as vscode from 'vscode'
 import { join } from 'path'
 import { setStorage } from '../storage'
 import ReactWebView from '../ReactWebView'
-import * as CR from '../../typings'
+import * as CR from 'typings'
 
 const COMMANDS = {
     START: 'coderoad.start',
diff --git a/src/editor/index.ts b/src/editor/index.ts
index 5bacfebf..db128230 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -1,5 +1,5 @@
 import * as vscode from 'vscode'
-import * as CR from '../typings'
+import * as CR from 'typings'
 import { createCommands } from './commands'
 
 interface Props {
diff --git a/src/state/message.ts b/src/state/message.ts
index 70c2909f..571b2100 100644
--- a/src/state/message.ts
+++ b/src/state/message.ts
@@ -1,5 +1,5 @@
 // import panel from '../views/Panel'
-import * as CR from '../typings'
+import * as CR from 'typings'
 
 export const onSend = (action: CR.Action) => {
   // if (!panel || !panel.currentPanel) {
diff --git a/tsconfig.json b/tsconfig.json
index 8cd5646e..cef79727 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,7 @@
 		"experimentalDecorators": true,
 		"emitDecoratorMetadata": true,
 		"paths": {
-			"typings": ["./typings/index.d.ts"],
+			"typings": ["../typings/index.d.ts"],
 		},
 	},
 	"exclude": [
diff --git a/src/typings/context.d.ts b/typings/context.d.ts
similarity index 100%
rename from src/typings/context.d.ts
rename to typings/context.d.ts
diff --git a/src/typings/index.d.ts b/typings/index.d.ts
similarity index 98%
rename from src/typings/index.d.ts
rename to typings/index.d.ts
index 4c8a9bf3..73b2b99f 100644
--- a/src/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1,5 +1,3 @@
-import { onReceive } from "state/message";
-
 export interface TutorialLevel {
     stageList: string[]
     content: {
diff --git a/web-app/src/components/Continue/index.tsx b/web-app/src/components/Continue/index.tsx
index c99195df..ea65eaac 100644
--- a/web-app/src/components/Continue/index.tsx
+++ b/web-app/src/components/Continue/index.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react'
-import CR from '../../../../src/typings'
+import CR from 'typings'
 
 import ContinueItem from './ContinueItem'
 
diff --git a/web-app/src/components/Level/index.tsx b/web-app/src/components/Level/index.tsx
index 403c9f3d..a48d8ee5 100644
--- a/web-app/src/components/Level/index.tsx
+++ b/web-app/src/components/Level/index.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react'
 import { Button, Card } from '@alifd/next'
-import CR from '../../../../src/typings'
+import CR from 'typings'
 
 const styles = {
   card: {
diff --git a/web-app/src/components/Stage/index.tsx b/web-app/src/components/Stage/index.tsx
index ac45304c..16b7a104 100644
--- a/web-app/src/components/Stage/index.tsx
+++ b/web-app/src/components/Stage/index.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react'
 import { Button, Card } from '@alifd/next'
-import CR from '../../../../src/typings'
+import CR from 'typings'
 
 import Step from '../Step'
 
diff --git a/web-app/src/components/Step/index.tsx b/web-app/src/components/Step/index.tsx
index 1e764e17..372c77d4 100644
--- a/web-app/src/components/Step/index.tsx
+++ b/web-app/src/components/Step/index.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react'
 import { Checkbox } from '@alifd/next'
 // import CC from '../../typings/client'
-import CR from '../../../../src/typings'
+import CR from 'typings'
 
 const styles = {
   card: {
diff --git a/web-app/src/components/Summary/index.tsx b/web-app/src/components/Summary/index.tsx
index 00509b27..e00ac952 100644
--- a/web-app/src/components/Summary/index.tsx
+++ b/web-app/src/components/Summary/index.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react'
 import { Button, Card } from '@alifd/next'
-import CR from '../../../../src/typings'
+import CR from 'typings'
 
 const styles = {
   card: {
diff --git a/web-app/tsconfig.json b/web-app/tsconfig.json
index e68470ec..04664a04 100644
--- a/web-app/tsconfig.json
+++ b/web-app/tsconfig.json
@@ -1,8 +1,10 @@
 {
+  "extends": "./tsconfig.paths.json",
   "compilerOptions": {
     "target": "es5",
     "lib": [
       "dom",
+      "dom.iterable",
       "esnext"
     ],
     "allowJs": true,
@@ -16,21 +18,9 @@
     "resolveJsonModule": true,
     "isolatedModules": true,
     "noEmit": true,
-    "jsx": "preserve",
-    "sourceMap": true,
-    "rootDirs": ["src", "stories"],
-    "baseUrl": "src",
-    "outDir": "build"
+    "jsx": "preserve"
   },
   "include": [
-    "src",
-    "../src/typings"
-  ],
-  "exclude": [
-    "node_modules",
-    "build",
-    "scripts",
-    "jest",
-    "public"
+    "src"
   ]
 }
diff --git a/web-app/tsconfig.paths.json b/web-app/tsconfig.paths.json
new file mode 100644
index 00000000..89a64aa3
--- /dev/null
+++ b/web-app/tsconfig.paths.json
@@ -0,0 +1,21 @@
+{
+    "compilerOptions": {
+        "baseUrl": "src",
+        "rootDirs": [
+            "src",
+            "stories"
+        ],
+        "paths": {
+            "typings": [
+                "../../typings/index.d.ts"
+            ],
+        }
+    },
+    "exclude": [
+        "node_modules",
+        "build",
+        "scripts",
+        "jest",
+        "public"
+    ]
+}
\ No newline at end of file

From b50e2c095bb37b21d05d0bdd3058e71fbe401c3a Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 14:25:14 -0700
Subject: [PATCH 07/12] support passing of messages to view

---
 src/editor/ReactWebView.ts   | 93 ++++++++++++++++++------------------
 src/editor/commands/index.ts | 53 ++++++++++++++++++--
 src/editor/commands/start.ts | 45 -----------------
 src/editor/index.ts          |  9 ++++
 src/extension.ts             |  1 +
 src/services/git/index.ts    |  2 +-
 src/state/actions/index.ts   |  3 ++
 src/state/index.ts           | 10 +++-
 src/state/machine.ts         |  8 +++-
 typings/index.d.ts           |  4 ++
 web-app/package.json         |  1 -
 web-app/src/Routes.tsx       | 34 +++++++++++--
 12 files changed, 161 insertions(+), 102 deletions(-)
 delete mode 100644 src/editor/commands/start.ts

diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts
index e19c8f56..ca941fcd 100644
--- a/src/editor/ReactWebView.ts
+++ b/src/editor/ReactWebView.ts
@@ -2,27 +2,10 @@ import * as vscode from 'vscode'
 import * as CR from 'typings'
 import * as path from 'path'
 
-function getNonce(): string {
-    let text = ''
-    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-    for (let i = 0; i < 32; i++) {
-        text += possible.charAt(Math.floor(Math.random() * possible.length))
-    }
-    return text
-}
-
-// TODO: move column into createOrShow
-
-
 /**
  * Manages React webview panels
  */
 class ReactWebView {
-    /**
-     * Track the currently panel. Only allow a single panel to exist at a time.
-     */
-    public static currentPanel: ReactWebView | undefined = undefined
-
     // @ts-ignore
     private panel: vscode.WebviewPanel
     private extensionPath: string
@@ -32,53 +15,71 @@ class ReactWebView {
     public constructor(extensionPath: string, onReceive: any) {
         this.extensionPath = extensionPath
         this.onReceive = onReceive
+
+        // Create and show a new webview panel
+        this.panel = this.createWebviewPanel(vscode.ViewColumn.One)
+
+        // Set the webview's initial html content
+        this.panel.webview.html = this.getHtmlForWebview()
+
+        // Listen for when the panel is disposed
+        // This happens when the user closes the panel or when the panel is closed programatically
+        this.panel.onDidDispose(() => this.dispose(), null, this.disposables)
+
+        // Handle messages from the webview
+        this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables)
+        console.log('webview loaded')
     }
 
-    public async createOrShow(column: number = vscode.ViewColumn.One): Promise<void> {
+    public async createOrShow(column: number): Promise<void> {
         // If we already have a panel, show it.
         // Otherwise, create a new panel.
-        if (ReactWebView.currentPanel && ReactWebView.currentPanel.panel) {
-            ReactWebView.currentPanel.panel.reveal(column)
+        if (this.panel && this.panel.webview) {
+            console.log('reveal')
+            this.panel.reveal(column)
         } else {
-            const viewType = 'CodeRoad'
-            const title = 'CodeRoad'
-            const config = {
-                // Enable javascript in the webview
-                enableScripts: true,
-
-                // And restric the webview to only loading content from our extension's `media` directory.
-                localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))],
+            console.log('make new panel')
+            this.panel = this.createWebviewPanel(column)
 
-                // prevents destroying the window when it is in the background
-                retainContextWhenHidden: true,
-            }
-            // Create and show a new webview panel
-            this.panel = vscode.window.createWebviewPanel(viewType, title, column, config)
-
-            // Set the webview's initial html content
-            this.panel.webview.html = this.getHtmlForWebview()
+        }
+    }
 
-            // Listen for when the panel is disposed
-            // This happens when the user closes the panel or when the panel is closed programatically
-            this.panel.onDidDispose(() => this.dispose(), null, this.disposables)
+    private createWebviewPanel(column: number): vscode.WebviewPanel {
+        const viewType = 'CodeRoad'
+        const title = 'CodeRoad'
+        const config = {
+            // Enable javascript in the webview
+            enableScripts: true,
+            // And restric the webview to only loading content from our extension's `media` directory.
+            localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))],
+            // prevents destroying the window when it is in the background
+            retainContextWhenHidden: true,
+        }
+        return vscode.window.createWebviewPanel(viewType, title, column, config)
+    }
 
-            // Handle messages from the webview
-            this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables)
+    private getNonce(): string {
+        let text = ''
+        const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+        for (let i = 0; i < 32; i++) {
+            text += possible.charAt(Math.floor(Math.random() * possible.length))
         }
+        return text
     }
 
     public async postMessage(action: CR.Action): Promise<void> {
+        console.log('webview postMessage')
+        console.log(action)
         // Send a message to the webview webview.
         // You can send any JSON serializable data.
         const success = await this.panel.webview.postMessage(action)
         if (!success) {
             throw new Error(`Message post failure: ${JSON.stringify(action)}`)
         }
+        console.log('postMessage sent')
     }
 
     public dispose(): void {
-        ReactWebView.currentPanel = undefined
-
         // Clean up our resources
         this.panel.dispose()
 
@@ -108,9 +109,9 @@ class ReactWebView {
         const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' })
 
         // Use a nonce to whitelist which scripts can be run
-        const nonce = getNonce()
-        const nonce2 = getNonce()
-        const nonce3 = getNonce()
+        const nonce = this.getNonce()
+        const nonce2 = this.getNonce()
+        const nonce3 = this.getNonce()
 
         return `<!DOCTYPE html>
 			<html lang="en">
diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 2b96940e..ec1c41e7 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -6,32 +6,65 @@ import * as CR from 'typings'
 
 const COMMANDS = {
     START: 'coderoad.start',
+    NEW_OR_CONTINUE: 'coderoad.new_or_continue',
     OPEN_WEBVIEW: 'coderoad.open_webview',
+    SEND_STATE: 'coderoad.send_state',
     OPEN_FILE: 'coderoad.open_file',
     RUN_TEST: 'coderoad.test_run',
 }
 
 interface CreateCommandProps {
     context: vscode.ExtensionContext,
-    machine: CR.StateMachine
+    machine: CR.StateMachine,
+    storage: any,
+    git: any
 }
 
 // React panel webview
 let webview: any;
+let initialTutorial: CR.Tutorial | undefined
+let initialProgress: CR.Progress = {
+    levels: {},
+    stages: {},
+    steps: {},
+    complete: false,
+}
 
-export const createCommands = ({ context, machine }: CreateCommandProps) => ({
+export const createCommands = ({ context, machine, storage, git }: CreateCommandProps) => ({
+    // initialize
     [COMMANDS.START]: () => {
         // set local storage workspace
         setStorage(context.workspaceState)
 
         // activate machine
         webview = new ReactWebView(context.extensionPath, machine.onReceive)
+        console.log('webview', webview.panel.webview.postMessage)
         machine.activate()
     },
-    [COMMANDS.OPEN_WEBVIEW]: () => {
-        webview.createOrShow();
+    [COMMANDS.NEW_OR_CONTINUE]: async () => {
+        // verify that the user has a tutorial & progress
+        // verify git is setup with a coderoad remote
+        const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([
+            storage.getTutorial(),
+            storage.getProgress(),
+            git.gitVersion(),
+            git.gitCheckRemoteExists(),
+        ])
+        initialTutorial = tutorial
+        initialProgress = progress
+        const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
+        console.log('canContinue', canContinue)
+        // if a tutorial exists, "CONTINUE"
+        // otherwise start from "NEW"
+        machine.send(canContinue ? 'CONTINUE' : 'NEW')
+    },
+    // open React webview
+    [COMMANDS.OPEN_WEBVIEW]: (column: number = vscode.ViewColumn.One) => {
+        webview.createOrShow(column);
     },
+    // open a file
     [COMMANDS.OPEN_FILE]: async (relativeFilePath: string) => {
+        console.log(`OPEN_FILE ${JSON.stringify(relativeFilePath)}`)
         try {
             const workspaceRoot = vscode.workspace.rootPath
             if (!workspaceRoot) {
@@ -43,5 +76,17 @@ export const createCommands = ({ context, machine }: CreateCommandProps) => ({
         } catch (error) {
             console.log(`Failed to open file ${relativeFilePath}`, error)
         }
+    },
+    // send messages to webview
+    [COMMANDS.SEND_STATE]: (action: CR.Action) => {
+        console.log(`SEND ${JSON.stringify(action)}`)
+        console.log('webview')
+        console.log(webview)
+        // console.log(webview.currentPanel)
+        // if (!webview || !webview.currentPanel) {
+        //     throw new Error('No valid panel available')
+        // }
+        webview.postMessage(action)
+
     }
 })
\ No newline at end of file
diff --git a/src/editor/commands/start.ts b/src/editor/commands/start.ts
deleted file mode 100644
index a7e80ae3..00000000
--- a/src/editor/commands/start.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-// import * as vscode from 'vscode'
-// import { setWorkspaceRoot } from '../../services/node'
-// import { setStorage } from '../../editor/storage'
-// import { activate as activateMachine, default as machine } from '../../state'
-// import * as storage from '../../services/storage'
-// import * as git from '../../services/git'
-// import * as CR from 'typings'
-
-// let initialTutorial: CR.Tutorial | undefined
-// let initialProgress: CR.Progress = {
-//     levels: {},
-//     stages: {},
-//     steps: {},
-//     complete: false,
-// }
-
-// export default async function start(context: vscode.ExtensionContext): Promise<void> {
-//     console.log('TUTORIAL_START')
-
-//     // setup connection to workspace
-//     // await setWorkspaceRoot()
-//     // set workspace context path
-//     // await setStorage(context.workspaceState)
-
-//     // initialize state machine
-//     activateMachine()
-
-//     console.log('ACTION: start')
-
-//     // verify that the user has a tutorial & progress
-//     // verify git is setup with a coderoad remote
-//     const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([
-//         storage.getTutorial(),
-//         storage.getProgress(),
-//         git.gitVersion(),
-//         git.gitCheckRemoteExists(),
-//     ])
-//     initialTutorial = tutorial
-//     initialProgress = progress
-//     const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
-//     console.log('canContinue', canContinue)
-//     // if a tutorial exists, "CONTINUE"
-//     // otherwise start from "NEW"
-//     machine.send(canContinue ? 'CONTINUE' : 'NEW')
-// }
\ No newline at end of file
diff --git a/src/editor/index.ts b/src/editor/index.ts
index db128230..26504478 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -1,6 +1,8 @@
 import * as vscode from 'vscode'
 import * as CR from 'typings'
 import { createCommands } from './commands'
+import * as storage from '../services/storage'
+import * as git from '../services/git'
 
 interface Props {
     machine: CR.StateMachine,
@@ -29,6 +31,8 @@ class Editor {
         const commands = createCommands({
             context: this.context,
             machine: this.machine,
+            storage,
+            git,
         })
         for (const cmd in commands) {
             const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
@@ -53,6 +57,11 @@ class Editor {
         // shut down state machine
         this.machine.deactivate()
     }
+
+    // execute vscode command
+    public dispatch = (type: string, payload: any) => {
+        vscode.commands.executeCommand(type, payload)
+    }
 }
 
 export default Editor
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 62a91e02..ad32b1d8 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -2,6 +2,7 @@ import { setWorkspaceRoot } from './services/node'
 import StateMachine from './state'
 import Editor from './editor'
 
+
 // state machine that governs application logic
 export const machine = new StateMachine()
 
diff --git a/src/services/git/index.ts b/src/services/git/index.ts
index 46cc654f..4cd7e34a 100644
--- a/src/services/git/index.ts
+++ b/src/services/git/index.ts
@@ -41,7 +41,7 @@ export async function gitLoadCommits(actions: CR.TutorialAction): Promise<void>
 
   if (files) {
     for (const filePath of files) {
-      vscode.commands.executeCommand('coderoad.open_webview', filePath)
+      vscode.commands.executeCommand('coderoad.open_file', filePath)
     }
   }
 }
diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts
index 3c435d38..c2abb20b 100644
--- a/src/state/actions/index.ts
+++ b/src/state/actions/index.ts
@@ -48,5 +48,8 @@ export default {
     createWebview() {
         console.log('execute coderoad.open_webview')
         vscode.commands.executeCommand('coderoad.open_webview')
+    },
+    newOrContinue() {
+        vscode.commands.executeCommand('coderoad.new_or_continue')
     }
 }
\ No newline at end of file
diff --git a/src/state/index.ts b/src/state/index.ts
index 516f1358..4d2905b4 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -1,6 +1,7 @@
 import { interpret, Interpreter } from 'xstate'
-import * as CR from '../typings'
+import * as CR from 'typings'
 import machine from './machine'
+import * as vscode from 'vscode'
 
 // machine interpreter
 // https://xstate.js.org/docs/guides/interpretation.html
@@ -17,9 +18,11 @@ class StateMachine {
         this.service = interpret(machine, this.machineOptions)
             // logging
             .onTransition(state => {
+                console.log('onTransition', state.changed)
                 if (state.changed) {
                     console.log('next state')
                     console.log(state.value)
+                    vscode.commands.executeCommand('coderoad.send_state', state.value)
                 }
             })
     }
@@ -30,6 +33,11 @@ class StateMachine {
     deactivate() {
         this.service.stop()
     }
+    send(action: string | CR.Action) {
+        console.log('machine.send')
+        console.log(action)
+        this.service.send(action)
+    }
     onReceive(action: CR.Action) {
         console.log('RECEIVED ACTION')
         console.log(action)
diff --git a/src/state/machine.ts b/src/state/machine.ts
index 49f6008b..273a915e 100644
--- a/src/state/machine.ts
+++ b/src/state/machine.ts
@@ -16,10 +16,16 @@ export const machine = Machine<
         initial: 'SelectTutorial',
         states: {
             SelectTutorial: {
+                onEntry: ['createWebview'],
                 initial: 'Initial',
                 states: {
                     Initial: {
-                        onEntry: ['createWebview'],
+                        after: {
+                            1000: 'Startup'
+                        }
+                    },
+                    Startup: {
+                        onEntry: ['newOrContinue'],
                         on: {
                             CONTINUE: 'ContinueTutorial',
                             NEW: 'NewTutorial',
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 73b2b99f..84b30f12 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1,3 +1,5 @@
+import { send } from "xstate";
+
 export interface TutorialLevel {
     stageList: string[]
     content: {
@@ -132,6 +134,7 @@ export interface MachineStateSchema {
         SelectTutorial: {
             states: {
                 Initial: {}
+                Startup: {}
                 NewTutorial: {
                     states: {
                         SelectTutorial: {}
@@ -164,5 +167,6 @@ export interface MachineStateSchema {
 export interface StateMachine {
     activate(): void
     deactivate(): void
+    send(action: string | Action): void
     onReceive(action: Action): void
 }
diff --git a/web-app/package.json b/web-app/package.json
index f2e82aec..4ac6c449 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -19,7 +19,6 @@
     "build": "react-scripts build",
     "postbuild": "cp -R ./build/ ../build/ && cp public/webpackBuild.js ../build/webpackBuild.js",
     "test": "react-scripts test",
-    "eject": "react-scripts eject",
     "storybook": "start-storybook -p 6006",
     "build-storybook": "build-storybook"
   },
diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index a9953b31..5d7c9a34 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -1,5 +1,33 @@
 import * as React from 'react'
+import * as CR from 'typings'
 
-export default () => {
-    return <div>Hello World</div>
-}
\ No newline at end of file
+interface ReceivedEvent {
+    data: CR.Action
+}
+
+const Routes = () => {
+    const [state, setState] = React.useState({ SelectTutorial: 'Initial' })
+    const handleEvent = (event: ReceivedEvent): void => {
+        console.log('--- HANDLE EVENT ---')
+        const message = event.data
+        console.log(`RECEIVED: ${JSON.stringify(message)}`)
+        // messages from core
+        if (message.type === 'SET_STATE') {
+            setState(message.payload)
+        }
+    }
+
+    // event bus listener
+    React.useEffect(() => {
+        const listener = 'message'
+        window.addEventListener(listener, handleEvent)
+        return () => {
+            window.removeEventListener(listener, handleEvent)
+        }
+    })
+    return (
+        <div>State: {JSON.stringify(state)}</div>
+    )
+}
+
+export default Routes
\ No newline at end of file

From 9c530c95b403e18b0ad0d7e1aaecea6abfee9ead Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 14:29:43 -0700
Subject: [PATCH 08/12] update state in client

---
 src/editor/commands/index.ts | 12 +++++-------
 web-app/src/Routes.tsx       |  1 -
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index ec1c41e7..05dd3939 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -54,8 +54,8 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
         initialProgress = progress
         const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
         console.log('canContinue', canContinue)
-        // if a tutorial exists, "CONTINUE"
-        // otherwise start from "NEW"
+        // if a tutorial exists, 'CONTINUE'
+        // otherwise start from 'NEW'
         machine.send(canContinue ? 'CONTINUE' : 'NEW')
     },
     // open React webview
@@ -78,15 +78,13 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
         }
     },
     // send messages to webview
-    [COMMANDS.SEND_STATE]: (action: CR.Action) => {
-        console.log(`SEND ${JSON.stringify(action)}`)
-        console.log('webview')
-        console.log(webview)
+    [COMMANDS.SEND_STATE]: (payload: any) => {
+        console.log(`SEND ${JSON.stringify(payload)}`)
         // console.log(webview.currentPanel)
         // if (!webview || !webview.currentPanel) {
         //     throw new Error('No valid panel available')
         // }
-        webview.postMessage(action)
+        webview.postMessage({ type: 'SET_STATE', payload })
 
     }
 })
\ No newline at end of file
diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index 5d7c9a34..1d65c837 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -8,7 +8,6 @@ interface ReceivedEvent {
 const Routes = () => {
     const [state, setState] = React.useState({ SelectTutorial: 'Initial' })
     const handleEvent = (event: ReceivedEvent): void => {
-        console.log('--- HANDLE EVENT ---')
         const message = event.data
         console.log(`RECEIVED: ${JSON.stringify(message)}`)
         // messages from core

From 864a26ed7732f8ad05c2c7a692d2db820d09f5f5 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 15:00:19 -0700
Subject: [PATCH 09/12] setup cond rendering route

---
 web-app/src/Routes.tsx                     | 13 ++++++++++++-
 web-app/src/components/Cond/index.tsx      | 17 +++++++++++++++++
 web-app/src/components/Cond/utils/state.ts | 12 ++++++++++++
 3 files changed, 41 insertions(+), 1 deletion(-)
 create mode 100644 web-app/src/components/Cond/index.tsx
 create mode 100644 web-app/src/components/Cond/utils/state.ts

diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index 1d65c837..f83e0363 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -1,5 +1,8 @@
 import * as React from 'react'
 import * as CR from 'typings'
+import NewPage from './components/New'
+import ContinuePage from './components/Continue'
+import Cond from './components/Cond'
 
 interface ReceivedEvent {
     data: CR.Action
@@ -24,8 +27,16 @@ const Routes = () => {
             window.removeEventListener(listener, handleEvent)
         }
     })
+
     return (
-        <div>State: {JSON.stringify(state)}</div>
+      <div>
+        <Cond state={state} path="SelectTutorial.NewTutorial">
+          <NewPage onNew={() => console.log('new!')} />
+        </Cond>
+        <Cond state={state} path="SelectTutorial.ContinueTutorial">
+          <ContinuePage onContinue={() => console.log('continue!')} tutorials={[]} />
+        </Cond>
+      </div>
     )
 }
 
diff --git a/web-app/src/components/Cond/index.tsx b/web-app/src/components/Cond/index.tsx
new file mode 100644
index 00000000..9eed24ba
--- /dev/null
+++ b/web-app/src/components/Cond/index.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react'
+import { stateMatch } from './utils/state'
+
+interface Props {
+    state: any
+    path: string
+    children: React.ReactElement
+}
+
+const Cond = (props: Props) => {
+    if (!stateMatch(props.state, props.path)) {
+        return null
+    }
+    return props.children
+}
+
+export default Cond
diff --git a/web-app/src/components/Cond/utils/state.ts b/web-app/src/components/Cond/utils/state.ts
new file mode 100644
index 00000000..5706cc57
--- /dev/null
+++ b/web-app/src/components/Cond/utils/state.ts
@@ -0,0 +1,12 @@
+export function stateMatch(state: any, statePath: string) {
+    let current = state
+    let paths = statePath.split('.')
+    try {
+        for (const p of paths) {
+            current = current[p]
+        }
+    } catch (error) {
+        return false
+    }
+    return current !== undefined
+}
\ No newline at end of file

From 08b28afd3d66695d663a5dcc0afbef93735f1ef6 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 15:43:15 -0700
Subject: [PATCH 10/12] run received actions through state machine

---
 src/editor/ReactWebView.ts   |  6 +++---
 src/editor/commands/index.ts | 16 +++++-----------
 src/state/index.ts           |  4 ++--
 typings/index.d.ts           |  2 +-
 web-app/src/Routes.tsx       | 11 ++++++++++-
 5 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/src/editor/ReactWebView.ts b/src/editor/ReactWebView.ts
index ca941fcd..de113d6b 100644
--- a/src/editor/ReactWebView.ts
+++ b/src/editor/ReactWebView.ts
@@ -12,9 +12,8 @@ class ReactWebView {
     private disposables: vscode.Disposable[] = []
     private onReceive: any // TODO: properly type
 
-    public constructor(extensionPath: string, onReceive: any) {
+    public constructor(extensionPath: string) {
         this.extensionPath = extensionPath
-        this.onReceive = onReceive
 
         // Create and show a new webview panel
         this.panel = this.createWebviewPanel(vscode.ViewColumn.One)
@@ -27,7 +26,8 @@ class ReactWebView {
         this.panel.onDidDispose(() => this.dispose(), null, this.disposables)
 
         // Handle messages from the webview
-        this.panel.webview.onDidReceiveMessage(this.onReceive, null, this.disposables)
+        const onReceive = (action: string | CR.Action) => vscode.commands.executeCommand('coderoad.receive_action', action)
+        this.panel.webview.onDidReceiveMessage(onReceive, null, this.disposables)
         console.log('webview loaded')
     }
 
diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 05dd3939..45d45a47 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -9,6 +9,7 @@ const COMMANDS = {
     NEW_OR_CONTINUE: 'coderoad.new_or_continue',
     OPEN_WEBVIEW: 'coderoad.open_webview',
     SEND_STATE: 'coderoad.send_state',
+    RECEIVE_ACTION: 'coderoad.receive_action',
     OPEN_FILE: 'coderoad.open_file',
     RUN_TEST: 'coderoad.test_run',
 }
@@ -22,13 +23,6 @@ interface CreateCommandProps {
 
 // React panel webview
 let webview: any;
-let initialTutorial: CR.Tutorial | undefined
-let initialProgress: CR.Progress = {
-    levels: {},
-    stages: {},
-    steps: {},
-    complete: false,
-}
 
 export const createCommands = ({ context, machine, storage, git }: CreateCommandProps) => ({
     // initialize
@@ -37,7 +31,7 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
         setStorage(context.workspaceState)
 
         // activate machine
-        webview = new ReactWebView(context.extensionPath, machine.onReceive)
+        webview = new ReactWebView(context.extensionPath)
         console.log('webview', webview.panel.webview.postMessage)
         machine.activate()
     },
@@ -50,8 +44,6 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
             git.gitVersion(),
             git.gitCheckRemoteExists(),
         ])
-        initialTutorial = tutorial
-        initialProgress = progress
         const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
         console.log('canContinue', canContinue)
         // if a tutorial exists, 'CONTINUE'
@@ -85,6 +77,8 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
         //     throw new Error('No valid panel available')
         // }
         webview.postMessage({ type: 'SET_STATE', payload })
-
+    },
+    [COMMANDS.RECEIVE_ACTION]: (action: string | CR.Action) => {
+        machine.onReceive(action)
     }
 })
\ No newline at end of file
diff --git a/src/state/index.ts b/src/state/index.ts
index 4d2905b4..d7f0e827 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -38,9 +38,9 @@ class StateMachine {
         console.log(action)
         this.service.send(action)
     }
-    onReceive(action: CR.Action) {
-        console.log('RECEIVED ACTION')
+    onReceive(action: string | CR.Action) {
         console.log(action)
+        this.service.send(action)
     }
 }
 
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 84b30f12..044be525 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -168,5 +168,5 @@ export interface StateMachine {
     activate(): void
     deactivate(): void
     send(action: string | Action): void
-    onReceive(action: Action): void
+    onReceive(action: string | Action): void
 }
diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index f83e0363..5822eea5 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -8,6 +8,15 @@ interface ReceivedEvent {
     data: CR.Action
 }
 
+declare var acquireVsCodeApi: any
+
+const vscode = acquireVsCodeApi()
+
+function send(event: string|CR.Action) {
+  return vscode.postMessage(event)
+}
+
+
 const Routes = () => {
     const [state, setState] = React.useState({ SelectTutorial: 'Initial' })
     const handleEvent = (event: ReceivedEvent): void => {
@@ -31,7 +40,7 @@ const Routes = () => {
     return (
       <div>
         <Cond state={state} path="SelectTutorial.NewTutorial">
-          <NewPage onNew={() => console.log('new!')} />
+          <NewPage onNew={() => send('TUTORIAL_START')} />
         </Cond>
         <Cond state={state} path="SelectTutorial.ContinueTutorial">
           <ContinuePage onContinue={() => console.log('continue!')} tutorials={[]} />

From 3cdce4d9aa088558b0956273c0de0229e1bf8292 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 16:02:09 -0700
Subject: [PATCH 11/12] setup state/data

---
 src/editor/commands/index.ts | 12 ++++++------
 src/state/index.ts           |  6 ++++--
 web-app/src/Routes.tsx       | 25 ++++++++++++++-----------
 web-app/src/utils/vscode.ts  |  9 +++++++++
 4 files changed, 33 insertions(+), 19 deletions(-)
 create mode 100644 web-app/src/utils/vscode.ts

diff --git a/src/editor/commands/index.ts b/src/editor/commands/index.ts
index 45d45a47..9ea0b92e 100644
--- a/src/editor/commands/index.ts
+++ b/src/editor/commands/index.ts
@@ -9,6 +9,7 @@ const COMMANDS = {
     NEW_OR_CONTINUE: 'coderoad.new_or_continue',
     OPEN_WEBVIEW: 'coderoad.open_webview',
     SEND_STATE: 'coderoad.send_state',
+    SEND_DATA: 'coderoad.send_data',
     RECEIVE_ACTION: 'coderoad.receive_action',
     OPEN_FILE: 'coderoad.open_file',
     RUN_TEST: 'coderoad.test_run',
@@ -70,15 +71,14 @@ export const createCommands = ({ context, machine, storage, git }: CreateCommand
         }
     },
     // send messages to webview
-    [COMMANDS.SEND_STATE]: (payload: any) => {
-        console.log(`SEND ${JSON.stringify(payload)}`)
-        // console.log(webview.currentPanel)
-        // if (!webview || !webview.currentPanel) {
-        //     throw new Error('No valid panel available')
-        // }
+    [COMMANDS.SEND_STATE]: (payload: { data: any, state: any }) => {
         webview.postMessage({ type: 'SET_STATE', payload })
     },
+    [COMMANDS.SEND_DATA]: (payload: { data: any }) => {
+        webview.postMessage({ type: 'SET_DATA', payload })
+    },
     [COMMANDS.RECEIVE_ACTION]: (action: string | CR.Action) => {
+        console.log('onReceiveAction', action)
         machine.onReceive(action)
     }
 })
\ No newline at end of file
diff --git a/src/state/index.ts b/src/state/index.ts
index d7f0e827..6a7cb3bb 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -18,11 +18,13 @@ class StateMachine {
         this.service = interpret(machine, this.machineOptions)
             // logging
             .onTransition(state => {
-                console.log('onTransition', state.changed)
+                console.log('onTransition', state)
                 if (state.changed) {
                     console.log('next state')
                     console.log(state.value)
-                    vscode.commands.executeCommand('coderoad.send_state', state.value)
+                    vscode.commands.executeCommand('coderoad.send_state', { state: state.value, data: state.context })
+                } else {
+                    vscode.commands.executeCommand('coderoad.send_data', { data: state.context })
                 }
             })
     }
diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index 5822eea5..6e0c9e88 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -1,5 +1,7 @@
 import * as React from 'react'
 import * as CR from 'typings'
+import { send } from './utils/vscode'
+
 import NewPage from './components/New'
 import ContinuePage from './components/Continue'
 import Cond from './components/Cond'
@@ -8,23 +10,21 @@ interface ReceivedEvent {
     data: CR.Action
 }
 
-declare var acquireVsCodeApi: any
-
-const vscode = acquireVsCodeApi()
-
-function send(event: string|CR.Action) {
-  return vscode.postMessage(event)
-}
-
-
 const Routes = () => {
     const [state, setState] = React.useState({ SelectTutorial: 'Initial' })
+    const [data, setData] = React.useState({})
+
+
     const handleEvent = (event: ReceivedEvent): void => {
         const message = event.data
-        console.log(`RECEIVED: ${JSON.stringify(message)}`)
+        console.log('RECEIVED')
+        console.log(message)
         // messages from core
         if (message.type === 'SET_STATE') {
-            setState(message.payload)
+            setState(message.payload.state)
+            setData(message.payload.data)
+        } else if (message.type === 'SET_DATA') {
+          setData(message.payload.data)
         }
     }
 
@@ -37,8 +37,11 @@ const Routes = () => {
         }
     })
 
+    // TODO: refactor cond to user <Router><Route> and accept first route as if/else if
     return (
       <div>
+        <h5>state: {JSON.stringify(state)}</h5>
+        <p>data:{JSON.stringify(data)}</p>
         <Cond state={state} path="SelectTutorial.NewTutorial">
           <NewPage onNew={() => send('TUTORIAL_START')} />
         </Cond>
diff --git a/web-app/src/utils/vscode.ts b/web-app/src/utils/vscode.ts
new file mode 100644
index 00000000..c53a5ebf
--- /dev/null
+++ b/web-app/src/utils/vscode.ts
@@ -0,0 +1,9 @@
+import { Action } from 'typings'
+
+declare var acquireVsCodeApi: any
+
+const vscode = acquireVsCodeApi()
+
+export function send(event: string | Action) {
+    return vscode.postMessage(event)
+}

From 99372b4e9ac156dc3836b1727eba2d8a1e5fbd36 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 9 Jun 2019 16:03:47 -0700
Subject: [PATCH 12/12] move components into containers

---
 web-app/src/Routes.tsx                                       | 5 +++--
 .../src/{components => containers}/Continue/ContinueItem.tsx | 0
 web-app/src/{components => containers}/Continue/index.tsx    | 0
 web-app/src/{components => containers}/New/index.tsx         | 0
 web-app/stories/Continue.stories.tsx                         | 2 +-
 web-app/stories/New.stories.tsx                              | 2 +-
 6 files changed, 5 insertions(+), 4 deletions(-)
 rename web-app/src/{components => containers}/Continue/ContinueItem.tsx (100%)
 rename web-app/src/{components => containers}/Continue/index.tsx (100%)
 rename web-app/src/{components => containers}/New/index.tsx (100%)

diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx
index 6e0c9e88..ca1d1512 100644
--- a/web-app/src/Routes.tsx
+++ b/web-app/src/Routes.tsx
@@ -2,9 +2,10 @@ import * as React from 'react'
 import * as CR from 'typings'
 import { send } from './utils/vscode'
 
-import NewPage from './components/New'
-import ContinuePage from './components/Continue'
 import Cond from './components/Cond'
+import NewPage from './containers/New'
+import ContinuePage from './containers/Continue'
+
 
 interface ReceivedEvent {
     data: CR.Action
diff --git a/web-app/src/components/Continue/ContinueItem.tsx b/web-app/src/containers/Continue/ContinueItem.tsx
similarity index 100%
rename from web-app/src/components/Continue/ContinueItem.tsx
rename to web-app/src/containers/Continue/ContinueItem.tsx
diff --git a/web-app/src/components/Continue/index.tsx b/web-app/src/containers/Continue/index.tsx
similarity index 100%
rename from web-app/src/components/Continue/index.tsx
rename to web-app/src/containers/Continue/index.tsx
diff --git a/web-app/src/components/New/index.tsx b/web-app/src/containers/New/index.tsx
similarity index 100%
rename from web-app/src/components/New/index.tsx
rename to web-app/src/containers/New/index.tsx
diff --git a/web-app/stories/Continue.stories.tsx b/web-app/stories/Continue.stories.tsx
index 6845b72c..1f4df9d0 100644
--- a/web-app/stories/Continue.stories.tsx
+++ b/web-app/stories/Continue.stories.tsx
@@ -3,7 +3,7 @@ import React from 'react'
 import { storiesOf } from '@storybook/react'
 import { action } from '@storybook/addon-actions'
 
-import Continue from '../src/components/Continue'
+import Continue from '../src/containers/Continue'
 import demo from './data/basic'
 
 storiesOf('Continue', module).add('Page', () => <Continue tutorials={[demo]} onContinue={action('onContinue')} />)
diff --git a/web-app/stories/New.stories.tsx b/web-app/stories/New.stories.tsx
index 643311d0..5ade6701 100644
--- a/web-app/stories/New.stories.tsx
+++ b/web-app/stories/New.stories.tsx
@@ -3,6 +3,6 @@ import React from 'react'
 import { storiesOf } from '@storybook/react'
 import { action } from '@storybook/addon-actions'
 
-import New from '../src/components/New'
+import New from '../src/containers/New'
 
 storiesOf('New', module).add('Page', () => <New onNew={action('onNew')} />)