Repository: electron/fiddle
Pull request #675 (2021-04-29 11:48:00)

feat: allow setting environment variables
Adds a new settings preference similar to that for command line switches that allows for custom environment variables to pass to the executable. 

Example use case - debugging drag regions. 

Screenshot:

<img width="1312" alt="Screen Shot 2021-04-29 at 1 47 42 PM" src="https://user-images.githubusercontent.com/2036040/116546084-7c3eb080-a8f1-11eb-9407-eda2c697d9c8.png">


coveralls: 
[![Coverage Status](https://coveralls.io/builds/39222903/badge)](https://coveralls.io/builds/39222903)

Coverage decreased (-0.05%) to 95.931% when pulling **a91016a8d26097b669e37507f5931b3ce37ca004 on enable-env-vars** into **72714dc8d9dc78aacc67ed1dec45153752207dc2 on master**.


Patch:
diff --git a/src/ambient.d.ts b/src/ambient.d.ts
index bdf27546..ed3db63a 100644
--- a/src/ambient.d.ts
+++ b/src/ambient.d.ts
@@ -2,7 +2,7 @@ import * as MonacoType from 'monaco-editor';
 
 // Type-only-import
 import { App as AppType } from './renderer/app';
-import { MosaicId } from './interfaces';
+import { EditorId } from './interfaces';
 
 declare global {
   interface Window {
@@ -11,7 +11,7 @@ declare global {
       app: AppType;
       contentChangeListeners: Array<any>;
       editors: Record<
-        MosaicId,
+        EditorId,
         MonacoType.editor.IStandaloneCodeEditor | null | undefined
       >;
     };
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 0e3ae669..ea093eaf 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -129,13 +129,6 @@ export type CustomEditorId = Exclude<
 
 export type EditorId = DefaultEditorId | CustomEditorId;
 
-// Panels that can show up as a mosaic
-export const enum PanelId {
-  'docsDemo' = 'docsDemo',
-}
-
-export type MosaicId = DefaultEditorId | CustomEditorId | PanelId;
-
 export const DEFAULT_EDITORS = [
   DefaultEditorId.main,
   DefaultEditorId.renderer,
@@ -150,17 +143,8 @@ export const MAIN_JS = DefaultEditorId.main;
 
 export const PACKAGE_NAME = 'package.json';
 
-export const ALL_PANELS = [PanelId.docsDemo];
-
-export const ALL_MOSAICS = [...DEFAULT_EDITORS, ...ALL_PANELS] as MosaicId[];
-
 export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
 
-export const enum DocsDemoPage {
-  DEFAULT = 'DEFAULT',
-  DEMO_APP = 'DEMO_APP',
-}
-
 export const enum BlockableAccelerator {
   save = 'save',
   saveAs = 'saveAs',
diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index 6e82b2e0..7b6566c1 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -19,7 +19,6 @@ import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
 import { updateEditorLayout } from '../utils/editor-layout';
 import { getEditorValue } from '../utils/editor-value';
 import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
-import { isEditorBackup } from '../utils/type-checks';
 import { compareEditors, getEmptyContent } from '../utils/editor-utils';
 import { FileManager } from './file-manager';
 import { RemoteLoader } from './remote-loader';
@@ -121,10 +120,9 @@ export class App {
     const allEditors = [...this.state.customMosaics, ...DEFAULT_EDITORS];
     for (const name of allEditors) {
       const editor = fiddle.editors[name];
-      const backup = this.state.closedPanels[name];
-
       if (typeof values[name] !== 'undefined') {
-        if (isEditorBackup(backup)) {
+        const backup = this.state.closedPanels[name];
+        if (backup) {
           // The editor does not exist, attempt to set it on the backup.
           // If there's a model, we'll do it on the model. Else, we'll
           // set the value.
diff --git a/src/renderer/components/commands-editors.tsx b/src/renderer/components/commands-editors.tsx
index b85f4273..d89c4bdc 100644
--- a/src/renderer/components/commands-editors.tsx
+++ b/src/renderer/components/commands-editors.tsx
@@ -14,8 +14,7 @@ import {
   DEFAULT_EDITORS,
   CustomEditorId,
   GenericDialogType,
-  MosaicId,
-  PanelId,
+  EditorId,
   DefaultEditorId,
 } from '../../interfaces';
 import { getVisibleMosaics } from '../../utils/editors-mosaic-arrangement';
@@ -56,27 +55,10 @@ export class EditorDropdown extends React.Component<
         <Popover content={this.renderMenu()} position={Position.BOTTOM}>
           <Button icon="applications" text="Editors" />
         </Popover>
-        {this.renderDocsDemos()}
       </>
     );
   }
 
-  public renderDocsDemos() {
-    if (!process.env.FIDDLE_DOCS_DEMOS) {
-      return null;
-    }
-
-    return (
-      <Button
-        icon="help"
-        text="Docs & Demos"
-        id={PanelId.docsDemo}
-        onClick={this.onItemClick}
-        active={!this.props.appState.closedPanels.docsDemo}
-      />
-    );
-  }
-
   public renderMenu() {
     return <Menu>{...this.renderMenuItems()}</Menu>;
   }
@@ -217,7 +199,7 @@ export class EditorDropdown extends React.Component<
     const { appState } = this.props;
 
     console.log(`EditorDropdown: Removing custom editor ${id}`);
-    appState.removeCustomMosaic(id as MosaicId);
+    appState.removeCustomMosaic(id as EditorId);
   }
 
   public onItemClick(event: React.MouseEvent) {
@@ -225,12 +207,12 @@ export class EditorDropdown extends React.Component<
     const { appState } = this.props;
     const visibleMosaics = getVisibleMosaics(appState.mosaicArrangement);
 
-    if (visibleMosaics.includes(id as MosaicId)) {
+    if (visibleMosaics.includes(id as EditorId)) {
       console.log(`EditorDropdown: Closing ${id}`);
-      appState.hideAndBackupMosaic(id as MosaicId);
+      appState.hideAndBackupMosaic(id as EditorId);
     } else {
       console.log(`EditorDropdown: Opening ${id}`);
-      appState.showMosaic(id as MosaicId);
+      appState.showMosaic(id as EditorId);
     }
   }
 }
diff --git a/src/renderer/components/editors-toolbar-button.tsx b/src/renderer/components/editors-toolbar-button.tsx
index d0f9c415..2461a972 100644
--- a/src/renderer/components/editors-toolbar-button.tsx
+++ b/src/renderer/components/editors-toolbar-button.tsx
@@ -7,12 +7,12 @@ import {
   MosaicWindowContext,
 } from 'react-mosaic-component';
 
-import { DocsDemoPage, MosaicId } from '../../interfaces';
+import { EditorId } from '../../interfaces';
 import { AppState } from '../state';
 
 interface ToolbarButtonProps {
   appState: AppState;
-  id: MosaicId;
+  id: EditorId;
 }
 
 abstract class ToolbarButton extends React.PureComponent<ToolbarButtonProps> {
@@ -63,22 +63,3 @@ export class RemoveButton extends ToolbarButton {
     return <Button icon="cross" className="bp3-small" onClick={onClick} />;
   }
 }
-
-export class DocsDemoGoHomeButton extends ToolbarButton {
-  /**
-   * Create a button that can remove this panel
-   */
-  public createButton(_mosaicActions: MosaicRootActions<any>) {
-    const onClick = () =>
-      (this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT);
-
-    return (
-      <Button
-        icon="home"
-        className="bp3-small"
-        onClick={onClick}
-        text="Overview"
-      />
-    );
-  }
-}
diff --git a/src/renderer/components/editors.tsx b/src/renderer/components/editors.tsx
index ec640119..f2a6422c 100644
--- a/src/renderer/components/editors.tsx
+++ b/src/renderer/components/editors.tsx
@@ -10,30 +10,20 @@ import {
   MosaicWindowProps,
 } from 'react-mosaic-component';
 
-import {
-  EditorId,
-  MosaicId,
-  PanelId,
-  SetFiddleOptions,
-} from '../../interfaces';
+import { EditorId, SetFiddleOptions } from '../../interfaces';
 import { IpcEvents } from '../../ipc-events';
 import { updateEditorLayout } from '../../utils/editor-layout';
 import { getFocusedEditor } from '../../utils/focused-editor';
 import { getAtPath, setAtPath } from '../../utils/js-path';
 import { toggleMonaco } from '../../utils/toggle-monaco';
 import { getEditorTitle } from '../../utils/editor-utils';
-import { isEditorId } from '../../utils/type-checks';
 import { getTemplate, getTestTemplate } from '../content';
 import { ipcRendererManager } from '../ipc';
 import { AppState } from '../state';
 import { activateTheme } from '../themes';
 import { Editor } from './editor';
 import { renderNonIdealState } from './editors-non-ideal-state';
-import {
-  DocsDemoGoHomeButton,
-  MaximizeButton,
-  RemoveButton,
-} from './editors-toolbar-button';
+import { MaximizeButton, RemoveButton } from './editors-toolbar-button';
 
 const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
   minimap: {
@@ -194,19 +184,15 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
   /**
    * Renders the little tool bar on top of each panel
    *
-   * @param {MosaicWindowProps<MosaicId>} { title }
-   * @param {MosaicId} id
+   * @param {MosaicWindowProps<EditorId>} { title }
+   * @param {EditorId} id
    * @returns {JSX.Element}
    */
   public renderToolbar(
-    { title }: MosaicWindowProps<MosaicId>,
-    id: MosaicId,
+    { title }: MosaicWindowProps<EditorId>,
+    id: EditorId,
   ): JSX.Element {
     const { appState } = this.props;
-    const docsDemoGoHomeMaybe =
-      id === PanelId.docsDemo ? (
-        <DocsDemoGoHomeButton id={id} appState={appState} />
-      ) : null;
 
     // only show toolbar controls if we have more than 1 visible editor
     // Mosaic arrangement is type string if 1 editor, object otherwise
@@ -227,10 +213,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
         {/* Middle */}
         <div />
         {/* Right */}
-        <div className="mosaic-controls">
-          {docsDemoGoHomeMaybe}
-          {toolbarControlsMaybe}
-        </div>
+        <div className="mosaic-controls">{toolbarControlsMaybe}</div>
       </div>
     );
   }
@@ -242,10 +225,8 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
    * @param {string} path
    * @returns {JSX.Element | null}
    */
-  public renderTile(id: MosaicId, path: Array<MosaicBranch>): JSX.Element {
-    const { appState } = this.props;
-    const content =
-      isEditorId(id, appState.customMosaics) && this.renderEditor(id);
+  public renderTile(id: EditorId, path: Array<MosaicBranch>): JSX.Element {
+    const content = this.renderEditor(id);
     const title = getEditorTitle(id as EditorId);
 
     return (
@@ -253,7 +234,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
         className={id}
         path={path}
         title={title}
-        renderToolbar={(props: MosaicWindowProps<MosaicId>) =>
+        renderToolbar={(props: MosaicWindowProps<EditorId>) =>
           this.renderToolbar(props, id)
         }
       >
@@ -291,7 +272,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
     if (!monaco) return null;
 
     return (
-      <Mosaic<EditorId | PanelId>
+      <Mosaic<EditorId>
         className={`focused__${this.state.focused}`}
         onChange={this.onChange}
         value={appState.mosaicArrangement}
diff --git a/src/renderer/components/output-editors-wrapper.tsx b/src/renderer/components/output-editors-wrapper.tsx
index 8f740fba..c73aab44 100644
--- a/src/renderer/components/output-editors-wrapper.tsx
+++ b/src/renderer/components/output-editors-wrapper.tsx
@@ -10,10 +10,10 @@ interface WrapperProps {
 }
 
 interface WrapperState {
-  mosaicArrangement: MosaicNode<WrapperMosaicId>;
+  mosaicArrangement: MosaicNode<WrapperEditorId>;
 }
 
-export type WrapperMosaicId = 'output' | 'editors';
+export type WrapperEditorId = 'output' | 'editors';
 
 export class OutputEditorsWrapper extends React.Component<
   WrapperProps,
@@ -39,7 +39,7 @@ export class OutputEditorsWrapper extends React.Component<
   public render() {
     return (
       <>
-        <Mosaic<WrapperMosaicId>
+        <Mosaic<WrapperEditorId>
           renderTile={(id: string) => this.MOSAIC_ELEMENTS[id]}
           resize={{ minimumPaneSizePercentage: 0 }}
           value={this.state.mosaicArrangement}
@@ -49,7 +49,7 @@ export class OutputEditorsWrapper extends React.Component<
     );
   }
 
-  private onChange = (rootNode: MosaicParent<WrapperMosaicId>) => {
+  private onChange = (rootNode: MosaicParent<WrapperEditorId>) => {
     const isConsoleShowing = rootNode.splitPercentage !== 0;
 
     if (isConsoleShowing !== this.props.appState.isConsoleShowing) {
diff --git a/src/renderer/components/output.tsx b/src/renderer/components/output.tsx
index 77369fc7..2391bad7 100644
--- a/src/renderer/components/output.tsx
+++ b/src/renderer/components/output.tsx
@@ -11,7 +11,7 @@ import { isWebUri } from 'valid-url';
 
 import { OutputEntry } from '../../interfaces';
 import { AppState } from '../state';
-import { WrapperMosaicId } from './output-editors-wrapper';
+import { WrapperEditorId } from './output-editors-wrapper';
 
 interface CommandsProps {
   appState: AppState;
@@ -29,7 +29,7 @@ interface CommandsProps {
 @observer
 export class Output extends React.Component<CommandsProps> {
   public static contextType = MosaicContext;
-  public context: MosaicContext<WrapperMosaicId>;
+  public context: MosaicContext<WrapperEditorId>;
   private outputRef = React.createRef<HTMLDivElement>();
 
   constructor(props: CommandsProps) {
@@ -50,9 +50,9 @@ export class Output extends React.Component<CommandsProps> {
        * @returns Whether that node is a MosaicParent or not
        */
       const isParentNode = (
-        node: MosaicNode<WrapperMosaicId> | null,
-      ): node is MosaicParent<WrapperMosaicId> => {
-        return (node as MosaicParent<WrapperMosaicId>)?.direction !== undefined;
+        node: MosaicNode<WrapperEditorId> | null,
+      ): node is MosaicParent<WrapperEditorId> => {
+        return (node as MosaicParent<WrapperEditorId>)?.direction !== undefined;
       };
 
       const { isConsoleShowing } = this.props.appState;
diff --git a/src/renderer/components/settings-execution.tsx b/src/renderer/components/settings-execution.tsx
index f805cd4f..c3bef3f4 100644
--- a/src/renderer/components/settings-execution.tsx
+++ b/src/renderer/components/settings-execution.tsx
@@ -32,6 +32,9 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
       this,
     );
     this.handleExecutionFlagChange = this.handleExecutionFlagChange.bind(this);
+    this.handleEnvironmentVariableChange = this.handleEnvironmentVariableChange.bind(
+      this,
+    );
   }
 
   /**
@@ -56,7 +59,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
   }
 
   /**
-   * Handles a change in the execution flags run with the Electron executable
+   * Handles a change in the execution flags run with the Electron executable.
    *
    * @param {React.ChangeEvent<HTMLInputElement>} event
    */
@@ -66,11 +69,26 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
     this.props.appState.executionFlags = flags;
   }
 
+  /**
+   * Handles a change in the environment variables run with the Electron executable.
+   *
+   * @param {React.ChangeEvent<HTMLInputElement>} event
+   */
+  public handleEnvironmentVariableChange(
+    event: React.FormEvent<HTMLInputElement>,
+  ) {
+    const { value } = event.currentTarget;
+
+    const variables = value.split('|');
+    this.props.appState.environmentVariables = variables;
+  }
+
   public render() {
     const {
       isKeepingUserDataDirs,
       isEnablingElectronLogging,
-      executionFlags = [],
+      executionFlags,
+      environmentVariables,
     } = this.props.appState;
 
     const deleteUserDirLabel = `
@@ -81,7 +99,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
     `.trim();
     const electronLoggingLabel = `
       There are some flags that Electron uses to log extra information both internally
-      and through Chromium.  Enable this option to make Fiddle produce those logs.`.trim();
+      and through Chromium. Enable this option to make Fiddle produce those logs.`.trim();
 
     return (
       <div>
@@ -103,6 +121,17 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
         <br />
         <Callout>
           <FormGroup label={electronLoggingLabel}>
+            <p>
+              Enabling advanced Electron logging will set the{' '}
+              <code>ELECTRON_ENABLE_LOGGING</code>,
+              <code>ELECTRON_DEBUG_NOTIFICATION</code>, and{' '}
+              <code>ELECTRON_ENABLE_STACK_DUMPING</code> environment variables
+              to true. See{' '}
+              <a href="https://www.electronjs.org/docs/api/environnment-variables">
+                documentation
+              </a>{' '}
+              for more information about what they do.
+            </p>
             <Checkbox
               checked={isEnablingElectronLogging}
               label="Enable advanced Electron logging."
@@ -118,7 +147,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
               <a href="https://www.electronjs.org/docs/api/command-line-switches">
                 user-provided flags
               </a>
-              , such as &apos;--js-flags=--expose-gc&apos;. Those can be added
+              , such as <code>--js-flags=--expose-gc</code>. Those can be added
               here as bar-separated (|) flags to run when you start your
               Fiddles.
             </p>
@@ -131,6 +160,29 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
           </FormGroup>
         </Callout>
         <br />
+        <Callout>
+          <FormGroup>
+            <p>
+              Electron allows starting the executable with{' '}
+              <a href="https://www.electronjs.org/docs/api/environment-variables">
+                user-provided environment variables
+              </a>
+              , such as{' '}
+              <code>
+                NODE_OPTIONS=&quot;--no-warnings --max-old-space-size=2048&quot;
+              </code>
+              . Those can be added here as bar-separated (|) variables to run
+              when you start your Fiddles.
+            </p>
+            <br />
+            <InputGroup
+              placeholder='NODE_OPTIONS="--no-warnings --max-old-space-size=2048"'
+              value={environmentVariables.join('|')}
+              onChange={this.handleEnvironmentVariableChange}
+            />
+          </FormGroup>
+        </Callout>
+        <br />
         <Callout>
           <FormGroup>
             <span style={{ marginRight: 4 }}>
diff --git a/src/renderer/constants.ts b/src/renderer/constants.ts
index a3e17d9b..f668a175 100644
--- a/src/renderer/constants.ts
+++ b/src/renderer/constants.ts
@@ -1,7 +1,7 @@
 import * as path from 'path';
 import { MosaicNode } from 'react-mosaic-component';
 
-import { DefaultEditorId, EditorId, MosaicId } from '../interfaces';
+import { DefaultEditorId, EditorId } from '../interfaces';
 import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
 
 export const USER_DATA_PATH = window.ElectronFiddle.appPaths.userData;
@@ -18,7 +18,7 @@ export const SORTED_EDITORS = Object.freeze([
   DefaultEditorId.css,
 ]);
 
-export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
+export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<EditorId> = {
   direction: 'row',
   first: {
     direction: 'column',
@@ -35,7 +35,6 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
 export const DEFAULT_CLOSED_PANELS: Partial<
   Record<EditorId, EditorBackup | true>
 > = {
-  docsDemo: true,
   'styles.css': getEditorBackup(DefaultEditorId.css),
 };
 
diff --git a/src/renderer/runner.ts b/src/renderer/runner.ts
index fa2cd153..ac6b7018 100644
--- a/src/renderer/runner.ts
+++ b/src/renderer/runner.ts
@@ -324,13 +324,20 @@ export class Runner {
    * @memberof Runner
    */
   public async execute(dir: string): Promise<RunResult> {
-    const { currentElectronVersion, pushOutput } = this.appState;
+    const {
+      currentElectronVersion,
+      isEnablingElectronLogging,
+      pushOutput,
+      executionFlags,
+      environmentVariables,
+    } = this.appState;
+
     const { version, localPath } = currentElectronVersion;
     const binaryPath = getElectronBinaryPath(version, localPath);
     console.log(`Runner: Binary ${binaryPath} ready, launching`);
 
     const env = { ...process.env };
-    if (this.appState.isEnablingElectronLogging) {
+    if (isEnablingElectronLogging) {
       env.ELECTRON_ENABLE_LOGGING = 'true';
       env.ELECTRON_DEBUG_NOTIFICATIONS = 'true';
       env.ELECTRON_ENABLE_STACK_DUMPING = 'true';
@@ -340,8 +347,14 @@ export class Runner {
       delete env.ELECTRON_ENABLE_STACK_DUMPING;
     }
 
+    // Add user-specified environment variables if any have been set.
+    for (const v of environmentVariables) {
+      const [key, value] = v.split('=');
+      env[key] = value;
+    }
+
     // Add user-specified cli flags if any have been set.
-    const options = [dir, '--inspect'].concat(this.appState.executionFlags);
+    const options = [dir, '--inspect'].concat(executionFlags);
 
     return new Promise((resolve, _reject) => {
       this.child = spawn(binaryPath, options, { cwd: dir, env });
diff --git a/src/renderer/state.ts b/src/renderer/state.ts
index d6826258..d3fc06c0 100644
--- a/src/renderer/state.ts
+++ b/src/renderer/state.ts
@@ -4,16 +4,15 @@ import { action, autorun, computed, observable, when } from 'mobx';
 import { MosaicNode } from 'react-mosaic-component';
 
 import {
-  DEFAULT_EDITORS,
-  ALL_MOSAICS,
   BlockableAccelerator,
-  DocsDemoPage,
+  CustomEditorId,
+  DEFAULT_EDITORS,
+  EditorId,
   ElectronReleaseChannel,
   GenericDialogOptions,
   GenericDialogType,
   GistActionState,
   MAIN_JS,
-  MosaicId,
   OutputEntry,
   OutputOptions,
   RunnableVersion,
@@ -21,8 +20,6 @@ import {
   Version,
   VersionSource,
   VersionState,
-  CustomEditorId,
-  EditorId,
 } from '../interfaces';
 import { IpcEvents } from '../ipc-events';
 import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
@@ -32,7 +29,6 @@ import {
 } from '../utils/editors-mosaic-arrangement';
 import { getName } from '../utils/get-name';
 import { normalizeVersion } from '../utils/normalize-version';
-import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
 import { removeBinary, setupBinary } from './binary';
 import { Bisector } from './bisect';
 import { DEFAULT_CLOSED_PANELS, DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
@@ -109,6 +105,10 @@ export class AppState {
     (this.retrieve('executionFlags') as Array<string>) === null
       ? []
       : (this.retrieve('executionFlags') as Array<string>);
+  @observable public environmentVariables: Array<string> =
+    (this.retrieve('environmentVariables') as Array<string>) === null
+      ? []
+      : (this.retrieve('environmentVariables') as Array<string>);
   @observable public packageManager: IPackageManager =
     (localStorage.getItem('packageManager') as IPackageManager) || 'npm';
   @observable public acceleratorsToBlock: Array<BlockableAccelerator> =
@@ -131,9 +131,8 @@ export class AppState {
   @observable public genericDialogLastResult: boolean | null = null;
   @observable public genericDialogLastInput: string | null = null;
   @observable
-  public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
+  public mosaicArrangement: MosaicNode<EditorId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
   @observable public templateName: string | undefined;
-  @observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
   @observable public localTypeWatcher: fs.FSWatcher | undefined;
   @observable public Bisector: Bisector | undefined;
 
@@ -157,8 +156,9 @@ export class AppState {
   @observable public isTourShowing = !localStorage.getItem('hasShownTour');
 
   // -- Editor Values stored when we close the editor ------------------
-  @observable public closedPanels: Partial<
-    Record<MosaicId, EditorBackup | true>
+  @observable public closedPanels: Record<
+    EditorId,
+    EditorBackup
   > = DEFAULT_CLOSED_PANELS;
 
   private outputBuffer = '';
@@ -281,7 +281,7 @@ export class AppState {
         );
         await waitForEditorsToMount(ids);
         for (const editor of Object.values(window.ElectronFiddle.editors)) {
-          const disposable = editor!.onDidChangeModelContent(() => {
+          const disposable = (editor as any).onDidChangeModelContent(() => {
             this.isUnsaved = true;
             disposable.dispose();
           });
@@ -678,38 +678,20 @@ export class AppState {
     id: EditorId,
   ): EditorBackup | null {
     const value = this.closedPanels[id];
-
-    if (isEditorBackup(value)) {
-      delete this.closedPanels[id];
-      return value;
-    }
-
-    return null;
+    delete this.closedPanels[id];
+    return value;
   }
 
-  @action public async setVisibleMosaics(visible: Array<MosaicId>) {
+  @action public async setVisibleMosaics(visible: Array<EditorId>) {
     const { editors } = window.ElectronFiddle;
     const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
 
-    for (const id of ALL_MOSAICS) {
+    for (const id of DEFAULT_EDITORS) {
       if (!visible.includes(id) && currentlyVisible.includes(id)) {
-        this.closedPanels[id] = isEditorId(id, this.customMosaics)
-          ? getEditorBackup(id)
-          : true;
-
+        this.closedPanels[id] = getEditorBackup(id);
         // if we have backup, remove active editor
         delete editors[id];
       }
-
-      // Remove the backup for panels now. Editors will remove their
-      // backup once the data has been loaded.
-      if (
-        isPanelId(id) &&
-        visible.includes(id) &&
-        !currentlyVisible.includes(id)
-      ) {
-        delete this.closedPanels[id];
-      }
     }
 
     const updatedArrangement = createMosaicArrangement(visible);
@@ -729,21 +711,21 @@ export class AppState {
   }
 
   /**
-   * Hides the panel for a given MosaicId.
+   * Hides the panel for a given EditorId.
    *
-   * @param {MosaicId} id
+   * @param {EditorId} id
    */
-  @action public hideAndBackupMosaic(id: MosaicId) {
+  @action public hideAndBackupMosaic(id: EditorId) {
     const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
     this.setVisibleMosaics(currentlyVisible.filter((v) => v !== id));
   }
 
   /**
-   * Removes the panel for a given custom MosaicId.
+   * Removes the panel for a given custom EditorId.
    *
-   * @param {MosaicId} id
+   * @param {EditorId} id
    */
-  @action public removeCustomMosaic(id: MosaicId) {
+  @action public removeCustomMosaic(id: EditorId) {
     this.hideAndBackupMosaic(id);
     delete window.ElectronFiddle.editors[id];
     this.customMosaics = this.customMosaics.filter((mosaic) => mosaic !== id);
@@ -752,9 +734,9 @@ export class AppState {
   /**
    * Shows the editor value for a given editor.
    *
-   * @param {MosaicId} id
+   * @param {EditorId} id
    */
-  @action public showMosaic(id: MosaicId) {
+  @action public showMosaic(id: EditorId) {
     const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
     this.setVisibleMosaics([...currentlyVisible, id]);
   }
diff --git a/src/utils/editor-mounted.ts b/src/utils/editor-mounted.ts
index 1534587a..4ce4a314 100644
--- a/src/utils/editor-mounted.ts
+++ b/src/utils/editor-mounted.ts
@@ -1,4 +1,4 @@
-import { MosaicId } from '../interfaces';
+import { EditorId } from '../interfaces';
 
 import { waitFor } from './wait-for';
 
@@ -6,7 +6,7 @@ import { waitFor } from './wait-for';
  * Waits for editors to mount on a list of Mosaic IDs
  * @param editors
  */
-export function waitForEditorsToMount(editors: Array<MosaicId>) {
+export function waitForEditorsToMount(editors: Array<EditorId>) {
   const allMounted = () =>
     editors.every((id) => !!window.ElectronFiddle.editors[id]);
   const opts = { timeout: 3000, interval: 100 };
diff --git a/src/utils/editors-mosaic-arrangement.ts b/src/utils/editors-mosaic-arrangement.ts
index ea891e8f..ae2638ae 100644
--- a/src/utils/editors-mosaic-arrangement.ts
+++ b/src/utils/editors-mosaic-arrangement.ts
@@ -1,18 +1,18 @@
 import { MosaicDirection, MosaicNode } from 'react-mosaic-component';
 
-import { MosaicId } from '../interfaces';
+import { EditorId } from '../interfaces';
 
 /**
  * Create a mosaic arrangement given an array of editor ids.
  *
  * @export
- * @param {Array<MosaicId>} input
- * @returns {MosaicNode<MosaicId>}
+ * @param {Array<EditorId>} input
+ * @returns {MosaicNode<EditorId>}
  */
 export function createMosaicArrangement(
-  input: Array<MosaicId>,
+  input: Array<EditorId>,
   direction: MosaicDirection = 'row',
-): MosaicNode<MosaicId> {
+): MosaicNode<EditorId> {
   if (input.length === 1) {
     return input[0];
   }
@@ -32,25 +32,25 @@ export function createMosaicArrangement(
  * Returns an array of visible editors given a mosaic arrangement.
  *
  * @export
- * @param {MosaicNode<MosaicId> | null} input
- * @returns {Array<MosaicId>}
+ * @param {MosaicNode<EditorId> | null} input
+ * @returns {Array<EditorId>}
  */
 export function getVisibleMosaics(
-  input: MosaicNode<MosaicId> | null,
-): Array<MosaicId> {
+  input: MosaicNode<EditorId> | null,
+): Array<EditorId> {
   // Handle the unlikely null case
   if (!input) return [];
 
   // Handle the case where only one editor is visible
   if (typeof input === 'string') {
-    return [input as MosaicId];
+    return [input as EditorId];
   }
 
   // Handle the other cases (2 - 4)
-  const result: Array<MosaicId> = [];
+  const result: Array<EditorId> = [];
   for (const node of [input.first, input.second]) {
     if (typeof node === 'string') {
-      result.push(node as MosaicId);
+      result.push(node as EditorId);
     } else {
       result.push(...getVisibleMosaics(node));
     }
diff --git a/src/utils/type-checks.ts b/src/utils/type-checks.ts
deleted file mode 100644
index 6ffaad80..00000000
--- a/src/utils/type-checks.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import {
-  ALL_PANELS,
-  DefaultEditorId,
-  DEFAULT_EDITORS,
-  EditorId,
-  MosaicId,
-  PanelId,
-} from '../interfaces';
-import { EditorBackup } from './editor-backup';
-
-/**
- * Is the given string of type `EditorId`?
- *
- * @export
- * @param {(EditorId | PanelId | MosaicId)} input
- * @returns {input is EditorId}
- */
-export function isEditorId(
-  input: MosaicId,
-  editors: string[],
-): input is EditorId {
-  return (
-    DEFAULT_EDITORS.includes(input as DefaultEditorId) ||
-    editors.includes(input as EditorId)
-  );
-}
-
-/**
- * Is the given string of type `isPanelId`?
- *
- * @export
- * @param {(EditorId | PanelId | MosaicId)} input
- * @returns {input is MosaicId}
- */
-export function isPanelId(
-  input: EditorId | PanelId | MosaicId,
-): input is PanelId {
-  return ALL_PANELS.includes(input as PanelId);
-}
-
-/**
- * Is the given input of type `EditorBackup`?
- *
- * @export
- * @param {(EditorBackup | true)} input
- * @returns {input is EditorBackup}
- */
-export function isEditorBackup(
-  input?: EditorBackup | true | null,
-): input is EditorBackup {
-  return !!(input && input !== true);
-}


Test patch:
diff --git a/tests/mocks/state.ts b/tests/mocks/state.ts
index 6cebc694..a19c9599 100644
--- a/tests/mocks/state.ts
+++ b/tests/mocks/state.ts
@@ -4,11 +4,10 @@ import { MosaicNode } from 'react-mosaic-component';
 import {
   BlockableAccelerator,
   CustomEditorId,
-  DocsDemoPage,
   ElectronReleaseChannel,
   GenericDialogOptions,
   GistActionState,
-  MosaicId,
+  EditorId,
   RunnableVersion,
 } from '../../src/interfaces';
 import { EditorBackup } from '../../src/utils/editor-backup';
@@ -21,10 +20,11 @@ export class StateMock {
   @observable public activeGistAction = GistActionState.none;
   @observable public channelsToShow: ElectronReleaseChannel[] = [];
   @observable public closedPanels: Partial<
-    Record<MosaicId, EditorBackup | true>
+    Record<EditorId, EditorBackup | true>
   > = {};
-  @observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
   @observable public customMosaics: CustomEditorId[] = [];
+  @observable public environmentVariables: string[] = [];
+  @observable public executionFlags: string[] = [];
   @observable public genericDialogLastInput: string | null = null;
   @observable public genericDialogLastResult: boolean | null = null;
   @observable public genericDialogOptions: GenericDialogOptions = {} as any;
@@ -48,7 +48,7 @@ export class StateMock {
   @observable public isTourShowing = false;
   @observable public isUsingSystemTheme = true;
   @observable public isWarningDialogShowing = false;
-  @observable public mosaicArrangement: MosaicNode<MosaicId> | null = null;
+  @observable public mosaicArrangement: MosaicNode<EditorId> | null = null;
   @observable public output = [];
   @observable public showUndownloadedVersions = false;
   @observable public theme: string | null;
diff --git a/tests/renderer/components/__snapshots__/commands-editors-spec.tsx.snap b/tests/renderer/components/__snapshots__/commands-editors-spec.tsx.snap
index 6ab884fe..64e98158 100644
--- a/tests/renderer/components/__snapshots__/commands-editors-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/commands-editors-spec.tsx.snap
@@ -182,13 +182,6 @@ exports[`EditorDropdown component renders 1`] = `
       text="Editors"
     />
   </Blueprint3.Popover>
-  <Blueprint3.Button
-    active={true}
-    icon="help"
-    id="docsDemo"
-    onClick={[Function]}
-    text="Docs & Demos"
-  />
 </Fragment>
 `;
 
@@ -296,12 +289,5 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
       text="Editors"
     />
   </Blueprint3.Popover>
-  <Blueprint3.Button
-    active={true}
-    icon="help"
-    id="docsDemo"
-    onClick={[Function]}
-    text="Docs & Demos"
-  />
 </Fragment>
 `;
diff --git a/tests/renderer/components/__snapshots__/commands-spec.tsx.snap b/tests/renderer/components/__snapshots__/commands-spec.tsx.snap
index aa8ca579..cd1bff7f 100644
--- a/tests/renderer/components/__snapshots__/commands-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/commands-spec.tsx.snap
@@ -24,7 +24,6 @@ exports[`Commands component renders 1`] = `
             "channelsToShow": Array [],
             "clearConsole": [MockFunction],
             "closedPanels": Object {},
-            "currentDocsDemoPage": "DEFAULT",
             "currentElectronVersion": Object {
               "source": "remote",
               "state": "ready",
@@ -33,6 +32,8 @@ exports[`Commands component renders 1`] = `
             "customMosaics": Array [],
             "disableTour": [MockFunction],
             "downloadVersion": [MockFunction],
+            "environmentVariables": Array [],
+            "executionFlags": Array [],
             "genericDialogLastInput": null,
             "genericDialogLastResult": null,
             "genericDialogOptions": Object {},
@@ -131,7 +132,6 @@ exports[`Commands component renders 1`] = `
             "channelsToShow": Array [],
             "clearConsole": [MockFunction],
             "closedPanels": Object {},
-            "currentDocsDemoPage": "DEFAULT",
             "currentElectronVersion": Object {
               "source": "remote",
               "state": "ready",
@@ -140,6 +140,8 @@ exports[`Commands component renders 1`] = `
             "customMosaics": Array [],
             "disableTour": [MockFunction],
             "downloadVersion": [MockFunction],
+            "environmentVariables": Array [],
+            "executionFlags": Array [],
             "genericDialogLastInput": null,
             "genericDialogLastResult": null,
             "genericDialogOptions": Object {},
@@ -248,7 +250,6 @@ exports[`Commands component renders 1`] = `
             "channelsToShow": Array [],
             "clearConsole": [MockFunction],
             "closedPanels": Object {},
-            "currentDocsDemoPage": "DEFAULT",
             "currentElectronVersion": Object {
               "source": "remote",
               "state": "ready",
@@ -257,6 +258,8 @@ exports[`Commands component renders 1`] = `
             "customMosaics": Array [],
             "disableTour": [MockFunction],
             "downloadVersion": [MockFunction],
+            "environmentVariables": Array [],
+            "executionFlags": Array [],
             "genericDialogLastInput": null,
             "genericDialogLastResult": null,
             "genericDialogOptions": Object {},
@@ -358,7 +361,6 @@ exports[`Commands component renders 1`] = `
           "channelsToShow": Array [],
           "clearConsole": [MockFunction],
           "closedPanels": Object {},
-          "currentDocsDemoPage": "DEFAULT",
           "currentElectronVersion": Object {
             "source": "remote",
             "state": "ready",
@@ -367,6 +369,8 @@ exports[`Commands component renders 1`] = `
           "customMosaics": Array [],
           "disableTour": [MockFunction],
           "downloadVersion": [MockFunction],
+          "environmentVariables": Array [],
+          "executionFlags": Array [],
           "genericDialogLastInput": null,
           "genericDialogLastResult": null,
           "genericDialogOptions": Object {},
@@ -465,7 +469,6 @@ exports[`Commands component renders 1`] = `
           "channelsToShow": Array [],
           "clearConsole": [MockFunction],
           "closedPanels": Object {},
-          "currentDocsDemoPage": "DEFAULT",
           "currentElectronVersion": Object {
             "source": "remote",
             "state": "ready",
@@ -474,6 +477,8 @@ exports[`Commands component renders 1`] = `
           "customMosaics": Array [],
           "disableTour": [MockFunction],
           "downloadVersion": [MockFunction],
+          "environmentVariables": Array [],
+          "executionFlags": Array [],
           "genericDialogLastInput": null,
           "genericDialogLastResult": null,
           "genericDialogOptions": Object {},
diff --git a/tests/renderer/components/__snapshots__/commands-version-chooser-spec.tsx.snap b/tests/renderer/components/__snapshots__/commands-version-chooser-spec.tsx.snap
index 559da7f4..ef639b6f 100644
--- a/tests/renderer/components/__snapshots__/commands-version-chooser-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/commands-version-chooser-spec.tsx.snap
@@ -20,7 +20,6 @@ exports[`VersionSelect component renders 1`] = `
         ],
         "clearConsole": [MockFunction],
         "closedPanels": Object {},
-        "currentDocsDemoPage": "DEFAULT",
         "currentElectronVersion": Object {
           "source": "remote",
           "state": "ready",
@@ -29,6 +28,8 @@ exports[`VersionSelect component renders 1`] = `
         "customMosaics": Array [],
         "disableTour": [MockFunction],
         "downloadVersion": [MockFunction],
+        "environmentVariables": Array [],
+        "executionFlags": Array [],
         "genericDialogLastInput": null,
         "genericDialogLastResult": null,
         "genericDialogOptions": Object {},
diff --git a/tests/renderer/components/__snapshots__/dialog-bisect-spec.tsx.snap b/tests/renderer/components/__snapshots__/dialog-bisect-spec.tsx.snap
index 33bbd5cc..9d438227 100644
--- a/tests/renderer/components/__snapshots__/dialog-bisect-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/dialog-bisect-spec.tsx.snap
@@ -63,7 +63,6 @@ exports[`BisectDialog component renders 1`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -72,6 +71,8 @@ exports[`BisectDialog component renders 1`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -264,7 +265,6 @@ exports[`BisectDialog component renders 1`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -273,6 +273,8 @@ exports[`BisectDialog component renders 1`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -538,7 +540,6 @@ exports[`BisectDialog component renders 2`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -547,6 +548,8 @@ exports[`BisectDialog component renders 2`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -732,7 +735,6 @@ exports[`BisectDialog component renders 2`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -741,6 +743,8 @@ exports[`BisectDialog component renders 2`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -999,7 +1003,6 @@ exports[`BisectDialog component renders 3`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -1008,6 +1011,8 @@ exports[`BisectDialog component renders 3`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -1200,7 +1205,6 @@ exports[`BisectDialog component renders 3`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -1209,6 +1213,8 @@ exports[`BisectDialog component renders 3`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -1467,7 +1473,6 @@ exports[`BisectDialog component renders 4`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -1476,6 +1481,8 @@ exports[`BisectDialog component renders 4`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -1668,7 +1675,6 @@ exports[`BisectDialog component renders 4`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -1677,6 +1683,8 @@ exports[`BisectDialog component renders 4`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -1943,7 +1951,6 @@ exports[`BisectDialog component renders 5`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -1952,6 +1959,8 @@ exports[`BisectDialog component renders 5`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
@@ -2144,7 +2153,6 @@ exports[`BisectDialog component renders 5`] = `
               ],
               "clearConsole": [MockFunction],
               "closedPanels": Object {},
-              "currentDocsDemoPage": "DEFAULT",
               "currentElectronVersion": Object {
                 "source": "remote",
                 "state": "ready",
@@ -2153,6 +2161,8 @@ exports[`BisectDialog component renders 5`] = `
               "customMosaics": Array [],
               "disableTour": [MockFunction],
               "downloadVersion": [MockFunction],
+              "environmentVariables": Array [],
+              "executionFlags": Array [],
               "genericDialogLastInput": null,
               "genericDialogLastResult": null,
               "genericDialogOptions": Object {},
diff --git a/tests/renderer/components/__snapshots__/editors-spec.tsx.snap b/tests/renderer/components/__snapshots__/editors-spec.tsx.snap
index 0838af84..b57a1447 100644
--- a/tests/renderer/components/__snapshots__/editors-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/editors-spec.tsx.snap
@@ -201,7 +201,7 @@ exports[`Editors component renders 1`] = `
       key="renderer.js"
       style={
         Object {
-          "bottom": "25%",
+          "bottom": "0%",
           "left": "0%",
           "right": "50%",
           "top": "50%",
@@ -356,14 +356,14 @@ exports[`Editors component renders 1`] = `
       </div>
     </div>
     <div
-      className="mosaic-split -column"
+      className="mosaic-split -row"
       onMouseDown={[Function]}
       style={
         Object {
           "bottom": "0%",
-          "left": "0%",
-          "right": "50%",
-          "top": "75%",
+          "left": "50%",
+          "right": "0%",
+          "top": "0%",
         }
       }
     >
@@ -376,10 +376,10 @@ exports[`Editors component renders 1`] = `
       key="preload.js"
       style={
         Object {
-          "bottom": "0%",
-          "left": "0%",
-          "right": "50%",
-          "top": "75%",
+          "bottom": "50%",
+          "left": "50%",
+          "right": "0%",
+          "top": "0%",
         }
       }
     >
@@ -531,14 +531,14 @@ exports[`Editors component renders 1`] = `
       </div>
     </div>
     <div
-      className="mosaic-split -row"
+      className="mosaic-split -column"
       onMouseDown={[Function]}
       style={
         Object {
           "bottom": "0%",
           "left": "50%",
           "right": "0%",
-          "top": "0%",
+          "top": "50%",
         }
       }
     >
@@ -551,10 +551,10 @@ exports[`Editors component renders 1`] = `
       key="index.html"
       style={
         Object {
-          "bottom": "50%",
+          "bottom": "25%",
           "left": "50%",
           "right": "0%",
-          "top": "0%",
+          "top": "50%",
         }
       }
     >
@@ -713,7 +713,7 @@ exports[`Editors component renders 1`] = `
           "bottom": "0%",
           "left": "50%",
           "right": "0%",
-          "top": "50%",
+          "top": "75%",
         }
       }
     >
@@ -726,10 +726,10 @@ exports[`Editors component renders 1`] = `
       key="styles.css"
       style={
         Object {
-          "bottom": "25%",
+          "bottom": "0%",
           "left": "50%",
           "right": "0%",
-          "top": "50%",
+          "top": "75%",
         }
       }
     >
@@ -880,214 +880,6 @@ exports[`Editors component renders 1`] = `
         </div>
       </div>
     </div>
-    <div
-      className="mosaic-split -column"
-      onMouseDown={[Function]}
-      style={
-        Object {
-          "bottom": "0%",
-          "left": "50%",
-          "right": "0%",
-          "top": "75%",
-        }
-      }
-    >
-      <div
-        className="mosaic-split-line"
-      />
-    </div>
-    <div
-      className="mosaic-tile"
-      key="docsDemo"
-      style={
-        Object {
-          "bottom": "0%",
-          "left": "50%",
-          "right": "0%",
-          "top": "75%",
-        }
-      }
-    >
-      <div
-        className="mosaic-window mosaic-drop-target docsDemo"
-      >
-        <div
-          className="mosaic-window-toolbar draggable"
-        >
-          <div>
-            <div>
-              <h5>
-                Custom Editor (docsDemo)
-              </h5>
-            </div>
-            <div />
-            <div
-              className="mosaic-controls"
-            >
-              <button
-                className="bp3-button bp3-small"
-                onBlur={[Function]}
-                onClick={[Function]}
-                onKeyDown={[Function]}
-                onKeyUp={[Function]}
-                type="button"
-              >
-                <span
-                  className="bp3-icon bp3-icon-home"
-                  icon="home"
-                >
-                  <svg
-                    data-icon="home"
-                    height={16}
-                    viewBox="0 0 16 16"
-                    width={16}
-                  >
-                    <desc>
-                      home
-                    </desc>
-                    <path
-                      d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 001.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
-                      fillRule="evenodd"
-                      key="0"
-                    />
-                  </svg>
-                </span>
-                <span
-                  className="bp3-button-text"
-                  key="text"
-                >
-                  Overview
-                </span>
-              </button>
-              <button
-                className="bp3-button bp3-small"
-                onBlur={[Function]}
-                onClick={[Function]}
-                onKeyDown={[Function]}
-                onKeyUp={[Function]}
-                type="button"
-              >
-                <span
-                  className="bp3-icon bp3-icon-maximize"
-                  icon="maximize"
-                >
-                  <svg
-                    data-icon="maximize"
-                    height={16}
-                    viewBox="0 0 16 16"
-                    width={16}
-                  >
-                    <desc>
-                      maximize
-                    </desc>
-                    <path
-                      d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
-                      fillRule="evenodd"
-                      key="0"
-                    />
-                  </svg>
-                </span>
-              </button>
-              <button
-                className="bp3-button bp3-small"
-                onBlur={[Function]}
-                onClick={[Function]}
-                onKeyDown={[Function]}
-                onKeyUp={[Function]}
-                type="button"
-              >
-                <span
-                  className="bp3-icon bp3-icon-cross"
-                  icon="cross"
-                >
-                  <svg
-                    data-icon="cross"
-                    height={16}
-                    viewBox="0 0 16 16"
-                    width={16}
-                  >
-                    <desc>
-                      cross
-                    </desc>
-                    <path
-                      d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
-                      fillRule="evenodd"
-                      key="0"
-                    />
-                  </svg>
-                </span>
-              </button>
-            </div>
-          </div>
-        </div>
-        <div
-          className="mosaic-window-body"
-        />
-        <div
-          className="mosaic-window-body-overlay"
-          onClick={[Function]}
-        />
-        <div
-          className="mosaic-window-additional-actions-bar"
-        />
-        <div
-          className="mosaic-preview"
-        >
-          <div
-            className="mosaic-window-toolbar"
-          >
-            <div
-              className="mosaic-window-title"
-            >
-              Custom Editor (docsDemo)
-            </div>
-          </div>
-          <div
-            className="mosaic-window-body"
-          >
-            <h4>
-              Custom Editor (docsDemo)
-            </h4>
-            <span
-              className="bp3-icon bp3-icon-application"
-              icon="application"
-            >
-              <svg
-                data-icon="application"
-                height={72}
-                viewBox="0 0 20 20"
-                width={72}
-              >
-                <desc>
-                  application
-                </desc>
-                <path
-                  d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
-                  fillRule="evenodd"
-                  key="0"
-                />
-              </svg>
-            </span>
-          </div>
-        </div>
-        <div
-          className="drop-target-container"
-        >
-          <div
-            className="drop-target top"
-          />
-          <div
-            className="drop-target bottom"
-          />
-          <div
-            className="drop-target left"
-          />
-          <div
-            className="drop-target right"
-          />
-        </div>
-      </div>
-    </div>
   </div>
   <div
     className="drop-target-container"
@@ -1135,7 +927,6 @@ exports[`Editors component renders a toolbar 1`] = `
             "channelsToShow": Array [],
             "clearConsole": [MockFunction],
             "closedPanels": Object {},
-            "currentDocsDemoPage": "DEFAULT",
             "currentElectronVersion": Object {
               "source": "remote",
               "state": "ready",
@@ -1144,6 +935,8 @@ exports[`Editors component renders a toolbar 1`] = `
             "customMosaics": Array [],
             "disableTour": [MockFunction],
             "downloadVersion": [MockFunction],
+            "environmentVariables": Array [],
+            "executionFlags": Array [],
             "genericDialogLastInput": null,
             "genericDialogLastResult": null,
             "genericDialogOptions": Object {},
@@ -1177,19 +970,15 @@ exports[`Editors component renders a toolbar 1`] = `
               "first": Object {
                 "direction": "column",
                 "first": "main.js",
-                "second": Object {
-                  "direction": "column",
-                  "first": "renderer.js",
-                  "second": "preload.js",
-                },
+                "second": "renderer.js",
               },
               "second": Object {
                 "direction": "column",
-                "first": "index.html",
+                "first": "preload.js",
                 "second": Object {
                   "direction": "column",
-                  "first": "styles.css",
-                  "second": "docsDemo",
+                  "first": "index.html",
+                  "second": "styles.css",
                 },
               },
             },
@@ -1263,7 +1052,6 @@ exports[`Editors component renders a toolbar 1`] = `
             "channelsToShow": Array [],
             "clearConsole": [MockFunction],
             "closedPanels": Object {},
-            "currentDocsDemoPage": "DEFAULT",
             "currentElectronVersion": Object {
               "source": "remote",
               "state": "ready",
@@ -1272,6 +1060,8 @@ exports[`Editors component renders a toolbar 1`] = `
             "customMosaics": Array [],
             "disableTour": [MockFunction],
             "downloadVersion": [MockFunction],
+            "environmentVariables": Array [],
+            "executionFlags": Array [],
             "genericDialogLastInput": null,
             "genericDialogLastResult": null,
             "genericDialogOptions": Object {},
@@ -1305,19 +1095,15 @@ exports[`Editors component renders a toolbar 1`] = `
               "first": Object {
                 "direction": "column",
                 "first": "main.js",
-                "second": Object {
-                  "direction": "column",
-                  "first": "renderer.js",
-                  "second": "preload.js",
-                },
+                "second": "renderer.js",
               },
               "second": Object {
                 "direction": "column",
-                "first": "index.html",
+                "first": "preload.js",
                 "second": Object {
                   "direction": "column",
-                  "first": "styles.css",
-                  "second": "docsDemo",
+                  "first": "index.html",
+                  "second": "styles.css",
                 },
               },
             },
diff --git a/tests/renderer/components/__snapshots__/settings-execution-spec.tsx.snap b/tests/renderer/components/__snapshots__/settings-execution-spec.tsx.snap
index 87de5f77..73d9b636 100644
--- a/tests/renderer/components/__snapshots__/settings-execution-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/settings-execution-spec.tsx.snap
@@ -26,8 +26,33 @@ exports[`ExecutionSettings component renders 1`] = `
   <Blueprint3.Callout>
     <Blueprint3.FormGroup
       label="There are some flags that Electron uses to log extra information both internally
-      and through Chromium.  Enable this option to make Fiddle produce those logs."
+      and through Chromium. Enable this option to make Fiddle produce those logs."
     >
+      <p>
+        Enabling advanced Electron logging will set the
+         
+        <code>
+          ELECTRON_ENABLE_LOGGING
+        </code>
+        ,
+        <code>
+          ELECTRON_DEBUG_NOTIFICATION
+        </code>
+        , and
+         
+        <code>
+          ELECTRON_ENABLE_STACK_DUMPING
+        </code>
+         environment variables to true. See
+         
+        <a
+          href="https://www.electronjs.org/docs/api/environnment-variables"
+        >
+          documentation
+        </a>
+         
+        for more information about what they do.
+      </p>
       <Blueprint3.Checkbox
         label="Enable advanced Electron logging."
         onChange={[Function]}
@@ -45,7 +70,11 @@ exports[`ExecutionSettings component renders 1`] = `
         >
           user-provided flags
         </a>
-        , such as '--js-flags=--expose-gc'. Those can be added here as bar-separated (|) flags to run when you start your Fiddles.
+        , such as 
+        <code>
+          --js-flags=--expose-gc
+        </code>
+        . Those can be added here as bar-separated (|) flags to run when you start your Fiddles.
       </p>
       <br />
       <Blueprint3.InputGroup
@@ -56,6 +85,32 @@ exports[`ExecutionSettings component renders 1`] = `
     </Blueprint3.FormGroup>
   </Blueprint3.Callout>
   <br />
+  <Blueprint3.Callout>
+    <Blueprint3.FormGroup>
+      <p>
+        Electron allows starting the executable with
+         
+        <a
+          href="https://www.electronjs.org/docs/api/environment-variables"
+        >
+          user-provided environment variables
+        </a>
+        , such as
+         
+        <code>
+          NODE_OPTIONS="--no-warnings --max-old-space-size=2048"
+        </code>
+        . Those can be added here as bar-separated (|) variables to run when you start your Fiddles.
+      </p>
+      <br />
+      <Blueprint3.InputGroup
+        onChange={[Function]}
+        placeholder="NODE_OPTIONS=\\"--no-warnings --max-old-space-size=2048\\""
+        value=""
+      />
+    </Blueprint3.FormGroup>
+  </Blueprint3.Callout>
+  <br />
   <Blueprint3.Callout>
     <Blueprint3.FormGroup>
       <span
diff --git a/tests/renderer/components/editors-spec.tsx b/tests/renderer/components/editors-spec.tsx
index 170a74e8..0eec4eac 100644
--- a/tests/renderer/components/editors-spec.tsx
+++ b/tests/renderer/components/editors-spec.tsx
@@ -2,7 +2,7 @@ import { mount, shallow } from 'enzyme';
 import * as React from 'react';
 
 import * as content from '../../../src/renderer/content';
-import { ALL_MOSAICS, DefaultEditorId } from '../../../src/interfaces';
+import { DEFAULT_EDITORS, DefaultEditorId } from '../../../src/interfaces';
 import { Editors } from '../../../src/renderer/components/editors';
 import { IpcEvents } from '../../../src/ipc-events';
 import { createMosaicArrangement } from '../../../src/utils/editors-mosaic-arrangement';
@@ -38,7 +38,7 @@ describe('Editors component', () => {
   beforeEach(() => {
     ({ ElectronFiddle } = window as any);
     ({ monaco, state: store } = ElectronFiddle.app);
-    store.mosaicArrangement = createMosaicArrangement(ALL_MOSAICS);
+    store.mosaicArrangement = createMosaicArrangement(DEFAULT_EDITORS);
   });
 
   it('renders', () => {
diff --git a/tests/renderer/components/settings-execution-spec.tsx b/tests/renderer/components/settings-execution-spec.tsx
index 179064c1..ede9414e 100644
--- a/tests/renderer/components/settings-execution-spec.tsx
+++ b/tests/renderer/components/settings-execution-spec.tsx
@@ -7,7 +7,10 @@ describe('ExecutionSettings component', () => {
   let store: any;
 
   beforeEach(() => {
-    store = {};
+    store = {
+      executionFlags: [],
+      environmentVariables: [],
+    };
   });
 
   it('renders', () => {
@@ -71,4 +74,27 @@ describe('ExecutionSettings component', () => {
       ]);
     });
   });
+
+  describe('handleEnvironmentVariableChange()', () => {
+    it('handles new environment variables', async () => {
+      const wrapper = shallow(<ExecutionSettings appState={store} />);
+      const instance = wrapper.instance() as any;
+
+      const dragRegions = 'ELECTRON_DEBUG_DRAG_REGIONS=1';
+      const trash = 'ELECTRON_TRASH=trash-cli';
+      await instance.handleEnvironmentVariableChange({
+        currentTarget: { value: dragRegions },
+      });
+
+      expect(store.environmentVariables).toEqual([dragRegions]);
+
+      await instance.handleEnvironmentVariableChange({
+        currentTarget: {
+          value: `${dragRegions}|${trash}`,
+        },
+      });
+
+      expect(store.environmentVariables).toEqual([dragRegions, trash]);
+    });
+  });
 });
diff --git a/tests/renderer/state-spec.ts b/tests/renderer/state-spec.ts
index 61cfac61..b0136363 100644
--- a/tests/renderer/state-spec.ts
+++ b/tests/renderer/state-spec.ts
@@ -1,12 +1,11 @@
 import * as MonacoType from 'monaco-editor';
 
 import {
-  ALL_MOSAICS,
   BlockableAccelerator,
+  DEFAULT_EDITORS,
   DefaultEditorId,
   ElectronReleaseChannel,
   GenericDialogType,
-  PanelId,
   RunnableVersion,
   Version,
   VersionSource,
@@ -604,7 +603,7 @@ describe('AppState', () => {
       const result = appState.getAndRemoveEditorValueBackup(
         DefaultEditorId.main,
       );
-      expect(result).toEqual(null);
+      expect(result).toEqual(undefined);
     });
 
     it('returns and deletes a backup if there is one', () => {
@@ -619,7 +618,7 @@ describe('AppState', () => {
 
   describe('setVisibleMosaics()', () => {
     it('updates the visible editors and creates a backup', async () => {
-      appState.mosaicArrangement = createMosaicArrangement(ALL_MOSAICS);
+      appState.mosaicArrangement = createMosaicArrangement(DEFAULT_EDITORS);
       appState.closedPanels = {};
       appState.customMosaics = [];
       await appState.setVisibleMosaics([DefaultEditorId.main]);
@@ -634,22 +633,6 @@ describe('AppState', () => {
       expect(appState.closedPanels[DefaultEditorId.html]).toBeTruthy();
       expect(appState.closedPanels[DefaultEditorId.main]).toBeUndefined();
     });
-
-    it('removes the backup for a non-editor right away', async () => {
-      appState.closedPanels = {};
-      appState.closedPanels[PanelId.docsDemo] = true;
-
-      for (const mosaic of ALL_MOSAICS) {
-        // we just need to mock something truthy here
-        window.ElectronFiddle.editors[
-          mosaic
-        ] = {} as MonacoType.editor.IStandaloneCodeEditor;
-      }
-
-      await appState.setVisibleMosaics(ALL_MOSAICS);
-
-      expect(appState.closedPanels[PanelId.docsDemo]).toBeUndefined();
-    });
   });
 
   describe('removeCustomMosaic()', () => {
diff --git a/tests/utils/editor-value-spec.ts b/tests/utils/editor-value-spec.ts
index 5540246a..a4ad7c9f 100644
--- a/tests/utils/editor-value-spec.ts
+++ b/tests/utils/editor-value-spec.ts
@@ -1,4 +1,4 @@
-import { DefaultEditorId, MosaicId } from '../../src/interfaces';
+import { DefaultEditorId, EditorId } from '../../src/interfaces';
 import { getEditorValue } from '../../src/utils/editor-value';
 import { StateMock } from '../mocks/state';
 
@@ -16,7 +16,7 @@ describe('getEditorValue()', () => {
     const mockState = window.ElectronFiddle.app.state;
 
     mockState.closedPanels = {
-      [DefaultEditorId.html as MosaicId]: {
+      [DefaultEditorId.html as EditorId]: {
         value: 'editor-backup-value',
       },
     };
diff --git a/tests/utils/type-checks-spec.ts b/tests/utils/type-checks-spec.ts
deleted file mode 100644
index f2e593c0..00000000
--- a/tests/utils/type-checks-spec.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { DefaultEditorId, PanelId } from '../../src/interfaces';
-import {
-  isEditorBackup,
-  isEditorId,
-  isPanelId,
-} from '../../src/utils/type-checks';
-
-describe('Type checks', () => {
-  describe('isEditorId()', () => {
-    it('works', () => {
-      expect(isEditorId(DefaultEditorId.html, [])).toBe(true);
-      expect(isEditorId(PanelId.docsDemo, [])).toBe(false);
-    });
-  });
-
-  describe('isPanelId()', () => {
-    it('works', () => {
-      expect(isPanelId(DefaultEditorId.html)).toBe(false);
-      expect(isPanelId(PanelId.docsDemo)).toBe(true);
-    });
-  });
-
-  describe('isEditorBackup()', () => {
-    it('works', () => {
-      expect(isEditorBackup(true)).toBe(false);
-      expect(isEditorBackup({} as any)).toBe(true);
-    });
-  });
-});