diff --git a/.gitignore b/.gitignore
index 17bba57618..4036c5ece4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,6 @@ node_modules
/store.umd.js
/yarn-error.log
_actual*.*
-_*/
/site/cypress/screenshots/
/site/__sapper__/
diff --git a/site/.gitignore b/site/.gitignore
deleted file mode 100644
index ed567f230d..0000000000
--- a/site/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.DS_Store
-node_modules
-yarn-error.log
-/cypress/screenshots/
-/__sapper__/
diff --git a/site/src/routes/repl/_components/AppControls.html b/site/src/routes/repl/_components/AppControls.html
new file mode 100644
index 0000000000..40e5c9cc20
--- /dev/null
+++ b/site/src/routes/repl/_components/AppControls.html
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if $user}
+
+ {:else}
+
+
+
+ {/if}
+
+
+ {#if zen_mode}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
+ {#if $user}
+
+ {#if justForked}
+
+ {:else}
+
+ {/if}
+
+
+
+ {#if justSaved}
+
+ {:else}
+
+ {/if}
+
+ {/if}
+
+ {#if gist}
+
+
+
+ {/if}
+
+
+
+
diff --git a/site/src/routes/repl/_components/CodeMirror.html b/site/src/routes/repl/_components/CodeMirror.html
new file mode 100644
index 0000000000..fc6ce81dd2
--- /dev/null
+++ b/site/src/routes/repl/_components/CodeMirror.html
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+ {#if error}
+
+ {#if error.loc}
+
+ {#if error.filename}
+ {error.filename}
+ {/if}
+
+ ({error.loc.line}:{error.loc.column})
+
+ {/if}
+
+ {error.message}
+
+ {:elseif warningCount > 0}
+
+ Compiled, but with {warningCount} {warningCount === 1 ? 'warning' : 'warnings'} — check the console for details
+
+ {/if}
+
+
+{#if !CodeMirror}
+ loading editor...
+{/if}
+
+
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/ExampleSelector.html b/site/src/routes/repl/_components/ExampleSelector.html
new file mode 100644
index 0000000000..b41ff40231
--- /dev/null
+++ b/site/src/routes/repl/_components/ExampleSelector.html
@@ -0,0 +1,77 @@
+
+
+
+
+ Select an example
+
+ {#each examples as group}
+
+ {#each group.examples as example}
+ {example.title}
+ {/each}
+
+ {/each}
+
+
+
+ {name}
+
+
+
+
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/Input/ComponentSelector.html b/site/src/routes/repl/_components/Input/ComponentSelector.html
new file mode 100644
index 0000000000..ffd02a221e
--- /dev/null
+++ b/site/src/routes/repl/_components/Input/ComponentSelector.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+ {#each components as component}
+
+ {#if component.name == 'App'}
+
+ App.html
+
+ {:else}
+ {#if component.edit}
+ {component.name + (/\./.test(component.name) ? '' : '.html')}
+
+
+ {:else}
+
+ {component.name}.{component.type}
+
+
+
+
+
+
+ {/if}
+ {/if}
+
+ {/each}
+
+
+
+
+
+
diff --git a/site/src/routes/repl/_components/Input/ModuleEditor.html b/site/src/routes/repl/_components/Input/ModuleEditor.html
new file mode 100644
index 0000000000..7dc74bdf05
--- /dev/null
+++ b/site/src/routes/repl/_components/Input/ModuleEditor.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ {#if selectedComponent}
+
+ {/if}
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/Input/index.html b/site/src/routes/repl/_components/Input/index.html
new file mode 100644
index 0000000000..c97f2e26b2
--- /dev/null
+++ b/site/src/routes/repl/_components/Input/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/Output/Viewer.html b/site/src/routes/repl/_components/Output/Viewer.html
new file mode 100644
index 0000000000..b160277952
--- /dev/null
+++ b/site/src/routes/repl/_components/Output/Viewer.html
@@ -0,0 +1,386 @@
+
+
+
+
+
+
+
+
+
+ {#if error}
+
+ {#if error.loc}
+
+ {#if error.filename}
+ {error.filename}
+ {/if}
+
+ ({error.loc.line}:{error.loc.column})
+
+ {/if}
+
+ {error.message}
+
+ {:elseif pending}
+
+ Click to run
+
+ {:elseif pendingImports}
+
loading {pendingImports} {pendingImports === 1 ? 'dependency' : 'dependencies'} from
+ https://bundle.run
+ {/if}
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/Output/index.html b/site/src/routes/repl/_components/Output/index.html
new file mode 100644
index 0000000000..2f0775891d
--- /dev/null
+++ b/site/src/routes/repl/_components/Output/index.html
@@ -0,0 +1,101 @@
+
+
+
+
+
+ Result
+
+ Compiled code
+
+
+
+{#if view === 'result'}
+
+
+ {#if bundle}
+
+ {:else}
+
loading Svelte compiler...
+ {/if}
+
+
+
+
+{:else}
+
+
+
+
+
+
+ compiler options go here
+
+
+{/if}
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/Repl.html b/site/src/routes/repl/_components/Repl.html
new file mode 100644
index 0000000000..449f5f1e2e
--- /dev/null
+++ b/site/src/routes/repl/_components/Repl.html
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/SplitPane.html b/site/src/routes/repl/_components/SplitPane.html
new file mode 100644
index 0000000000..8c1e618131
--- /dev/null
+++ b/site/src/routes/repl/_components/SplitPane.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+{#if dragging}
+
+{/if}
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/UserMenu.html b/site/src/routes/repl/_components/UserMenu.html
new file mode 100644
index 0000000000..c85264fc0d
--- /dev/null
+++ b/site/src/routes/repl/_components/UserMenu.html
@@ -0,0 +1,66 @@
+
+
+
+
{name}
+
+
+ {#if showMenu}
+
+ {/if}
+
+
+
\ No newline at end of file
diff --git a/site/src/routes/repl/_components/_codemirror.js b/site/src/routes/repl/_components/_codemirror.js
new file mode 100644
index 0000000000..1619ab760c
--- /dev/null
+++ b/site/src/routes/repl/_components/_codemirror.js
@@ -0,0 +1,10 @@
+const CodeMirror = require('codemirror');
+require('./codemirror.css');
+require('codemirror/mode/javascript/javascript.js');
+require('codemirror/mode/shell/shell.js');
+require('codemirror/mode/handlebars/handlebars.js');
+require('codemirror/mode/htmlmixed/htmlmixed.js');
+require('codemirror/mode/xml/xml.js');
+require('codemirror/mode/css/css.js');
+
+module.exports = CodeMirror;
diff --git a/site/src/routes/repl/_components/codemirror.css b/site/src/routes/repl/_components/codemirror.css
new file mode 100644
index 0000000000..299c1b444f
--- /dev/null
+++ b/site/src/routes/repl/_components/codemirror.css
@@ -0,0 +1,350 @@
+/* BASICS */
+
+.CodeMirror {
+ /* copied colors over from prism */
+ --background: var(--back-light);
+ --base: #9b978b;
+ --comment: #afbfcf;
+ --keyword: #5ba3d3;
+ --function: #db794b;
+ --string: #b69b61;
+ --number: #86af75;
+ --tags: var(--function);
+ --important: var(--string);
+
+ /* Set height, width, borders, and global font properties here */
+ /* see prism.css */
+ height: 300px;
+ direction: ltr;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: var(--comment);
+ white-space: nowrap;
+ opacity: .6;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+.cm-fat-cursor-mark {
+ background-color: rgba(20, 255, 20, .5);
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+}
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: -20px;
+ overflow: hidden;
+}
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+.cm-s-default .cm-header {color: blue}
+.cm-s-default .cm-quote {color: #090}
+.cm-negative {color: #d44}
+.cm-positive {color: #292}
+.cm-header, .cm-strong {font-weight: bold}
+.cm-em {font-style: italic}
+.cm-link {text-decoration: underline}
+.cm-strikethrough {text-decoration: line-through}
+
+.cm-s-default .cm-atom,
+.cm-s-default .cm-def,
+.cm-s-default .cm-property,
+.cm-s-default .cm-variable-2,
+.cm-s-default .cm-variable-3,
+.cm-s-default .cm-punctuation {color: var(--base)}
+.cm-s-default .cm-hr,
+.cm-s-default .cm-comment {color: var(--comment)}
+.cm-s-default .cm-attribute,
+.cm-s-default .cm-keyword {color: var(--keyword)}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-bracket,
+.cm-s-default .cm-tag {color: var(--tags)}
+.cm-s-default .cm-number {color: var(--number)}
+.cm-s-default .cm-string {color: var(--string)}
+
+.cm-s-default .cm-string-2 {color: #f50}
+.cm-s-default .cm-type {color: #085}
+.cm-s-default .cm-meta {color: #555}
+.cm-s-default .cm-qualifier {color: #555}
+.cm-s-default .cm-builtin {color: #30a}
+.cm-s-default .cm-link {color: var(--flash)}
+.cm-s-default .cm-error {color: #ff008c}
+.cm-invalidchar {color: #ff008c}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ padding: .1px; /* Force widget margins to stay inside of the container */
+}
+
+.CodeMirror-rtl pre { direction: rtl; }
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background-color: #ffa;
+ background-color: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/site/src/routes/repl/_components/component-selectors I cant find.css b/site/src/routes/repl/_components/component-selectors I cant find.css
new file mode 100644
index 0000000000..3e01d2a2bf
--- /dev/null
+++ b/site/src/routes/repl/_components/component-selectors I cant find.css
@@ -0,0 +1,62 @@
+.module-name {
+ position: relative;
+ display: block;
+ background-color: #ff00d4;
+}
+
+.panel-header {
+ /* padding: 0 40px .5em 0; */
+ background-color: #ff00d4;
+}
+
+.dropdown {
+ position: relative;
+ display: block;
+ float: left;
+ padding: 0 2em 0 0;
+ background-color: #ff00d4;
+}
+
+.dropdown::after {
+ content: '▼';
+ position: absolute;
+ right: 1rem;
+ top: .55rem;
+ font-size: .8em;
+ color: #999;
+ pointer-events: none;
+ background-color: #ff00d4;
+}
+
+.input-wrapper {
+ position: relative;
+ display: block;
+ float: left;
+ line-height: 1;
+ /* margin: 0 .3em 0 0; */
+ background-color: #ff00d4;
+}
+
+.file-tabs li.active {
+ /* background-color: var(--back-light); */
+ background-color: #ff00d4;
+}
+
+.widther {
+ display: block;
+ font-family: inherit;
+ font-size: inherit;
+ border: 1px solid #eee;
+ padding: calc(.5em - 1px) .25em;
+ line-height: 1;
+ background-color: #ff00d4;
+}
+
+.file-extension {
+ display: inline-block;
+ padding: calc(.5em - 1px) 0;
+ color: var(--prime);
+ left: -.2em;
+ pointer-events: none;
+ background-color: #ff00d4;
+}
diff --git a/site/src/routes/repl/_components/events.js b/site/src/routes/repl/_components/events.js
new file mode 100644
index 0000000000..c1d2e84199
--- /dev/null
+++ b/site/src/routes/repl/_components/events.js
@@ -0,0 +1,19 @@
+export function keyEvent(code) {
+ return function (node, callback) {
+ node.addEventListener('keydown', handleKeydown);
+
+ function handleKeydown(event) {
+ if (event.keyCode === code) {
+ callback.call(this, event);
+ }
+ }
+
+ return {
+ destroy() {
+ node.removeEventListener('keydown', handleKeydown);
+ }
+ };
+ }
+}
+
+export const enter = keyEvent(13);
\ No newline at end of file
diff --git a/site/src/routes/repl/_utils/downloadBlob.js b/site/src/routes/repl/_utils/downloadBlob.js
new file mode 100644
index 0000000000..d90ed40530
--- /dev/null
+++ b/site/src/routes/repl/_utils/downloadBlob.js
@@ -0,0 +1,11 @@
+export default (blob, filename) => {
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+ URL.revokeObjectURL(url);
+ link.remove();
+};
diff --git a/site/src/routes/repl/_utils/getLocationFromStack.js b/site/src/routes/repl/_utils/getLocationFromStack.js
new file mode 100644
index 0000000000..3d10fdee98
--- /dev/null
+++ b/site/src/routes/repl/_utils/getLocationFromStack.js
@@ -0,0 +1,31 @@
+import { decode } from 'sourcemap-codec';
+
+export default function getLocationFromStack(stack, map) {
+ if (!stack) return;
+ const last = stack.split('\n')[1];
+ const match = /:(\d+):(\d+)\)$/.exec(last);
+
+ if (!match) return null;
+
+ const line = +match[1];
+ const column = +match[2];
+
+ return trace({ line, column }, map);
+}
+
+function trace(loc, map) {
+ const mappings = decode(map.mappings);
+ const segments = mappings[loc.line - 1];
+
+ for (let i = 0; i < segments.length; i += 1) {
+ const segment = segments[i];
+ if (segment[0] === loc.column) {
+ const [, sourceIndex, line, column] = segment;
+ const source = map.sources[sourceIndex].slice(2);
+
+ return { source, line: line + 1, column };
+ }
+ }
+
+ return null;
+}