diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f28cab4c..d907765e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,7 +11,7 @@ Some Caveats Up Front:
 * Feedback may take time
 * Merges may take time 
 
-**--> The current release branch is [1.0.6](https://github.com/chriskiehl/Gooey/tree/1.0.6-release) <--**. All PRs should be opened against this branch. 
+**--> The current release branch is [1.2.1](https://github.com/chriskiehl/Gooey/tree/1.2.1-release) <--**. All PRs should be opened against this branch. 
 
 
 ### Getting Started: 
diff --git a/README.md b/README.md
index 12bf53ce..4475dbef 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,13 @@
-Gooey 
-=====  
-Turn (almost) any Python 2 or 3 Console Program into a GUI application with one line
+# Gooey 
+  
+
+Turn (almost) any Python 3 Console Program into a GUI application with one line
 
 <p align="center">
     <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/1-0-4-title-card.png" />
 </p>
 
 
-### Support this project
-
-<a href="https://patreon.com/chriskiehl" target="_blank">
-    <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/support-request.jpg" />
-</a>
-
-
 Table of Contents
 -----------------  
 
@@ -36,8 +30,8 @@ Table of Contents
     - [Basic](#basic)
     - [No Config](#no-config)
 - [Menus](#menus)    
-- [Input Validation](#input-validation)
-- [Using Dynamic Values](#using-dynamic-values)
+- [Dynamic Validation](#dynamic-validation)
+- [Lifecycle Events and UI control](#lifecycle-events-and-ui-control)
 - [Showing Progress](#showing-progress)
     - [Elapsed / Remaining Time](#elapsed--remaining-time)
 - [Customizing Icons](#customizing-icons)
@@ -69,8 +63,6 @@ run `setup.py`
 
     python setup.py install
     
-**NOTE:** Python 2 users must manually install WxPython! Unfortunately, this cannot be done as part of the pip installation and should be manually downloaded from the [wxPython website](http://www.wxpython.org/download.php).
-
 
 
 ### Usage  
@@ -163,6 +155,7 @@ Gooey does its best to choose sensible defaults based on the options it finds. C
 | store_const | CheckBox |<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>|
 | store_true | CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>|
 | store_False | CheckBox|  <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>   |
+| version | CheckBox|  <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>   |
 | append | TextCtrl |  <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png"/>  | 
 | count | DropDown &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f53ccbe4-07c5-11e5-80e5-510e2aa22922.png"/> | 
 | Mutually Exclusive Group | RadioGroup | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f553feb8-07c5-11e5-9d5b-eaa4772075a9.png"/>
@@ -209,6 +202,9 @@ However, by dropping in `GooeyParser` and supplying a `widget` name, you can dis
 | BlockCheckbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/46922288-9296f200-cfbb-11e8-8b0d-ddde08064247.png) <br/> The default InlineCheck box can look less than ideal if a large help text block is present. `BlockCheckbox` moves the text block to the normal position and provides a short-form `block_label` for display next to the control. Use `gooey_options.checkbox_label` to control the label text | 
 |  ColourChooser   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| <p align="center"><img src="https://user-images.githubusercontent.com/21027844/72672451-0752aa80-3a0f-11ea-86ed-8303bd3e54b5.gif" width="400"></p> |
 | FilterableDropdown | <p align="center"><img src="https://raw.githubusercontent.com/chriskiehl/GooeyImages/images/readme-images/filterable-dropdown.gif" width="400"></p> |
+| IntegerField | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/integer-field.PNG" width="400"></p> |
+| DecimalField | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/decimal-field.PNG" width="400"></p> |
+| Slider | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/slider.PNG" width="400"></p> |
 
 
 
@@ -227,7 +223,7 @@ Gooey is international ready and easily ported to your host language. Languages
 
 All program text is stored externally in `json` files. So adding new language support is as easy as pasting a few key/value pairs in the `gooey/languages/` directory. 
 
-Thanks to some awesome [contributers](https://github.com/chriskiehl/Gooey/graphs/contributors), Gooey currently comes pre-stocked with over 18 different translations! 
+Thanks to some awesome [contributors](https://github.com/chriskiehl/Gooey/graphs/contributors), Gooey currently comes pre-stocked with over 18 different translations! 
 
 Want to add another one? Submit a [pull request!](https://github.com/chriskiehl/Gooey/compare)
 
@@ -254,11 +250,11 @@ Just about everything in Gooey's overall look and feel can be customized by pass
 | program_description | Sets the text displayed in the top panel of the `Settings` screen. Defaults to the description pulled from `ArgumentParser`. |
 | default_size | Initial size of the window | 
 | fullscreen | start Gooey in fullscreen mode |
-| required_cols | Controls how many columns are in the Required Arguments section <br> :warning: **Deprecation notice:** See [Group Parameters](#group-configuration) for modern layout controls|
-| optional_cols | Controls how many columns are in the Optional Arguments section <br> :warning: **Deprecation notice:** See [Group Parameters](#group-configuration) for modern layout controls|
+| required_cols | Controls how many columns are in the Required Arguments section <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern layout controls|
+| optional_cols | Controls how many columns are in the Optional Arguments section <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern layout controls|
 | dump_build_config | Saves a `json` copy of its build configuration on disk for reuse/editing | 
 | load_build_config | Loads a `json` copy of its build configuration from disk | 
-| monospace_display | Uses a mono-spaced font in the output screen <br> :warning: **Deprecation notice:** See [Group Parameters](#group-configuration) for modern font configuration| 
+| monospace_display | Uses a mono-spaced font in the output screen <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern font configuration| 
 | image_dir | Path to the directory in which Gooey should look for custom images/icons |
 | language_dir | Path to the directory in which Gooey should look for custom languages files |
 | disable_stop_button | Disable the `Stop` button when running |
@@ -279,6 +275,7 @@ Just about everything in Gooey's overall look and feel can be customized by pass
 | show_time_remaining | Disable the time remaining text see [Elapsed / Remaining Time](#elapsed--remaining-time) |
 | hide_time_remaining_on_complete | Hide time remaining on complete screen see [Elapsed / Remaining Time](#elapsed--remaining-time) |
 | requires_shell | Controls whether or not the `shell` argument is used when invoking your program. [More info here](https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess#3172488) |
+| shutdown_signal | Specifies the `signal` to send to the child process when the `stop` button is pressed. See [Gracefully Stopping](https://github.com/chriskiehl/Gooey/tree/master/docs) in the docs for more info. | 
 | navigation | Sets the "navigation" style of Gooey's top level window. <br>Options: <table> <thead> <tr><th>TABBED</th><th>SIDEBAR</th></tr></thead> <tbody> <tr> <td><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png" width="200" height="auto"></td><td><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png" width="200" height="auto"></td></tr></tbody></table>|
 | sidebar_title | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34472159-1bfedbd0-ef10-11e7-8bc3-b6d69febb8c3.png" width="250" height="auto" align="right"> Controls the heading title above the SideBar's navigation pane. Defaults to: "Actions" |
 | show_sidebar | Show/Hide the sidebar in when navigation mode == `SIDEBAR` |
@@ -403,6 +400,14 @@ The basic view is best for times when the user is familiar with Console Applicat
 
 No Config pretty much does what you'd expect: it doesn't show a configuration screen. It hops right to the `display` section and begins execution of the host program. This is the one for improving the appearance of little one-off scripts. 
 
+To use this mode, set `auto_start=True` in the Gooey decorator. 
+
+```python
+@Gooey(auto_start=True) 
+def main (): 
+    ... 
+```
+
 <p align="center">
     <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54fe6f2-07c5-11e5-92e4-f72a2ae12862.png">
 </p>
@@ -450,6 +455,7 @@ Currently, three types of menu options are supported:
  * AboutDialog 
  * MessageDialog
  * Link
+ * HtmlDialog
  
 
 <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/47251026-9ffc1400-d3e1-11e8-9095-982a6367561b.png" width="400" height="auto" align="right" />
@@ -518,6 +524,36 @@ Example:
 }
 ```
 
+
+<img src="https://github.com/chriskiehl/GooeyImages/raw/images/docs/menus/html-dialog.PNG" width="400" height="auto" align="right" />
+
+**HtmlDialog** gives you full control over what's displayed in the message dialog (bonus: people can copy/paste text from this one!). 
+
+
+
+Schema: 
+
+ * `caption` - (_optional_) the caption in the title bar of the modal   
+ * `html` - (_required_) the html you want displayed in the dialog. Note: only a small subset of HTML is supported. [See the WX docs for more info](https://wxpython.org/Phoenix/docs/html/html_overview.html). 
+
+Example: 
+
+```python
+{
+    'type': 'HtmlDialog',
+    'menuTitle': 'Fancy Dialog!',
+    'caption': 'Demo of the HtmlDialog',
+    'html': '''
+    <body bgcolor="white">
+        <img src=/path/to/your/image.png" /> 
+        <h1>Hello world!</h1> 
+        <p><font color="red">Lorem ipsum dolor sit amet, consectetur</font></p>
+    </body>
+    '''
+}
+
+```
+
 **A full example:**
 
 Two menu groups ("File" and "Help") with four menu items between them. 
@@ -562,108 +598,158 @@ Two menu groups ("File" and "Help") with four menu items between them.
 ---------------------------------------  
 
 
-### Input Validation
+### Dynamic Validation 
 
 
 <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464861-0e82c214-ee48-11e7-8f4a-a8e00721efef.png" width="400" height="auto" align="right" />
 
-
 >:warning: 
->Note! This functionality is experimental. Its API may be changed or removed altogether. Feedback/thoughts on this feature is welcome and encouraged! 
+>Note! This functionality is experimental and likely to be unstable. Its API may be changed or removed altogether. Feedback/thoughts on this feature is welcome and encouraged!
+ 
+>:warning: 
+>See [Release Notes]() for guidance on upgrading from 1.0.8 to 1.2.0 
 
-Gooey can optionally do some basic pre-flight validation on user input. Internally, it uses these validator functions to check for the presence of required arguments. However, by using [GooeyParser](#gooeyparser), you can extend these functions with your own validation rules. This allows Gooey to show much, much more user friendly feedback before it hands control off to your program. 
 
+Before passing the user's inputs to your program, Gooey can optionally run a special pre-flight validation to check that all arguments pass your specified validations.  
 
-**Writing a validator:**
+**How does it work?**   
 
-Validators are specified as part of the `gooey_options` map available to `GooeyParser`. It's a simple map structure made up of a root key named `validator` and two internal pairs: 
+Gooey piggy backs on the `type` parameter available to most Argparse Argument types. 
 
- * `test` The inner body of the validation test you wish to perform 
- * `message` the error message that should display given a validation failure
- 
-e.g.
+```python
+parser.add_argument('--some-number', type=int)
+parser.add_argument('--some-number', type=float)
+```
+
+In addition to simple builtins like `int` and `float`, you can supply your own function to the `type` parameter to vet the incoming values. 
 
+```python
+def must_be_exactly_ten(value): 
+    number = int(value) 
+    if number == 10:
+        return number
+    else: 
+        raise TypeError("Hey! you need to provide exactly the number 10!")
+        
+        
+def main(): 
+    parser = ArgumentParser()
+    parser.add_argument('--ten', type=must_be_exactly_ten)
 ```
-gooey_options={
-    'validator':{
-        'test': 'len(user_input) > 3',
-        'message': 'some helpful message'
-    }
-}
+
+**How to enable the pre-flight validation**
+
+By default, Gooey won't run the validation. Why? This feature is fairly experimental and does a lot of intense Monkey Patching behind the scenes. As such, it's currently opt-in. 
+
+You enable to validation by telling Gooey you'd like to subscribe to the `VALIDATE_FORM` event. 
+
+```python
+from gooey import Gooey, Events 
+
+@Gooey(use_events=[Events.VALIDATE_FORM])
+def main(): 
+    ... 
 ```
 
-**The `test` function**
 
-Your test function can be made up of any valid Python expression. It receives the variable `user_input` as an argument against which to perform its validation. Note that all values coming from Gooey are in the form of a string, so you'll have to cast as needed in order to perform your validation.   
+<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/dynamic-validation-1-2-0.JPG" />
+
+Now, when you run Gooey, before it invokes your main program, it'll send a separate pre-validation check and record any issues raised from your `type` functions.  
+
 
 **Full Code Example**
 
 ```
-from gooey.python_bindings.gooey_decorator import Gooey
-from gooey.python_bindings.gooey_parser import GooeyParser
+from gooey import Gooey, Events
+from argparse import ArgumentParser
 
-@Gooey
-def main():
-    parser = GooeyParser(description='Example validator')
-    parser.add_argument(
-        'secret',
-        metavar='Super Secret Number',
-        help='A number specifically between 2 and 14',
-        gooey_options={
-            'validator': {
-                'test': '2 <= int(user_input) <= 14',
-                'message': 'Must be between 2 and 14'
-            }
-        })
+def must_be_exactly_ten(value):
+    number = int(value)
+    if number == 10:
+        return number
+    else:
+        raise TypeError("Hey! you need to provide exactly the number 10!")
 
+@Gooey(program_name='Validation Example', use_events=[Events.VALIDATE_FORM])
+def main():
+    parser = ArgumentParser(description="Checkout this validation!")
+    parser.add_argument('--ten', metavar='This field should be 10', type=must_be_exactly_ten)
     args = parser.parse_args()
-
-    print("Cool! Your secret number is: ", args.secret)
+    print(args)
 ```
 
-<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34465024-f011ac3e-ee4f-11e7-80ae-330adb4c47d6.png" width="400" height="auto" align="left" />
-
-With the validator in place, Gooey can present the error messages next to the relevant input field if any validators fail.  
 
 
 
 ---------------------------------------
   
 
-## Using Dynamic Values
+## Lifecycle Events and UI control
 
 >:warning: 
 >Note! This functionality is experimental. Its API may be changed or removed altogether. Feedback on this feature is welcome and encouraged! 
 
-Gooey's Choice style fields (Dropdown, Listbox) can be fed a dynamic set of values at runtime by enabling the `poll_external_updates` option. This will cause Gooey to request updated values from your program every time the user visits the Configuration page. This can be used to, for instance, show the result of a previous execution on the config screen without requiring that the user restart the program. 
+As of 1.2.0, Gooey now exposes coarse grain lifecycle hooks to your program. This means you can now take additional follow-up actions in response to successful runs or failures and even control the current state of the UI itself! 
 
-**How does it work?**
+Currently, two primary hooks are exposed: 
 
-<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/35487459-bd7fe938-0430-11e8-9f6d-fa8f703b9da5.gif" align="right" width="420"/>
+* `on_success`
+* `on_error`
 
-At runtime, whenever the user hits the Configuration screen, Gooey will call your program with a single CLI argument: `gooey-seed-ui`. This is a request to your program for updated values for the UI. In response to this, on `stdout`, your program should return a JSON string mapping cli-inputs to a list of options.
+These fire exactly when you'd expect: after your process has completed. 
 
-For example, assuming a setup where you have a dropdown that lists user files:
 
-```
- ...
- parser.add_argument(
-        '--load',
-        metavar='Load Previous Save',
-        help='Load a Previous save file',
-        dest='filename',
-        widget='Dropdown',
-        choices=list_savefiles(),
-    )
-```
+**Anatomy of an lifecycle handler**:
 
-Here the input we want to populate is `--load`. So, in response to the `gooey-seed-ui` request, you would return a JSON string with `--load` as the key, and a list of strings that you'd like to display to the user as the value. e.g.  
+Both `on_success` and `on_error` have the same type signature. 
 
+```python
+from typing import Mapping, Any, Optional
+from gooey.types import PublicGooeyState  
+
+def on_success(args: Mapping[str, Any], state: PublicGooeyState) -> Optional[PublicGooeyState]:
+    """
+    You can do anything you want in the handler including 
+    returning an updated UI state for your next run!   
+    """ 
+    return state
+    
+def on_error(args: Mapping[str, Any], state: PublicGooeyState) -> Optional[PublicGooeyState]:
+    """
+    You can do anything you want in the handler including 
+    returning an updated UI state for your next run!   
+    """ 
+    return state    
 ```
-{"--load": ["Filename_1.txt", "filename_2.txt", ..., "filename_n.txt]}
+
+* **args** This is the parsed Argparse object (e.g. the output of `parse_args()`). This will be a mapping of the user's arguments as existed when your program was invoked.
+* **state** This is the current state of Gooey's UI. If your program uses subparsers, this currently just lists the state of the active parser/form. Whatever updated version of this state you return will be reflected in the UI!    
+
+
+**Attaching the handlers:**
+
+Handlers are attached when instantiating the `GooeyParser`.
+
+```python
+parser = GooeyParser(
+    on_success=my_success_handler,
+    on_failure=my_failure_handler)
+``` 
+
+
+**Subscribing to the lifecycle events**
+
+Just like [Validation](#dynamic-validation), these lifecycle events are opt-in. Pass the event you'd like to subscribe to into the `use_events` Gooey decorator argument. 
+
+```python
+from gooey import Gooey, Events 
+
+@Gooey(use_events=[Events.ON_SUCCESS, Events.ON_ERROR])
+def main(): 
+    ... 
 ```
 
-Checkout the full example code in the [Examples Repository](https://github.com/chriskiehl/GooeyExamples/blob/master/examples/dynamic_updates.py). Or checkout a larger example in the silly little tool that spawned this feature: [SavingOverIt](https://github.com/chriskiehl/SavingOverIt). 
+
 
 -------------------------------------
 
@@ -710,9 +796,6 @@ Gooey also supports tracking elapsed / remaining time when progress is used! Thi
     })
 ```
 
-
-![Elapsed/Remaining Time](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/gooey-estimated-finish.gif)
-
 --------------------------------------
 
 
@@ -736,11 +819,11 @@ Images are discovered by Gooey based on their _filenames_. So, for example, in o
 
 ## Packaging
 
-Thanks to some [awesome contributers](https://github.com/chriskiehl/Gooey/issues/58), packaging Gooey as an executable is super easy. 
+Thanks to some [awesome contributors](https://github.com/chriskiehl/Gooey/issues/58), packaging Gooey as an executable is super easy. 
 
-The tl;dr [pyinstaller](https://github.com/pyinstaller/pyinstaller) version is to drop this [build.spec](https://github.com/chriskiehl/Gooey/files/29568/build.spec.txt) into the root directory of your application. Edit its contents so that the `application` and `name` are relevant to your project, then execute `pyinstaller build.spec` to bundle your app into a ready-to-go executable. 
+The tl;dr [pyinstaller](https://github.com/pyinstaller/pyinstaller) version is to drop this [build.spec](https://raw.githubusercontent.com/chriskiehl/Gooey/master/docs/packaging/build-win.spec) into the root directory of your application. Edit its contents so that the `APPPNAME` and `name` are relevant to your project and the `pathex` value points to your applications root, then execute `pyinstaller -F --windowed build.spec` to bundle your app into a ready-to-go executable. 
 
-Detailed step by step instructions can be found [here](http://chriskiehl.com/article/packaging-gooey-with-pyinstaller/). 
+Detailed step by step instructions can be found [here](https://github.com/chriskiehl/Gooey/blob/master/docs/packaging/Packaging-Gooey.md). 
 
 
 Screenshots
diff --git a/docs/Gooey-Options.md b/docs/Gooey-Options.md
index e46d0317..deafef0d 100644
--- a/docs/Gooey-Options.md
+++ b/docs/Gooey-Options.md
@@ -22,7 +22,8 @@ and with that, you're ready to rock.
 
 ## Overview
 
-* Global Style Options 
+* Global Style/Layout Options 
+* Global Config Options 
 * Custom Widget Options
     * Textarea
     * BlockCheckbox  
@@ -31,7 +32,7 @@ and with that, you're ready to rock.
 * Argument Group Options  
 
 
-## Global Widget Styles    
+## Global Style / Layout Options     
 
 All widgets in Gooey (with the exception of RadioGroups) are made up of three basic components. 
 
@@ -72,6 +73,17 @@ parser.add_argument('-my-arg', gooey_options={
 | full_width | bool | This is a layout hint for this widget. When `True` the widget will fill the entire available space within a given row. Otherwise, it will be sized based on the column rules provided elsewhere. | 
 
 
+## Global Config Options 
+
+> new in 1.0.8
+
+All widgets in Gooey accept an `initial_value` option to seed the UI. 
+
+```python
+parser.add_argument('-my-arg', widget='Textarea', gooey_options={
+    'initial_value': 'Hello world!'  
+})
+```
 
 ## Individual Widget Options
 
@@ -88,6 +100,41 @@ parser.add_argument('-my-arg', widget='Textarea', gooey_options={
 })
 ``` 
 
+### IntegerField
+
+```python
+parser.add_argument('-my-arg', widget='IntegerField', gooey_options={
+    'min': int, 
+    'max': int, 
+    'increment': int  
+})
+``` 
+
+
+### DecimalField
+
+```python
+parser.add_argument('-my-arg', widget='IntegerField', gooey_options={
+    'min': float, 
+    'max': float, 
+    'increment': float,
+    'precision': int  # 0 - 20
+})
+``` 
+
+### Slider
+
+The Slider is just a reskinned IntegerField, so it has the same options
+ 
+```python
+parser.add_argument('-my-arg', widget='Slider', gooey_options={
+    'min': int, 
+    'max': int, 
+    'increment': int  
+})
+``` 
+
+
 ### BlockCheckbox
 
 ```python
@@ -198,4 +245,4 @@ parser.add_argument("MultiFileSaver", widget="MultiFileChooser",
 | default_path | string | The default path |
 
 
-  
\ No newline at end of file
+  
diff --git a/docs/Gracefully-Stopping.md b/docs/Gracefully-Stopping.md
new file mode 100644
index 00000000..eff12ffe
--- /dev/null
+++ b/docs/Gracefully-Stopping.md
@@ -0,0 +1,76 @@
+# Gracefully Stopping a Running Process
+
+>New in v1.0.9!
+
+<p align="center">
+  <img src="https://github.com/chriskiehl/GooeyImages/raw/images/docs/graceful-stopping/screenshot.PNG"/>
+</p>
+
+**Contents:**
+
+* [How to tell Gooey which shutdown signal to use](#how-to-tell-gooey-which-signal-to-use)
+* [How to catch KeyboardInterrupts](#How-to-catch-KeyboardInterrupts)
+* [How to catch general interrupt signals](#How-to-catch-general-interrupt-signals)
+
+By default, Gooey will kill the child process without any chance for cleanup. This guide will explain how to adjust that behavior so that you can detect when Gooey is attempting to close your process and use that signal to shutdown gracefully.   
+
+### Basics: How to tell Gooey which shutdown signal to use: 
+
+You can control the signal Gooey sends while stopping your process via `shutdown_signal` decorator argument. Signal values come from the builtin `signal` python module. On linux, any of the available constants may be used as a value. However, on Windows, only `CTRL_BREAK_EVENT`, `CTRL_C_EVENT` and `SIGTERM` are supported by the OS.   
+ 
+ 
+```python
+import signal 
+@Gooey(shutdown_signal=signal.CTRL_C_EVENT)
+def main():
+    ...
+```
+
+
+### How to catch KeyboardInterrupts:
+
+Keyboard interrupts are triggered in response to the `CTRL_C_EVENT` signal.
+
+```python
+import signal 
+@Gooey(shutdown_signal=signal.CTRL_C_EVENT)
+def main():
+    ...
+``` 
+
+Catching them in your code is really easy! They conveniently show up as top-level Exceptions. Just wrap your main logic in a try/except and you'll be able to catch when Gooey tries to shut down your process.   
+
+```python
+try
+   # your code here
+except KeyboardInterrupt: 
+   # cleanup and shutdown or ignore 
+``` 
+
+### How to catch general interrupt signals
+
+Handling other signals is only slightly more involved than the `CTRL_C_EVENT` one. You need to install a handler via the `signal` module and tie it to the specific signal you want to handle. Let's use the `CTRL_BREAK_EVENT` signal as example. 
+
+```python
+import signal
+
+# (1)
+def handler(*args): 
+    print("I am called in response to an external signal!")
+    raise Exception("Kaboom!")
+
+# (2) 
+signal.signal(signal.SIGBREAK, handler)
+
+# (3)
+@Gooey(shutdown_signal=signal.CTRL_BREAK_EVENT)
+def main():
+    # your code here 
+    # ... 
+```    
+
+Here we setup a handler called `handler` (1). This function can do anything you want in response to the signal including ignoring it entirely. Next we tie the signal we're interested in to the handler (2). Finally, we tell Gooey to send the `BREAK` signal(3) when the stop button is clicked. 
+
+> Note: pay close attention to the different constants used while specifying a handler (e.g. `SIGBREAK`) versus specifying which signal will be sent (e.g. `CTRL_BREAK_SIGNAL`).   
+
+
diff --git a/docs/packaging/Packaging-Gooey.md b/docs/packaging/Packaging-Gooey.md
index 5b5b8053..ed03334b 100644
--- a/docs/packaging/Packaging-Gooey.md
+++ b/docs/packaging/Packaging-Gooey.md
@@ -75,7 +75,7 @@ The `Analysis` section is where you'll tell PyInstaller about your program. Usin
 
 Next is `EXE`. In this section you'll replace the `name` argument with what you'd like the final `.exe` to be named.
 
->Note: if you're providing your own icon file, EXE is where you'll provide it.   
+>Note: if you're providing your own icon file, EXE is where you'll provide it. If you're on Windows, you must provide an .ico file (not PNG).
 
 If you're on OSX, you'll have an additional `BUNDLE` section. You'll need to make one final edit here as well to control the name of the `.app` bundle that PyInstaller produces. Additionally, if you're customizing the bundle's icon, this is where you would supply the override (versus Windows, which places it in the EXE section). 
 
diff --git a/docs/releases/1.0.6-release-notes.md b/docs/releases/1.0.6-release-notes.md
new file mode 100644
index 00000000..d57a2a27
--- /dev/null
+++ b/docs/releases/1.0.6-release-notes.md
@@ -0,0 +1,51 @@
+## Gooey 1.0.6 Released! 
+
+
+This is a minor release beefing up the new FilterableDropdown's search capabilities and performance. In the previous release, the dropdown was backed by WX's `ListBox` widget. 1.0.6 replaces this for a fully virtualized version which allows Gooey to operate on massive datasets without taking a hit to UI performance. Additionally, how Gooey internally filters for matches has also been updated. Choice are now backed by a trie for super fast lookup even against large data sets. Tokenization and match strategies can be customized to support just about any lookup style.      
+
+Head over to the [Examples Repo](https://github.com/chriskiehl/GooeyExamples) to see the updated demo which now uses a dataset consisting of about 25k unique items.  
+
+
+**New Gooey Options:**
+
+`FilterableDropdown` now takes a `search_strategy` in its `gooey_options`.   
+
+```python
+from gooey import Gooey, GooeyParser, PrefixTokenizers
+
+gooey_options={
+    'label_color': (255, 100, 100),
+    'placeholder': 'Start typing to view suggestions',
+    'search_strategy': {
+        'type': 'PrefixFilter',
+        'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE,
+        'input_tokenizer': PrefixTokenizers.REGEX('\s'),
+        'ignore_case': True,
+        'operator': 'AND',
+        'index_suffix': False
+    }
+})
+```
+
+This gives control over how the choices and user input get tokenized, as well as how those tokenized matches get treated (ANDed together vs ORd). Want to match on any part of any word? Enable the `index_suffix` option to index all of your candidate words by their individual parts. e.g. 
+
+```
+Word: 'Banana' 
+Suffixes: ['Banana', 'anana', 'nana', 'ana']
+```
+
+These all get loaded into a trie for super fast lookup. Combine this with the `WORDs` tokenizer, and you get really fine grained search though your options! 
+
+
+## Thank you to the current Patreon supporters! 
+
+* Qteal
+* Joseph Rhodes
+
+
+## Breaking Changes 
+
+No breaking changes from 1.0.5.
+
+ 
+
diff --git a/docs/releases/1.0.7-release-notes.md b/docs/releases/1.0.7-release-notes.md
new file mode 100644
index 00000000..fc8639cb
--- /dev/null
+++ b/docs/releases/1.0.7-release-notes.md
@@ -0,0 +1,145 @@
+## Gooey 1.0.7 Released! 
+
+Lots of new stuff this release! We've got 3 new widget types, new gooey_options, as well as some quality of Life improvements for using Gooey Options. 
+
+
+### New Widgets: IntegerField, DecimalField, and Slider
+
+<p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.7/numeric-inputs.gif" ></p>
+
+
+Gooey now has 3 inputs specifically geared towards accepting numeric inputs. Previously, all Gooey had were text fields which you could add `validators` to in order to enforce only numbers were entered, but now we have top level widgets which do all of that out of the box! 
+  
+**Important Usage Note:** since these numeric inputs don't allow any non-numeric characters to be entered, they do **not** give you the ability to blank them out. Unlike a `TextField` which can be left empty and thus have its value not passed to your program, the numeric inputs will always send a value. Thus, you have to have sane handling in user-land.   
+
+Checkout the [Options docs](https://github.com/chriskiehl/Gooey/blob/master/docs/Gooey-Options.md) for more details. 
+
+ 
+### New Gooey Options: placeholder 
+
+<p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.7/placeholders.gif" ></p>
+
+Widgets with text inputs now all accept a `placeholder` Gooey option. 
+
+```python
+add_argument('--foo', widget='TextField', gooey_options=options.TextField(
+    placeholder='Type some text here!'
+)
+
+# or without the options helper 
+add_argument('--foo', widget='TextField', gooey_options={
+    'placeholder': 'Type some text here!'
+})
+```
+
+
+### New Validator option: RegexValidator
+
+```python
+add_argument('--foo', widget='TextField', gooey_options=options.TextField(
+    placeholder='Type some text here!',
+    validator=options.RegexValidator(
+        test='\d{4}',
+        message='Must be exactly 4 digits long!'
+    )
+)
+
+# or without the options helper 
+add_argument('--foo', widget='TextField', gooey_options={
+    'placeholder': 'Type some text here!',
+    'validator': {
+        'type': 'RegexValidator',
+        'test': '\d{4}',
+        'message': 'Must be exactly 4 digits long!'
+    }
+})
+```
+
+ 
+### New feature: Options helpers 
+
+Gooey now has a top-level `options` module which can be imported. Previously, Gooey Options have been an opaque map. While great for openness / extensibility, it's pretty terrible from a discoverability / "what does this actually take again..?" perspective. The new `options` module aims to make using `gooey_options` easier and more discoverable. 
+
+```python
+from gooey import options
+```
+
+The goal is to enable IDE's to provide better auto-completion help as well as more REPL driven usefulness via help() and docstrings. 
+
+```python
+from gooey import options
+
+parser.add_argument(
+    '--foo', 
+    help='Some foo thing',
+    widget='FilterableDropdown',
+    gooey_options=options.FilterableDropdown(
+        placeholder='Search for a Foo',
+        search_strategy=options.PrefixSearchStrategy(
+            ignore_case=True 
+        )
+    ))
+``` 
+
+Note that these are _just_ helpers for generating the right data shapes. They're still generating plain data behind the scenes and thus all existing `gooey_options` code remains 100% compatible. 
+
+**Better Docs:**
+
+Which is to say, documentation which actually exists rather than _not_ exist. You can inspect the docs live in the REPL or by hopping to the symbol in editors which support such things. 
+
+```
+>>> from gooey import options 
+>>> help(options.RadioGroup) 
+Help on function FileChooser in module __main__:
+
+FileChooser(wildcard=None, default_dir=None, default_file=None, message=None, **layout_options)
+    :param wildcard: Sets the wildcard, which can contain multiple file types, for 
+                     example: "BMP files (.bmp)|.bmp|GIF files (.gif)|.gif"
+    :param message:  Sets the message that will be displayed on the dialog.
+    :param default_dir: The default directory selected when the dialog spawns 
+    :param default_file: The default filename used in the dialog
+    
+    Layout Options:
+    ---------------
+    
+    Color options can be passed either as a hex string ('#ff0000') or as
+    a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`)
+    
+    :param label_color:    The foreground color of the label text
+    :param label_bg_color: The background color of the label text.
+    :param help_color:     The foreground color of the help text.
+    :param help_bg_color:  The background color of the help text.
+    :param error_color:    The foreground color of the error text (when visible).
+    :param error_bg_color: The background color of the error text (when visible).
+    :param show_label:     Toggles whether or not to display the label text
+    :param show_help:      Toggles whether or not to display the help text
+    :param visible:        Hides the entire widget when False. Note: the widget
+                           is still present in the UI and will still send along any
+                           default values that have been provided in code. This option
+                           is here for when you want to hide certain advanced / dangerous
+                           inputs from your GUI users.
+    :param full_width:     This is a layout hint for this widget. When True the widget
+                           will fill the entire available space within a given row.
+                           Otherwise, it will be sized based on the column rules
+                           provided elsewhere. 
+```
+
+Ideally, and eventually, we'll be able to completely type these options to increase visibility / usability even more. However, for backwards compatibility reasons, Gooey will continue to be sans anything more than the most basic of type hinting for the time being.
+
+
+## Breaking Changes 
+
+**No breaking API changes from 1.0.6 to 1.0.7.** However, the _strictness_ of existing Gooey Options has been increased, which _could_ result in issues when upgrading from 1.0.6. In an attempt to be helpful, Gooey now throws an exception if invalid Gooey Options are supplied. This is to catch things like invalid types or ill-formed data. If you were passing bad data in 1.0.6, it will now be flagged as such in 1.0.7.   
+
+
+## Thank you to the current [Patreon supporters](https://www.patreon.com/chriskiehl)! 
+
+* Sponsors: 
+    * Qteal
+* Individuals: 
+    * Joseph Rhodes
+    * Nicholas 
+    
+
+    
+    
\ No newline at end of file
diff --git a/docs/releases/1.0.8-release-notes.md b/docs/releases/1.0.8-release-notes.md
new file mode 100644
index 00000000..66c01903
--- /dev/null
+++ b/docs/releases/1.0.8-release-notes.md
@@ -0,0 +1,58 @@
+## Gooey 1.0.8 Released! 
+
+
+Another minor Gooey release! This one brings a new global Gooey Option for setting initial values in the UI, support for `version` action types, plus a few bug/linting fixes.
+
+Additionally, I continue to plug away at getting the test coverage to useful levels. We're now pushing 80% coverage which is making working on Gooey with confidence much easier!   
+
+
+### New Gooey Options: initial_value 
+
+This option lets you specify the value present in the widget when Gooey starts. 
+
+```python
+parser.add_argument('-my-arg', widget='Textarea', gooey_options={
+    'initial_value': 'Hello world!'  
+})
+```
+
+Or, using the new `options` helpers: 
+
+```python
+from gooey import options 
+parser.add_argument('-my-arg', widget='Textarea', gooey_options=options.Textarea(
+    initial_value='Hello World!'
+))
+```
+
+If you've been using Gooey awhile, you'll recognize that this overlaps with the current behavior of `default`. The new `initial_value` enables you to supply a truly optional seed value to the UI. When using `default`, even if the user clears your value out of the UI, argparse will add it back in when it parses the CLI string. While this is often useful behavior, it prevents certain workflows from being possible. `initial_value` let's you control the UI independent of argparse. This means you can now, for instance, set a checkbox to be checked by default in the UI, but optionally allow the user to deselect it without having argprase re-populate the 'checked' state (a behavior which comes up frequently in the issue tracker due to it being technically correct, but also very confusing!). 
+
+
+### action=version support 
+
+When using `action='version'` Gooey will now map it a CheckBox widget type. 
+
+
+### Other Fixes / Changes: 
+
+ * Bug fix: add missing translation step for tabbed group titles (@neonbunny)
+ * Linting: swap `is not` for `!=` (@DrStrinky) 
+
+
+## Breaking Changes 
+
+**No breaking API changes from 1.0.7 to 1.0.8**   
+
+
+## Thank you to the current [Patreon supporters](https://www.patreon.com/chriskiehl)! 
+
+* Sponsors: 
+    * Qteal
+* Individuals: 
+    * Joseph Rhodes
+    * Nicholas 
+    * Joey
+    
+
+    
+    
\ No newline at end of file
diff --git a/docs/releases/1.0.8.1-release-notes.md b/docs/releases/1.0.8.1-release-notes.md
new file mode 100644
index 00000000..b632b331
--- /dev/null
+++ b/docs/releases/1.0.8.1-release-notes.md
@@ -0,0 +1,6 @@
+## Gooey 1.0.8.1 Released! 
+
+
+This is a tiny intermediate release which just loosen Gooey's WxPython dependency from `=4.1.0` to `>=4.1.0` in `setup.py`. The strict version requirement was causing numerous installation issues across environments.
+
+  
\ No newline at end of file
diff --git a/docs/releases/1.2.0-ALPHA-release-notes.md b/docs/releases/1.2.0-ALPHA-release-notes.md
new file mode 100644
index 00000000..5751498c
--- /dev/null
+++ b/docs/releases/1.2.0-ALPHA-release-notes.md
@@ -0,0 +1,26 @@
+# Gooey 1.2.0-ALPHA Released! 
+
+### Warning: 
+
+>**Upgrade with caution!** 1.2.0 removes the experimental Dynamic Updates feature and replaces it with a _new_ experimental Dynamic Updates feature! The two APIs are incompatible.    
+
+This release brings a whole host of new features to Gooey. Chief among them are the new Dynamic Updates and Validation functionality. This was effectively a rebuild of a substantial portion of Gooey's internal to enable a more client/server style functionality. This means that you have more control over the gooey's lifecycle, and can subscribe to high level events. Currently, FormSubmit, OnComplete, and OnError are supported, but more are on their way! Soon you'll be able to have fine grained control over the UI and its presentation, and still without having to write a single line of traditional GUI code! 
+
+
+### Breaking Changes (1.0.8 -> 1.2.0) 
+
+ * **Validation** - the validation mechanism available via gooey_options has been removed entirely in favor of the new API.   
+ * **Dynamic Updates** - there was previously minimal support for loading new data at run time. This has been revomed in favor of a new system which gives advanced control over the state of the UI. 
+
+### New Features
+
+* **Dynamic Updates and Validation** - Checkout the [README](https://github.com/chriskiehl/Gooey/blob/master/README.md) for details on how to get started. This feature is really hairy behind the scenes and involves all kinds of crazy monkey patching in order to work. Odds of encountering a bug or scenario that doesn't work for your use case is high in this initial release. Please fill out an issue if any problems pop up! Checkout [the examples repo](https://github.com/chriskiehl/GooeyExamples/blob/master/examples/lifecycle_hooks.py) to see the new lifecycle hooks in action. 
+* **Graceful Shutdown control** - Gooey previously would `SIGTERM` your application when you tried to halt it while running. However, with 1.2.0, you have control over which signal Gooey sends when you request a shutdown. This gives you a chance to catch that signal and clean up and resources currently un use before shutting down.
+* **Better sys.argv handling** - Gooey no longer mutates the global sys.argv variable. This caused people all kinds of problems -- most frequent being Gooey spawning multiple windows. This is now removed, and hopefully all the pain stemming from it as well. 
+     
+
+
+ 
+  
+  
+ 
\ No newline at end of file
diff --git a/docs/releases/release-checklist.md b/docs/releases/release-checklist.md
index e5fc1620..f3929163 100644
--- a/docs/releases/release-checklist.md
+++ b/docs/releases/release-checklist.md
@@ -14,6 +14,7 @@
  - [ ] all debug prints removed  
  - [ ] setup.py version is updated 
  - [ ] __init__.py version is updated
+ - [ ] types check (for the most part) `./venv/Scripts/python.exe -m mypy /path/to/python_bindings/types.py`
  - [ ] pip install of release branch works.   
      - [ ] All Gooey Examples run and work as expected
  - [ ] pypi is updated 
diff --git a/gooey/__init__.py b/gooey/__init__.py
index 67eaf47d..77758af3 100644
--- a/gooey/__init__.py
+++ b/gooey/__init__.py
@@ -3,4 +3,9 @@
 from gooey.python_bindings.gooey_parser import GooeyParser
 from gooey.gui.util.freeze import localResourcePath as local_resource_path
 from gooey.python_bindings import constants
-__version__ = '1.0.5'
+from gooey.python_bindings.constants import Events
+from gooey.gui.components.filtering.prefix_filter import PrefixTokenizers
+from gooey.gui.components.options import options
+from gooey.python_bindings import types
+types = types
+__version__ = '1.2.0-ALPHA'
diff --git a/gooey/gui/application/__init__.py b/gooey/gui/application/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/gui/application/application.py b/gooey/gui/application/application.py
new file mode 100644
index 00000000..77254048
--- /dev/null
+++ b/gooey/gui/application/application.py
@@ -0,0 +1,360 @@
+import sys
+from json import JSONDecodeError
+
+import six
+import wx  # type: ignore
+
+from gooey import Events
+from gooey.gui import events
+from gooey.gui import host
+from gooey.gui import state as s
+from gooey.gui.application.components import RHeader, ProgressSpinner, ErrorWarning, RTabbedLayout, \
+    RSidebar, RFooter
+from gooey.gui.components import modals
+from gooey.gui.components.config import ConfigPage
+from gooey.gui.components.config import TabbedConfigPage
+from gooey.gui.components.console import Console
+from gooey.gui.components.menubar import MenuBar
+from gooey.gui.lang.i18n import _
+from gooey.gui.processor import ProcessController
+from gooey.gui.pubsub import pub
+from gooey.gui.state import FullGooeyState
+from gooey.gui.state import initial_state, ProgressEvent, TimingEvent
+from gooey.gui.util.wx_util import transactUI, callafter
+from gooey.python_bindings import constants
+from gooey.python_bindings.dynamics import unexpected_exit_explanations, \
+    deserialize_failure_explanations
+from gooey.python_bindings.types import PublicGooeyState
+from gooey.python_bindings.types import Try
+from gooey.util.functional import assoc
+from gooey.gui.util.time import Timing
+from rewx import components as c  # type: ignore
+from rewx import wsx  # type: ignore
+from rewx.core import Component, Ref  # type: ignore
+
+
+class RGooey(Component):
+    """
+    Main Application container for Gooey.
+
+    State Management
+    ----------------
+
+    Pending further refactor, state is tracked in two places:
+    1. On this instance (React style)
+    2. In the WX Form Elements themselves[0]
+
+    As needed, these two states are merged to form the `FullGooeyState`, which
+    is the canonical state object against which all logic runs.
+
+
+    Dynamic Updates
+    ---------------
+
+
+
+
+    [0] this is legacy and will (eventually) be refactored away
+
+    """
+    def __init__(self, props):
+        super().__init__(props)
+        self.frameRef = Ref()
+        self.consoleRef = Ref()
+        self.configRef = Ref()
+
+        self.buildSpec = props
+        self.state = initial_state(props)
+        self.headerprops = lambda state: {
+            'background_color': self.buildSpec['header_bg_color'],
+            'title': state['title'],
+            'show_title': state['header_show_title'],
+            'subtitle': state['subtitle'],
+            'show_subtitle': state['header_show_subtitle'],
+            'flag': wx.EXPAND,
+            'height': self.buildSpec['header_height'],
+            'image_uri': state['image'],
+            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)}
+
+        self.fprops = lambda state: {
+            'buttons': state['buttons'],
+            'progress': state['progress'],
+            'timing': state['timing'],
+            'bg_color': self.buildSpec['footer_bg_color'],
+            'flag': wx.EXPAND,
+        }
+        self.clientRunner = ProcessController.of(self.buildSpec)
+        self.timer = None
+
+
+    def component_did_mount(self):
+        pub.subscribe(events.WINDOW_START, self.onStart)
+        pub.subscribe(events.WINDOW_RESTART, self.onStart)
+        pub.subscribe(events.WINDOW_STOP, self.handleInterrupt)
+        pub.subscribe(events.WINDOW_CLOSE, self.handleClose)
+        pub.subscribe(events.WINDOW_CANCEL, self.handleCancel)
+        pub.subscribe(events.WINDOW_EDIT, self.handleEdit)
+        pub.subscribe(events.CONSOLE_UPDATE, self.consoleRef.instance.logOutput)
+        pub.subscribe(events.EXECUTION_COMPLETE, self.handleComplete)
+        pub.subscribe(events.PROGRESS_UPDATE, self.updateProgressBar)
+        pub.subscribe(events.TIME_UPDATE, self.updateTime)
+        # # Top level wx close event
+        frame: wx.Frame = self.frameRef.instance
+        frame.Bind(wx.EVT_CLOSE, self.handleClose)
+        frame.SetMenuBar(MenuBar(self.buildSpec))
+        self.timer = Timing(frame)
+
+        if self.state['fullscreen']:
+            frame.ShowFullScreen(True)
+
+        if self.state['show_preview_warning'] and not 'unittest' in sys.modules.keys():
+            wx.MessageDialog(None, caption='YOU CAN DISABLE THIS MESSAGE',
+                             message="""
+                This is a preview build of 1.2.0! There may be instability or 
+                broken functionality. If you encounter any issues, please open an issue 
+                here: https://github.com/chriskiehl/Gooey/issues 
+                
+                The current stable version is 1.0.8. 
+                
+                NOTE! You can disable this message by setting `show_preview_warning` to False. 
+                
+                e.g. 
+                `@Gooey(show_preview_warning=False)`
+                """).ShowModal()
+
+    def getActiveConfig(self):
+        return [item
+                for child in self.configRef.instance.Children
+                # we descend down another level of children  to account
+                # for Notebook layouts (which have wrapper objects)
+                for item in [child] + list(child.Children)
+                if isinstance(item, ConfigPage)
+                or isinstance(item, TabbedConfigPage)][self.state['activeSelection']]
+
+    def getActiveFormState(self):
+        """
+        This boiler-plate and manual interrogation of the UIs
+        state is required until we finish porting the Config Form
+        over to rewx (which is a battle left for another day given
+        its complexity)
+        """
+        return self.getActiveConfig().getFormState()
+
+
+    def fullState(self):
+        """
+        Re: final porting is a to do. For now we merge the UI
+        state into the main tracked state.
+        """
+        formState = self.getActiveFormState()
+        return s.combine(self.state, self.props, formState)
+
+
+    def onStart(self, *args, **kwargs):
+        """
+        Dispatches the start behavior.
+        """
+        if Events.VALIDATE_FORM in self.state['use_events']:
+            self.runAsyncValidation()
+        else:
+            self.startRun()
+
+
+    def startRun(self):
+        """
+        Kicks off a run by invoking the host's code
+        and pumping its stdout to Gooey's Console window.
+        """
+        state = self.fullState()
+        if state['clear_before_run']:
+            self.consoleRef.instance.Clear()
+        self.set_state(s.consoleScreen(_, state))
+        self.clientRunner.run(s.buildInvocationCmd(state))
+        self.timer.start()
+        self.frameRef.instance.Layout()
+        for child in self.frameRef.instance.Children:
+            child.Layout()
+
+
+    def syncExternalState(self, state: FullGooeyState):
+        """
+        Sync the UI's state to what the host program has requested.
+        """
+        self.getActiveConfig().syncFormState(s.activeFormState(state))
+        self.frameRef.instance.Layout()
+        for child in self.frameRef.instance.Children:
+            child.Layout()
+
+
+    def handleInterrupt(self, *args, **kwargs):
+        if self.shouldStopExecution():
+            self.clientRunner.stop()
+
+    def handleComplete(self, *args, **kwargs):
+        self.timer.stop()
+        if self.clientRunner.was_success():
+            self.handleSuccessfulRun()
+            if Events.ON_SUCCESS in self.state['use_events']:
+                self.runAsyncExternalOnCompleteHandler(was_success=True)
+        else:
+            self.handleErrantRun()
+            if Events.ON_ERROR in self.state['use_events']:
+                self.runAsyncExternalOnCompleteHandler(was_success=False)
+
+    def handleSuccessfulRun(self):
+        if self.state['return_to_config']:
+            self.set_state(s.editScreen(_, self.state))
+        else:
+            self.set_state(s.successScreen(_, self.state))
+            if self.state['show_success_modal']:
+                wx.CallAfter(modals.showSuccess)
+
+
+    def handleErrantRun(self):
+        if self.clientRunner.wasForcefullyStopped:
+            self.set_state(s.interruptedScreen(_, self.state))
+        else:
+            self.set_state(s.errorScreen(_, self.state))
+            if self.state['show_failure_modal']:
+                wx.CallAfter(modals.showFailure)
+
+
+    def successScreen(self):
+        strings = {'title': _('finished_title'), 'subtitle': _('finished_msg')}
+        self.set_state(s.success(self.state, strings, self.buildSpec))
+
+
+    def handleEdit(self, *args, **kwargs):
+        self.set_state(s.editScreen(_, self.state))
+
+    def handleCancel(self, *args, **kwargs):
+        if modals.confirmExit():
+            self.handleClose()
+
+    def handleClose(self, *args, **kwargs):
+        """Stop any actively running client program, cleanup the top
+        level WxFrame and shutdown the current process"""
+        # issue #592 - we need to run the same onStopExecution machinery
+        # when the exit button is clicked to ensure everything is cleaned
+        # up correctly.
+        frame: wx.Frame = self.frameRef.instance
+        if self.clientRunner.running():
+            if self.shouldStopExecution():
+                self.clientRunner.stop()
+                frame.Destroy()
+                # TODO: NOT exiting here would allow
+                # spawing the gooey to input params then
+                # returning control to the CLI
+                sys.exit()
+        else:
+            frame.Destroy()
+            sys.exit()
+
+    def shouldStopExecution(self):
+        return not self.state['show_stop_warning'] or modals.confirmForceStop()
+
+    def updateProgressBar(self, *args, progress=None):
+        self.set_state(s.updateProgress(self.state, ProgressEvent(progress=progress)))
+
+    def updateTime(self, *args, elapsed_time=None, estimatedRemaining=None, **kwargs):
+        event = TimingEvent(elapsed_time=elapsed_time, estimatedRemaining=estimatedRemaining)
+        self.set_state(s.updateTime(self.state, event))
+
+    def handleSelectAction(self, event):
+        self.set_state(assoc(self.state, 'activeSelection', event.Selection))
+
+
+    def runAsyncValidation(self):
+        def handleHostResponse(hostState: PublicGooeyState):
+            self.set_state(s.finishUpdate(self.state))
+            currentState = self.fullState()
+            self.syncExternalState(s.mergeExternalState(currentState, hostState))
+            if not s.has_errors(self.fullState()):
+                self.startRun()
+            else:
+                self.set_state(s.editScreen(_, s.show_alert(self.fullState())))
+
+        def onComplete(result: Try[PublicGooeyState]):
+            result.onSuccess(handleHostResponse)
+            result.onError(self.handleHostError)
+
+        self.set_state(s.beginUpdate(self.state))
+        fullState = self.fullState()
+        host.communicateFormValidation(fullState, callafter(onComplete))
+
+
+    def runAsyncExternalOnCompleteHandler(self, was_success):
+        def handleHostResponse(hostState):
+            if hostState:
+                self.syncExternalState(s.mergeExternalState(self.fullState(), hostState))
+
+        def onComplete(result: Try[PublicGooeyState]):
+            result.onError(self.handleHostError)
+            result.onSuccess(handleHostResponse)
+
+        if was_success:
+            host.communicateSuccessState(self.fullState(), callafter(onComplete))
+        else:
+            host.communicateErrorState(self.fullState(), callafter(onComplete))
+
+
+    def handleHostError(self, ex):
+        """
+        All async errors get pumped here where we dump out the
+        error and they hopefully provide a lot of helpful debugging info
+        for the user.
+        """
+        try:
+            self.set_state(s.errorScreen(_, self.state))
+            self.consoleRef.instance.appendText(str(ex))
+            self.consoleRef.instance.appendText(str(getattr(ex, 'output', '')))
+            self.consoleRef.instance.appendText(str(getattr(ex, 'stderr', '')))
+            raise ex
+        except JSONDecodeError as e:
+            self.consoleRef.instance.appendText(deserialize_failure_explanations)
+        except Exception as e:
+            self.consoleRef.instance.appendText(unexpected_exit_explanations)
+        finally:
+            self.set_state({**self.state, 'fetchingUpdate': False})
+
+
+    def render(self):
+        return wsx(
+            [c.Frame, {'title': self.buildSpec['program_name'],
+                       'background_color': self.buildSpec['body_bg_color'],
+                       'double_buffered': True,
+                       'min_size': (400, 300),
+                       'icon_uri': self.state['images']['programIcon'],
+                       'size': self.buildSpec['default_size'],
+                       'ref': self.frameRef},
+             [c.Block, {'orient': wx.VERTICAL},
+              [RHeader, self.headerprops(self.state)],
+              [c.StaticLine, {'style': wx.LI_HORIZONTAL, 'flag': wx.EXPAND}],
+              [ProgressSpinner, {'show': self.state['fetchingUpdate']}],
+              [ErrorWarning, {'show': self.state['show_error_alert'],
+                              'uri': self.state['images']['errorIcon']}],
+              [Console, {**self.buildSpec,
+                         'flag': wx.EXPAND,
+                         'proportion': 1,
+                         'show': self.state['screen'] == 'CONSOLE',
+                         'ref': self.consoleRef}],
+              [RTabbedLayout if self.buildSpec['navigation'] == constants.TABBED else RSidebar,
+               {'bg_color': self.buildSpec['sidebar_bg_color'],
+                'label': 'Some Action!',
+                'tabbed_groups': self.buildSpec['tabbed_groups'],
+                'show_sidebar': self.state['show_sidebar'],
+                'ref': self.configRef,
+                'show': self.state['screen'] == 'FORM',
+                'activeSelection': self.state['activeSelection'],
+                'options': list(self.buildSpec['widgets'].keys()),
+                'on_change': self.handleSelectAction,
+                'config': self.buildSpec['widgets'],
+                'flag': wx.EXPAND,
+                'proportion': 1}],
+              [c.StaticLine, {'style': wx.LI_HORIZONTAL, 'flag': wx.EXPAND}],
+              [RFooter, self.fprops(self.state)]]]
+        )
+
+
+
+
diff --git a/gooey/gui/application/components.py b/gooey/gui/application/components.py
new file mode 100644
index 00000000..e74c5bce
--- /dev/null
+++ b/gooey/gui/application/components.py
@@ -0,0 +1,334 @@
+"""
+Houses all the supporting rewx components for
+the main application window.
+"""
+import wx  # type: ignore
+from typing_extensions import TypedDict
+
+from gooey.gui.components.config import ConfigPage, TabbedConfigPage
+from gooey.gui.components.console import Console
+from gooey.gui.components.mouse import notifyMouseEvent
+from gooey.gui.components.sidebar import Sidebar
+from gooey.gui.components.tabbar import Tabbar
+from gooey.gui.lang.i18n import _
+from gooey.gui.pubsub import pub
+from gooey.gui.state import present_time
+from gooey.gui.three_to_four import Constants
+from gooey.python_bindings import constants
+from rewx import components as c  # type: ignore
+from rewx import wsx, mount, update  # type: ignore
+from rewx.core import Component, Ref  # type: ignore
+from rewx.widgets import set_basic_props  # type: ignore
+
+
+def attach_notifier(parent):
+    """
+    Recursively attaches the mouseEvent notifier
+    to all elements in the tree
+    """
+    parent.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
+    for child in parent.Children:
+        attach_notifier(child)
+
+
+class HeaderProps(TypedDict):
+    background_color: str
+    title: str
+    show_title: bool
+    subtitle: str
+    show_subtitle: bool
+
+
+class RHeader(Component):
+    def __init__(self, props):
+        super().__init__(props)
+        self.parentRef = Ref()
+
+    def component_did_mount(self):
+        attach_notifier(self.parentRef.instance)
+
+    def render(self):
+        if 'running' not in self.props['image_uri']:
+            imageProps = {
+                'uri': self.props['image_uri'],
+                'size': self.props['image_size'],
+                'flag': wx.RIGHT,
+                'border': 10}
+        else:
+            imageProps = {
+                'size': self.props['image_size'],
+                'flag': wx.RIGHT,
+                'border': 10}
+        return wsx(
+            [c.Block, {'orient': wx.HORIZONTAL,
+                       'ref': self.parentRef,
+                       'min_size': (120, self.props['height']),
+                       'background_color': self.props['background_color']},
+             [c.Block, {'orient': wx.VERTICAL,
+                        'flag': wx.ALIGN_CENTER_VERTICAL | wx.ALL,
+                        'proportion': 1,
+                        'border': 10},
+              [TitleText, {'label': self.props['title'],
+                           'show': self.props['show_title'],
+                           'wx_name': 'header_title'}],
+              [c.StaticText, {'label': self.props['subtitle'],
+                              'show': self.props['show_subtitle'],
+                              'wx_name': 'header_subtitle'}]],
+             [c.StaticBitmap, imageProps]]
+        )
+
+
+
+class RFooter(Component):
+    def __init__(self, props):
+        super().__init__(props)
+        self.ref = Ref()
+
+    def component_did_mount(self):
+        """
+        We have to manually wire up LEFT_DOWN handlers
+        for every component due to wx limitations.
+        See: mouse.py docs for background.
+        """
+        block: wx.BoxSizer = self.ref.instance
+        attach_notifier(block)
+
+    def handle(self, btn):
+        def inner(*args, **kwargs):
+            pub.send_message(btn['id'])
+        return inner
+
+    def render(self):
+        return wsx(
+            [c.Block, {'orient': wx.VERTICAL,
+                       'min_size': (30, 53),
+                       'background_color': self.props['bg_color']},
+             [c.Block, {'orient': wx.VERTICAL, 'proportion': 1}],
+             [c.Block, {'orient': wx.HORIZONTAL,
+                        'border': 20,
+                        'flag': wx.EXPAND | wx.LEFT | wx.RIGHT,
+                        'ref': self.ref},
+              [c.Gauge, {'range': 100,
+                         'proportion': 1,
+                         'value': self.props['progress']['value'],
+                         'show': self.props['progress']['show']}],
+              [c.StaticText, {'label': present_time(self.props['timing']),
+                              'flag': wx.LEFT,
+                              'wx_name': 'timing',
+                              'show': self.props['timing']['show'],
+                              'border': 20}],
+              [c.Block, {'orient': wx.HORIZONTAL, 'proportion': 1}],
+              *[[c.Button, {**btn,
+                            'label': _(btn['label_id']),
+                            'min_size': (90, 23),
+                            'flag': wx.LEFT,
+                            'border': 10,
+                            'on_click': self.handle(btn)
+                            }]
+                for btn in self.props['buttons']]],
+             [c.Block, {'orient': wx.VERTICAL, 'proportion': 1}]]
+        )
+
+
+class RNavbar(Component):
+    def __init__(self, props):
+        super().__init__(props)
+
+    # if self.buildSpec['navigation'] == constants.TABBED:
+    #     navigation = Tabbar(self, self.buildSpec, self.configs)
+    # else:
+    #     navigation = Sidebar(self, self.buildSpec, self.configs)
+    #     if self.buildSpec['navigation'] == constants.HIDDEN:
+    #         navigation.Hide()
+    def render(self):
+        return wsx(
+
+        )
+
+def VerticalSpacer(props):
+    return wsx([c.Block, {'orient': wx.VERTICAL, 'min_size': (-1, props['height'])}])
+
+def SidebarControls(props):
+    return wsx(
+        [c.Block, {'orient': wx.VERTICAL,
+                   'min_size': (180, 0),
+                   'size': (180, 0),
+                   'show': props.get('show', True),
+                   'flag': wx.EXPAND,
+                   'proportion': 0,
+                   'background_color': props['bg_color']},
+         [c.Block, {'orient': wx.VERTICAL,
+                    'min_size': (180, 0),
+                    'size': (180, 0),
+                    'flag': wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
+                    'border': 10,
+                    'proportion': 1,
+                    'background_color': props['bg_color']},
+          [VerticalSpacer, {'height': 15}],
+          [TitleText, {'label': props['label']}],
+          [VerticalSpacer, {'height': 5}],
+          [c.ListBox, {'choices': props['options'],
+                       'value': props['activeSelection'],
+                       'proportion': 1,
+                       'on_change': props['on_change'],
+                       'flag': wx.EXPAND}],
+          [VerticalSpacer, {'height': 10}]]]
+    )
+
+
+def ProgressSpinner(props):
+    return wsx(
+        [c.Block, {'flag': wx.EXPAND, 'show': props['show']},
+         [c.Gauge, {'flag': wx.EXPAND,
+                    'value': -1,
+                    'size': (-1, 4)}],
+         [c.StaticLine, {'style': wx.LI_HORIZONTAL,
+                         'flag': wx.EXPAND}]]
+    )
+
+
+def ErrorWarning(props):
+    return wsx(
+        [c.Block, {'orient': wx.HORIZONTAL,
+                   'background_color': '#fdeded',
+                   'style': wx.SIMPLE_BORDER,
+                   'flag': wx.EXPAND | wx.ALL,
+                   'proportion': 0,
+                   'border': 5,
+                   'min_size': (-1, 45),
+                   'show': props.get('show', True)},
+         [c.StaticBitmap, {'size': (24, 24),
+                           'flag': wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,
+                           'border': 6,
+                           'uri': props['uri']}],
+         [c.StaticText, {'label': 'Whoops! You have some errors which must be corrected',
+                         'flag': wx.ALIGN_CENTER_VERTICAL}]]
+    )
+
+def RSidebar(props):
+    return wsx(
+        [c.Block, {'orient': wx.HORIZONTAL,
+                   'show': props.get('show', True),
+                   'flag': props['flag'],
+                   'proportion': props['proportion'],
+                   'ref': props['ref']},
+         [SidebarControls, {**props, 'show': props['show_sidebar']}],
+         [c.StaticLine, {'style': wx.LI_VERTICAL,
+                         'flag': wx.EXPAND,
+                         'min_size': (1, -1)}],
+         *[[TabbedConfigPage if props['tabbed_groups'] else ConfigPage,
+            {'flag': wx.EXPAND,
+             'proportion': 3,
+             'config': config,
+             'show': i == props['activeSelection']}]
+           for i, config in enumerate(props['config'].values())]
+         ]
+    )
+
+
+def RTabbedLayout(props):
+    return wsx(
+        [c.Notebook, {'flag': wx.EXPAND | wx.ALL,
+                      'show': props.get('show', True),
+                      'proportion': 1,
+                      'on_change': props['on_change'],
+                      'ref': props['ref']},
+         *[[c.NotebookItem,
+            {'title': props['options'][i], 'selected': props['activeSelection'] == i},
+            [TabbedConfigPage if props['tabbed_groups'] else ConfigPage,
+             {'flag': wx.EXPAND,
+              'proportion': 3,
+              'config': config,
+              'show': i == props['activeSelection']}]]
+           for i, config in enumerate(props['config'].values())]]
+    )
+
+
+
+def layout_choose():
+    def buildNavigation(self):
+        """
+        Chooses the appropriate layout navigation component based on user prefs
+        """
+        if self.buildSpec['navigation'] == constants.TABBED:
+            navigation = Tabbar(self, self.buildSpec, self.configs)
+        else:
+            navigation = Sidebar(self, self.buildSpec, self.configs)
+            if self.buildSpec['navigation'] == constants.HIDDEN:
+                navigation.Hide()
+        return navigation
+
+
+    def buildConfigPanels(self, parent):
+        page_class = TabbedConfigPage if self.buildSpec['tabbed_groups'] else ConfigPage
+
+        return [page_class(parent, widgets, self.buildSpec)
+                for widgets in self.buildSpec['widgets'].values()]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class TitleText(Component):
+    def __init__(self, props):
+        super().__init__(props)
+        self.ref = Ref()
+
+    def component_did_mount(self):
+        text: wx.StaticText = self.ref.instance
+        font_size = text.GetFont().GetPointSize()
+        text.SetFont(wx.Font(
+            int(font_size * 1.2),
+            wx.FONTFAMILY_DEFAULT,
+            Constants.WX_FONTSTYLE_NORMAL,
+            wx.FONTWEIGHT_BOLD,
+            False
+        ))
+
+    def render(self):
+        return wsx([c.StaticText, {**self.props, 'label': self.props['label'], 'ref': self.ref}])
+
+
+##
+## REWX definitions:
+##
+
+@mount.register(ConfigPage)  # type: ignore
+def config(element, parent):
+    return update(element, ConfigPage(parent, element['props']['config'], {'contents': []}))
+
+@update.register(ConfigPage)  # type: ignore
+def config(element, instance: ConfigPage):
+    set_basic_props(instance, element['props'])
+    return instance
+
+@mount.register(TabbedConfigPage)  # type: ignore
+def tabbedconfig(element, parent):
+    return update(element, TabbedConfigPage(parent, element['props']['config'], {'contents': []}))
+
+@update.register(TabbedConfigPage)  # type: ignore
+def tabbedconfig(element, instance: TabbedConfigPage):
+    set_basic_props(instance, element['props'])
+    return instance
+
+@mount.register(Console)  # type: ignore
+def console(element, parent):
+    return update(element, Console(parent, element['props']))
+
+@update.register(Console)  # type: ignore
+def console(element, instance: Console):
+    set_basic_props(instance, element['props'])
+    if 'show' in element['props']:
+        instance.Show(element['props']['show'])
+    return instance
+
diff --git a/gooey/gui/application.py b/gooey/gui/bootstrap.py
similarity index 65%
rename from gooey/gui/application.py
rename to gooey/gui/bootstrap.py
index b8a4bbc3..040e903b 100644
--- a/gooey/gui/application.py
+++ b/gooey/gui/bootstrap.py
@@ -1,43 +1,46 @@
-'''
-Main runner entry point for Gooey.
-'''
-
-import wx
-# wx.html and wx.xml imports required here to make packaging with
-# pyinstaller on OSX possible without manually specifying `hidden_imports`
-# in the build.spec
-import wx.html
-import wx.xml
-import wx.richtext  # Need to be imported before the wx.App object is created.
-import wx.lib.inspection
-from gooey.gui.lang import i18n
-
-from gooey.gui import image_repository
-from gooey.gui.containers.application import GooeyApplication
-from gooey.util.functional import merge
-
-
-def run(build_spec):
-    app, _ = build_app(build_spec)
-    app.MainLoop()
-
-
-def build_app(build_spec):
-    app = wx.App(False)
-    return _build_app(build_spec, app)
-
-
-def _build_app(build_spec, app):
-    """
-    Note: this method is broken out with app as
-    an argument to facilitate testing.
-    """
-    # use actual program name instead of script file name in macOS menu
-    app.SetAppDisplayName(build_spec['program_name'])
-
-    i18n.load(build_spec['language_dir'], build_spec['language'], build_spec['encoding'])
-    imagesPaths = image_repository.loadImages(build_spec['image_dir'])
-    gapp = GooeyApplication(merge(build_spec, imagesPaths))
-    # wx.lib.inspection.InspectionTool().Show()
-    gapp.Show()
-    return (app, gapp)
+'''
+Main runner entry point for Gooey.
+'''
+from typing import Any, Tuple
+
+import wx  # type: ignore
+# wx.html and wx.xml imports required here to make packaging with
+# pyinstaller on OSX possible without manually specifying `hidden_imports`
+# in the build.spec
+import wx.html  # type: ignore
+import wx.lib.inspection  # type: ignore
+import wx.richtext  # type: ignore
+import wx.xml  # type: ignore
+
+from gooey.gui import image_repository
+from gooey.gui.application.application import RGooey
+from gooey.gui.lang import i18n
+from gooey.util.functional import merge
+from rewx import render, create_element  # type: ignore
+
+
+def run(build_spec):
+    app, _ = build_app(build_spec)
+    app.MainLoop()
+
+
+def build_app(build_spec):
+    app = wx.App(False)
+    return _build_app(build_spec, app)
+
+
+def _build_app(build_spec, app) -> Tuple[Any, wx.Frame]:
+    """
+    Note: this method is broken out with app as
+    an argument to facilitate testing.
+    """
+    # use actual program name instead of script file name in macOS menu
+    app.SetAppDisplayName(build_spec['program_name'])
+
+    i18n.load(build_spec['language_dir'], build_spec['language'], build_spec['encoding'])
+    imagesPaths = image_repository.loadImages(build_spec['image_dir'])
+    gapp2 = render(create_element(RGooey, merge(build_spec, imagesPaths)), None)
+    # wx.lib.inspection.InspectionTool().Show()
+    # gapp.Show()
+    gapp2.Show()
+    return (app, gapp2)
diff --git a/gooey/gui/cli.py b/gooey/gui/cli.py
index 9a47fa75..48455529 100644
--- a/gooey/gui/cli.py
+++ b/gooey/gui/cli.py
@@ -1,19 +1,106 @@
+import json
 from itertools import chain
 
 from copy import deepcopy
 
 from gooey.util.functional import compact
+from typing import List, Optional
 
+from gooey.gui.constants import VALUE_PLACEHOLDER
+from gooey.gui.formatters import formatArgument
+from gooey.python_bindings.types import FieldValue, Group, Item
+from gooey.util.functional import merge  # type: ignore
+from gooey.gui.state import FullGooeyState
 
-def buildCliString(target, cmd, positional, optional, suppress_gooey_flag=False):
+'''
+primary :: Target -> Command -> Array Arg -> Array Arg -> Boolean -> CliString
+validateForm :: Target -> Command -> Array Arg -> Array Arg -> CliString
+validateField :: Target -> Command -> Array Arg -> Array Arg -> ArgId -> CliString
+completed :: Target -> Command -> FromState -> CliString
+failed :: Target -> Command -> FromState -> CliString
+fieldAction :: Target -> Command ->   
+
+'''
+
+
+def buildSuccessCmd(state: FullGooeyState):
+    subcommand = state['subcommands'][state['activeSelection']]
+    widgets = state['widgets'][subcommand]
+
+
+
+
+def onSuccessCmd(target: str, subCommand: str, formState: List[str]) -> str:
+    command = subCommand if not subCommand == '::gooey/default' else ''
+    return f'{target} {command} --gooey-on-success {json.dumps(formState)}'
+
+
+def onErrorCmd(target: str, subCommand: str, formState: List[str]) -> str:
+    command = subCommand if not subCommand == '::gooey/default' else ''
+    return f'{target} {command} --gooey-on-error {json.dumps(formState)}'
+
+
+def formValidationCmd(target: str, subCommand: str, positionals: List[FieldValue], optionals: List[FieldValue]) -> str:
+    positional_args = [cmdOrPlaceholderOrNone(x) for x in positionals]
+    optional_args = [cmdOrPlaceholderOrNone(x) for x in optionals]
+    command = subCommand if not subCommand == '::gooey/default' else ''
+    return u' '.join(compact([
+        target,
+        command,
+        *optional_args,
+        '--gooey-validate-form',
+        '--' if positional_args else '',
+        *positional_args]))
+
+
+def cliCmd(target: str,
+           subCommand: str,
+           positionals: List[FieldValue],
+           optionals: List[FieldValue],
+           suppress_gooey_flag=False) -> str:
+    positional_args = [arg['cmd'] for arg in positionals]
+    optional_args = [arg['cmd'] for arg in optionals]
+    command = subCommand if not subCommand == '::gooey/default' else ''
+    ignore_flag = '' if suppress_gooey_flag else '--ignore-gooey'
+    return u' '.join(compact([
+        target,
+        command,
+        *optional_args,
+        ignore_flag,
+        '--' if positional_args else '',
+        *positional_args]))
+
+
+def cmdOrPlaceholderOrNone(field: FieldValue) -> Optional[str]:
+    # Argparse has a fail-fast-and-exit behavior for any missing
+    # values. This poses a problem for dynamic validation, as we
+    # want to collect _all_ errors to be more useful to the user.
+    # As such, if there is no value currently available, we pass
+    # through a stock placeholder values which allows GooeyParser
+    # to handle it being missing without Argparse exploding due to
+    # it actually being missing.
+    if field['clitype'] == 'positional':
+        return field['cmd'] or VALUE_PLACEHOLDER
+    elif field['clitype'] != 'positional' and field['meta']['required']:
+        # same rationale applies here. We supply the argument
+        # along with a fixed placeholder (when relevant i.e. `store`
+        # actions)
+        return field['cmd'] or formatArgument(field['meta'], VALUE_PLACEHOLDER)
+    else:
+        # Optional values are, well, optional. So, like usual, we send
+        # them if present or drop them if not.
+        return field['cmd']
+
+
+def buildCliString(target, subCommand, positional, optional, suppress_gooey_flag=False):
     positionals = deepcopy(positional)
     if positionals:
         positionals.insert(0, "--")
 
-    cmd_string = ' '.join(compact(chain(optional, positionals)))
+    arguments = ' '.join(compact(chain(optional, positionals)))
 
-    if cmd != '::gooey/default':
-        cmd_string = u'{} {}'.format(cmd, cmd_string)
+    if subCommand != '::gooey/default':
+        arguments = u'{} {}'.format(subCommand, arguments)
 
     ignore_flag = '' if suppress_gooey_flag else '--ignore-gooey'
-    return u'{} {} {}'.format(target, ignore_flag, cmd_string)
+    return u'{} {} {}'.format(target, ignore_flag, arguments)
diff --git a/gooey/gui/components/config.py b/gooey/gui/components/config.py
index 25517feb..1c01b6e2 100644
--- a/gooey/gui/components/config.py
+++ b/gooey/gui/components/config.py
@@ -1,16 +1,22 @@
-import wx
-from wx.lib.scrolledpanel import ScrolledPanel
+from typing import Mapping, List
 
+import wx  # type: ignore
+from wx.lib.scrolledpanel import ScrolledPanel  # type: ignore
+
+from gooey.gui.components.mouse import notifyMouseEvent
 from gooey.gui.components.util.wrapped_static_text import AutoWrappedStaticText
-from gooey.gui.util import wx_util
-from gooey.util.functional import getin, flatmap, compact, indexunique
 from gooey.gui.lang.i18n import _
-from gooey.gui.components.mouse import notifyMouseEvent
+from gooey.gui.util import wx_util
+from gooey.python_bindings.types import FormField
+from gooey.util.functional import getin, flatmap, indexunique
 
 
 class ConfigPage(ScrolledPanel):
+    self_managed = True
+
     def __init__(self, parent, rawWidgets, buildSpec,  *args, **kwargs):
         super(ConfigPage, self).__init__(parent, *args, **kwargs)
+
         self.SetupScrolling(scroll_x=False, scrollToTop=False)
         self.rawWidgets = rawWidgets
         self.buildSpec = buildSpec
@@ -49,10 +55,32 @@ def getOptionalArgs(self):
                 if widget.info['cli_type'] != 'positional']
 
 
+    def getPositionalValues(self):
+        return [widget.getValue() for widget in self.reifiedWidgets
+                if widget.info['cli_type'] == 'positional']
+
+
+    def getOptionalValues(self):
+        return [widget.getValue() for widget in self.reifiedWidgets
+                if widget.info['cli_type'] != 'positional']
+
+
+    def getFormState(self) -> List[FormField]:
+        return [widget.getUiState()
+                for widget in self.reifiedWidgets]
+
+
+    def syncFormState(self, formState: List[FormField]):
+        for item in formState:
+            self.widgetsMap[item['id']].syncUiState(item)
+
     def isValid(self):
-        states = [widget.getValue() for widget in self.reifiedWidgets]
-        return not any(compact([state['error'] for state in states]))
+        return not any(self.getErrors())
 
+    def getErrors(self):
+        states = [widget.getValue() for widget in self.reifiedWidgets]
+        return {state['meta']['dest']: state['error'] for state in states
+                if state['error']}
 
     def seedUI(self, seeds):
         radioWidgets = self.indexInternalRadioGroupWidgets()
@@ -62,10 +90,33 @@ def seedUI(self, seeds):
             if id in radioWidgets:
                 radioWidgets[id].setOptions(values)
 
+
+    def setErrors(self, errorMap: Mapping[str, str]):
+        self.resetErrors()
+        radioWidgets = self.indexInternalRadioGroupWidgets()
+        widgetsByDest = {v._meta['dest']: v for k,v in self.widgetsMap.items()
+                         if v.info['type'] != 'RadioGroup'}
+
+        # if there are any errors, then all error blocks should
+        # be displayed so that the UI elements remain inline with
+        # each other.
+        if errorMap:
+            for widget in self.widgetsMap.values():
+                widget.showErrorString(True)
+
+        for id, message in errorMap.items():
+            if id in widgetsByDest:
+                widgetsByDest[id].setErrorString(message)
+                widgetsByDest[id].showErrorString(True)
+            if id in radioWidgets:
+                radioWidgets[id].setErrorString(message)
+                radioWidgets[id].showErrorString(True)
+
+
     def indexInternalRadioGroupWidgets(self):
         groups = filter(lambda x: x.info['type'] == 'RadioGroup', self.reifiedWidgets)
         widgets = flatmap(lambda group: group.widgets, groups)
-        return indexunique(lambda x: x._id, widgets)
+        return indexunique(lambda x: x._meta['dest'], widgets)
 
 
     def displayErrors(self):
@@ -199,7 +250,6 @@ class TabbedConfigPage(ConfigPage):
     Splits top-level groups across tabs
     """
 
-
     def layoutComponent(self):
         # self.rawWidgets['contents'] = self.rawWidgets['contents'][1:2]
         self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT)
@@ -211,7 +261,7 @@ def layoutComponent(self):
             self.makeGroup(panel, sizer, group, 0, wx.EXPAND)
             panel.SetSizer(sizer)
             panel.Layout()
-            self.notebook.AddPage(panel, group['name'])
+            self.notebook.AddPage(panel, self.getName(group))
             self.notebook.Layout()
 
 
@@ -221,5 +271,6 @@ def layoutComponent(self):
         self.Layout()
 
 
-
+    def snapToErrorTab(self):
+        pass
 
diff --git a/gooey/gui/components/console.py b/gooey/gui/components/console.py
index d58c514b..9938bc03 100644
--- a/gooey/gui/components/console.py
+++ b/gooey/gui/components/console.py
@@ -1,6 +1,6 @@
 import webbrowser
 
-import wx
+import wx  # type: ignore
 
 from gooey.gui.lang.i18n import _
 from .widgets.basictextconsole import BasicTextConsole
@@ -10,9 +10,10 @@ class Console(wx.Panel):
     '''
     Textbox console/terminal displayed during the client program's execution.
     '''
+    self_managed = True
 
     def __init__(self, parent, buildSpec, **kwargs):
-        wx.Panel.__init__(self, parent, **kwargs)
+        wx.Panel.__init__(self, parent, name='console', **kwargs)
         self.buildSpec = buildSpec
 
         self.text = wx.StaticText(self, label=_("status"))
diff --git a/gooey/gui/components/dialogs.py b/gooey/gui/components/dialogs.py
new file mode 100644
index 00000000..a042add1
--- /dev/null
+++ b/gooey/gui/components/dialogs.py
@@ -0,0 +1,40 @@
+import rewx.components as c  # type: ignore
+import wx  # type: ignore
+import wx.html2  # type: ignore
+from rewx import wsx, render  # type: ignore
+
+
+def _html_window(html):
+    return wsx(
+        [c.Block, {'orient': wx.VERTICAL, 'flag': wx.EXPAND},
+         [c.HtmlWindow, {'style': wx.TE_READONLY, 'flag': wx.EXPAND | wx.ALL,
+                         'proportion': 1, 'value': html}]]
+    )
+
+
+class HtmlDialog(wx.Dialog):
+    """
+    A MessageDialog where the central contents are an HTML window
+    customizable by the user.
+    """
+    def __init__(self, *args, **kwargs):
+        caption = kwargs.pop('caption', '')
+        html = kwargs.pop('html', '')
+        super(HtmlDialog, self).__init__(None, *args, **kwargs)
+
+        wx.InitAllImageHandlers()
+
+        self.SetTitle(caption)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(render(_html_window(html), self), 1, wx.EXPAND)
+
+        # in addition to creating the sizer, this actually attached
+        # a few common handlers which makes it feel more dialog-y. Thus
+        # it being done here rather than in rewx
+        btnSizer = self.CreateStdDialogButtonSizer(wx.OK)
+        sizer.Add(btnSizer, 0, wx.ALL | wx.EXPAND, 9)
+        self.SetSizer(sizer)
+        self.Layout()
+
+
+
diff --git a/gooey/gui/components/filtering/__init__.py b/gooey/gui/components/filtering/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/gui/components/filtering/prefix_filter.py b/gooey/gui/components/filtering/prefix_filter.py
new file mode 100644
index 00000000..79174130
--- /dev/null
+++ b/gooey/gui/components/filtering/prefix_filter.py
@@ -0,0 +1,120 @@
+import re
+
+import pygtrie as trie  # type: ignore
+from functools import reduce
+
+__ALL__ = ('PrefixTokenizers', 'PrefixSearch')
+
+
+
+class PrefixTokenizers:
+    # This string here is just an arbitrary long string so that
+    # re.split finds no matches and returns the entire phrase
+    ENTIRE_PHRASE = '::gooey/tokenization/entire-phrase'
+    # \s == any whitespace character
+    WORDS = r'\s'
+
+    @classmethod
+    def REGEX(cls, expression):
+        return expression
+
+class OperatorType:
+    AND = 'AND'
+    OR = 'OR'
+
+class SearchOptions:
+    def __init__(self,
+                 choice_tokenizer=PrefixTokenizers.ENTIRE_PHRASE,
+                 input_tokenizer=PrefixTokenizers.ENTIRE_PHRASE,
+                 ignore_case=True,
+                 operator='AND',
+                 index_suffix= False,
+                 **kwargs):
+        self.choice_tokenizer = choice_tokenizer
+        self.input_tokenizer = input_tokenizer
+        self.ignore_case = ignore_case
+        self.operator = operator
+        self.index_suffix = index_suffix
+
+
+
+class PrefixSearch(object):
+    """
+    A trie backed index for quickly finding substrings
+    in a list of options.
+    """
+
+    def __init__(self, choices, options={}, *args, **kwargs):
+        self.choices = sorted(filter(None, choices))
+        self.options: SearchOptions = SearchOptions(**options)
+        self.searchtree = self.buildSearchTrie(choices)
+
+    def updateChoices(self, choices):
+        self.choices = sorted(filter(None, choices))
+        self.searchtree = self.buildSearchTrie(choices)
+
+    def findMatches(self, token):
+        if not token:
+            return sorted(self.choices)
+        tokens = self.tokenizeInput(token)
+        matches = [set(flatten(self._vals(self.searchtree, prefix=t))) for t in tokens]
+        op = intersection if self.options.operator == 'AND' else union
+        return sorted(reduce(op, matches))
+
+    def tokenizeInput(self, token):
+        """
+        Cleans and tokenizes the user's input.
+
+        empty characters and spaces are trimmed to prevent
+        matching all paths in the index.
+        """
+        return list(filter(None, re.split(self.options.input_tokenizer, self.clean(token))))
+
+    def tokenizeChoice(self, choice):
+        """
+        Splits the `choice` into a series of tokens based on
+        the user's criteria.
+
+        If suffix indexing is enabled, the individual tokens
+        are further broken down and indexed by their suffix offsets. e.g.
+
+            'Banana', 'anana', 'nana', 'ana'
+        """
+        choice_ = self.clean(choice)
+        tokens = re.split(self.options.choice_tokenizer, choice_)
+        if self.options.index_suffix:
+            return [token[i:]
+                    for token in tokens
+                    for i in range(len(token) - 2)]
+        else:
+            return tokens
+
+    def clean(self, text):
+        return text.lower() if self.options.ignore_case else text
+
+    def buildSearchTrie(self, choices):
+        searchtrie = trie.Trie()
+        for choice in choices:
+            for token in self.tokenizeChoice(choice):
+                if not searchtrie.has_key(token):
+                    searchtrie[token] = []
+                searchtrie[token].append(choice)
+        return searchtrie
+
+    def _vals(self, searchtrie, **kwargs):
+        try:
+            return searchtrie.values(**kwargs)
+        except KeyError:
+            return []
+
+
+def intersection(a, b):
+    return a.intersection(b)
+
+
+def union(a, b):
+    return a.union(b)
+
+
+def flatten(xs):
+    return [item for x in xs for item in x]
diff --git a/gooey/gui/components/footer.py b/gooey/gui/components/footer.py
index adfec4b6..4b70e6c6 100644
--- a/gooey/gui/components/footer.py
+++ b/gooey/gui/components/footer.py
@@ -1,5 +1,5 @@
 import sys
-import wx
+import wx  # type: ignore
 
 from gooey.gui import events
 from gooey.gui.lang.i18n import _
@@ -18,7 +18,10 @@ def __init__(self, parent, buildSpec, **kwargs):
         self.buildSpec = buildSpec
 
         self.SetMinSize((30, 53))
-        self.SetDoubleBuffered(True)
+        # TODO: The was set to True for the timer addition
+        #       however, it leads to 'tearing' issues when resizing
+        #       the GUI in windows. Disabling until I can dig into it.
+        self.SetDoubleBuffered(False)
         # components
         self.cancel_button = None
         self.start_button = None
@@ -135,7 +138,7 @@ def _do_layout(self):
         self.edit_button.Hide()
         self.restart_button.Hide()
         self.close_button.Hide()
-        self.progress_bar.Hide()
+        # self.progress_bar.Hide()
 
         v_sizer.AddStretchSpacer(1)
         self.SetSizer(v_sizer)
@@ -149,7 +152,8 @@ def button(self, label=None, style=None, event_id=-1):
             style=style)
 
     def dispatch_click(self, event):
-        pub.send_message(event.GetId())
+        if event.EventObject.Enabled:
+            pub.send_message(event.GetId())
 
     def hide_all_buttons(self):
         for button in self.buttons:
diff --git a/gooey/gui/components/header.py b/gooey/gui/components/header.py
index 547a419d..5f1b3d39 100644
--- a/gooey/gui/components/header.py
+++ b/gooey/gui/components/header.py
@@ -4,7 +4,9 @@
 @author: Chris
 '''
 
-import wx
+import wx  # type: ignore
+from rewx import wsx
+import rewx.components as c
 
 from gooey.gui import imageutil, image_repository
 from gooey.gui.util import wx_util
@@ -15,6 +17,9 @@
 PAD_SIZE = 10
 
 
+
+
+
 class FrameHeader(wx.Panel):
     def __init__(self, parent, buildSpec, **kwargs):
         wx.Panel.__init__(self, parent, **kwargs)
@@ -48,6 +53,7 @@ def setImage(self, image):
         getattr(self, image).Show(True)
         self.Layout()
 
+
     def layoutComponent(self):
         self.SetBackgroundColour(self.buildSpec['header_bg_color'])
         self.SetSize((30, self.buildSpec['header_height']))
diff --git a/gooey/gui/components/layouts/layouts.py b/gooey/gui/components/layouts/layouts.py
index c64faf82..883152f3 100644
--- a/gooey/gui/components/layouts/layouts.py
+++ b/gooey/gui/components/layouts/layouts.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 
 def standard_layout(title, subtitle, widget):
diff --git a/gooey/gui/components/menubar.py b/gooey/gui/components/menubar.py
index 5ca85b01..d6603f14 100644
--- a/gooey/gui/components/menubar.py
+++ b/gooey/gui/components/menubar.py
@@ -1,9 +1,10 @@
 import webbrowser
 from functools import partial
 
-import wx
+import wx  # type: ignore
 
 from gooey.gui import three_to_four
+from gooey.gui.components.dialogs import HtmlDialog
 
 
 class MenuBar(wx.MenuBar):
@@ -38,7 +39,8 @@ def handleMenuAction(self, item):
         handlers = {
             'Link': self.openBrowser,
             'AboutDialog': self.spawnAboutDialog,
-            'MessageDialog': self.spawnMessageDialog
+            'MessageDialog': self.spawnMessageDialog,
+            'HtmlDialog': self.spawnHtmlDialog
         }
         f = handlers[item['type']]
         return partial(f, item)
@@ -59,6 +61,10 @@ def spawnMessageDialog(self, item, *args, **kwargs):
                                caption=item.get('caption', '')).ShowModal()
 
 
+    def spawnHtmlDialog(self, item, *args, **kwargs):
+        HtmlDialog(caption=item.get('caption', ''), html=item.get('html')).ShowModal()
+
+
     def spawnAboutDialog(self, item, *args, **kwargs):
         """
         Fill the wx.AboutBox with any relevant info the user provided
@@ -78,4 +84,6 @@ def spawnAboutDialog(self, item, *args, **kwargs):
             if field in item:
                 getattr(about, method)(item[field])
 
-        three_to_four.AboutBox(about)
\ No newline at end of file
+        three_to_four.AboutBox(about)
+
+
diff --git a/gooey/gui/components/modals.py b/gooey/gui/components/modals.py
index 015346f0..58004cfe 100644
--- a/gooey/gui/components/modals.py
+++ b/gooey/gui/components/modals.py
@@ -3,13 +3,13 @@
 """
 from collections import namedtuple
 
-import wx
+import wx  # type: ignore
 
 from gooey.gui.lang.i18n import _
 
 
 # These don't seem to be specified anywhere in WX for some reason
-DialogConstants = namedtuple('DialogConstants', 'YES NO')(5103, 5104)
+DialogConstants = namedtuple('DialogConstants', 'YES NO')(5103, 5104)  # type: ignore
 
 
 def showDialog(title, content, style):
diff --git a/gooey/gui/components/options/__init__.py b/gooey/gui/components/options/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/gui/components/options/options.py b/gooey/gui/components/options/options.py
new file mode 100644
index 00000000..302ebe38
--- /dev/null
+++ b/gooey/gui/components/options/options.py
@@ -0,0 +1,334 @@
+from gooey.gui.components.filtering.prefix_filter import PrefixTokenizers
+
+
+
+def _include_layout_docs(f):
+    """
+    Combines the layout_options docsstring with the
+    wrapped function's doc string.
+    """
+    f.__doc__ = (f.__doc__ or '') + (LayoutOptions.__doc__ or '')
+    return f
+
+
+def _include_global_option_docs(f):
+    """
+    Combines docstrings for options available to
+    all widget types.
+    """
+    _doc = """:param initial_value:  Sets the initial value in the UI. 
+    """
+    f.__doc__ = (f.__doc__ or '') + _doc
+    return f
+
+def _include_chooser_msg_wildcard_docs(f):
+    """
+    Combines the basic Chooser options (wildard, message) docsstring
+    with the wrapped function's doc string.
+    """
+    _doc = """:param wildcard: Sets the wildcard, which can contain multiple file types, for 
+                     example: "BMP files (.bmp)|.bmp|GIF files (.gif)|.gif"
+    :param message:  Sets the message that will be displayed on the dialog.
+    """
+    f.__doc__ = (f.__doc__ or '') + _doc
+    return f
+
+def _include_choose_dir_file_docs(f):
+    """
+        Combines the basic Chooser options (wildard, message) docsstring
+        with the wrapped function's doc string.
+        """
+    _doc = """:param default_dir: The default directory selected when the dialog spawns 
+    :param default_file: The default filename used in the dialog
+    """
+    f.__doc__ = (f.__doc__ or '') + _doc
+    return f
+
+
+
+def LayoutOptions(label_color=None,
+                  label_bg_color=None,
+                  help_color=None,
+                  help_bg_color=None,
+                  error_color=None,
+                  error_bg_color=None,
+                  show_label=True,
+                  show_help=True,
+                  visible=True,
+                  full_width=False):
+    """
+    Layout Options:
+    ---------------
+
+    Color options can be passed either as a hex string ('#ff0000') or as
+    a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`)
+
+    :param label_color:    The foreground color of the label text
+    :param label_bg_color: The background color of the label text.
+    :param help_color:     The foreground color of the help text.
+    :param help_bg_color:  The background color of the help text.
+    :param error_color:    The foreground color of the error text (when visible).
+    :param error_bg_color: The background color of the error text (when visible).
+    :param show_label:     Toggles whether or not to display the label text
+    :param show_help:      Toggles whether or not to display the help text
+    :param visible:        Hides the entire widget when False. Note: the widget
+                           is still present in the UI and will still send along any
+                           default values that have been provided in code. This option
+                           is here for when you want to hide certain advanced / dangerous
+                           inputs from your GUI users.
+    :param full_width:     This is a layout hint for this widget. When True the widget
+                           will fill the entire available space within a given row.
+                           Otherwise, it will be sized based on the column rules
+                           provided elsewhere.
+    """
+    return _clean(locals())
+
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def TextField(initial_value=None, validator=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def PasswordField(initial_value=None, validator=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def IntegerField(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):
+    """
+    :param min: The minimum value allowed
+    :param max: The maximum value allowed
+    :param increment: The step size of the spinner
+    """
+    return _clean(locals())
+
+@_include_layout_docs
+@_include_global_option_docs
+def Slider(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):
+    """
+    :param min: The minimum value allowed
+    :param max: The maximum value allowed
+    :param increment: The step size of the slider
+    """
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def DecimalField(validator=None,
+                 initial_value=None,
+                 min=0.0,
+                 max=1.0,
+                 increment=0.01,
+                 precision=2,
+                 **layout_options):
+    """
+    :param min: The minimum value allowed
+    :param max: The maximum value allowed
+    :param increment: The step size of the spinner
+    :param precision: The precision of the decimal (0-20)
+    """
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def TextArea(initial_value=None, height=None, readonly=False, validator=None, **layout_options):
+    """
+    :param height:   The height of the TextArea.
+    :param readonly: Controls whether or not user's may modify the contents
+    """
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def RichTextConsole(**layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def ListBox(initial_value=None, height=None, **layout_options):
+    """
+    :param height: The height of the Listbox
+    """
+    return _clean(locals())
+
+# TODO: what are this guy's layout options..?
+def MutexGroup(initial_selection=None, title=None, **layout_options):
+    """
+    :param initial_selection: The index of the option which should be initially selected.
+    :param title:             Adds the supplied title above the RadioGroup options (when present)
+    """
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def Dropdown(initial_value=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def Counter(initial_value=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def CheckBox(initial_value=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def BlockCheckBox(initial_value=None, checkbox_label=None, **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+def FilterableDropdown(placeholder=None,
+                       empty_message=None,
+                       max_size=80,
+                       search_strategy=None,
+                       initial_value=None,
+                       **layout_options):
+    """
+    :param placeholder:     Text to display when the user has provided no input
+    :param empty_message:   Text to display if the user's query doesn't match anything
+    :param max_size:        maximum height of the dropdown
+    :param search_strategy: see: PrefixSearchStrategy
+    """
+    return _clean(locals())
+
+
+def PrefixSearchStrategy(
+                   choice_tokenizer=PrefixTokenizers.WORDS,
+                   input_tokenizer=PrefixTokenizers.REGEX('\s'),
+                   ignore_case=True,
+                   operator='AND',
+                   index_suffix=False):
+    """
+    :param choice_tokenizer: See: PrefixTokenizers - sets the tokenization strategy
+                             for the `choices`
+    :param input_tokenizer:  See: PrefixTokenizers sets how the users's `input` get tokenized.
+    :param ignore_case:      Controls whether or not to honor case while searching
+    :param operator:         see: `OperatorType` - controls whether or not individual
+                             search tokens
+                             get `AND`ed or `OR`d together when evaluating a match.
+    :param index_suffix:     When enabled, generates a suffix-tree to enable efficient
+                             partial-matching against any of the tokens.
+    """
+    return {**_clean(locals()), 'type': 'PrefixFilter'}
+
+
+@_include_layout_docs
+@_include_global_option_docs
+@_include_choose_dir_file_docs
+@_include_chooser_msg_wildcard_docs
+def FileChooser(wildcard=None,
+                default_dir=None,
+                default_file=None,
+                message=None,
+                initial_value=None,
+                **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+@_include_chooser_msg_wildcard_docs
+def DirectoryChooser(wildcard=None,
+                    default_path=None,
+                    message=None,
+                    initial_value=None,
+                    **layout_options):
+    """
+    :param default_path: The default path selected when the dialog spawns
+    """
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+@_include_choose_dir_file_docs
+@_include_chooser_msg_wildcard_docs
+def FileSaver(wildcard=None,
+              default_dir=None,
+              default_file=None,
+              message=None,
+              initial_value=None,
+              **layout_options):
+    return _clean(locals())
+
+
+@_include_layout_docs
+@_include_global_option_docs
+@_include_choose_dir_file_docs
+@_include_chooser_msg_wildcard_docs
+def MultiFileSaver(wildcard=None,
+              default_dir=None,
+              default_file=None,
+              message=None,
+              initial_value=None,
+              **layout_options):
+    return _clean(locals())
+
+
+def ExpressionValidator(test=None, message=None):
+    """
+    Creates the data for a basic expression validator.
+
+    Your test function can be made up of any valid Python expression.
+    It receives the variable user_input as an argument against which to
+    perform its validation. Note that all values coming from Gooey
+    are in the form of a string, so you'll have to cast as needed
+    in order to perform your validation.
+    """
+    return {**_clean(locals()), 'type': 'ExpressionValidator'}
+
+
+def RegexValidator(test=None, message=None):
+    """
+    Creates the data for a basic RegexValidator.
+
+    :param test:    the regex expression. This should be the expression
+                    directly (i.e. `test='\d+'`). Gooey will test
+                    that the user's input satisfies this expression.
+    :param message: The message to display if the input doesn't match
+                    the regex
+    """
+    return {**_clean(locals()), 'type': 'RegexValidator'}
+
+
+def ArgumentGroup(show_border=False,
+                  show_underline=True,
+                  label_color=None,
+                  columns=None,
+                  margin_top=None):
+    """
+    :param show_border:    When True a labeled border will surround all widgets added to this group.
+    :param show_underline: Controls whether or not to display the underline when using the default border style
+    :param label_color:    The foreground color for the group name
+    :param columns:        Controls the number of widgets on each row
+    :param margin_top:     specifies the top margin in pixels for this group
+    """
+    return _clean(locals())
+
+
+
+
+
+def _clean(options):
+    cleaned = {k: v for k, v in options.items()
+               if v is not None and k != "layout_options"}
+    return {**options.get('layout_options', {}), **cleaned}
+
diff --git a/gooey/gui/components/options/validators.py b/gooey/gui/components/options/validators.py
new file mode 100644
index 00000000..b6cfc149
--- /dev/null
+++ b/gooey/gui/components/options/validators.py
@@ -0,0 +1,201 @@
+import re
+from functools import wraps
+
+from gooey.gui.components.filtering.prefix_filter import OperatorType
+
+
+class SuperBool(object):
+    """
+    A boolean which keeps with it the rationale
+    for when it is false.
+    """
+    def __init__(self, value, rationale):
+        self.value = value
+        self.rationale = rationale
+
+    def __bool__(self):
+        return self.value
+
+    __nonzero__ = __bool__
+
+    def __str__(self):
+        return str(self.value)
+
+
+def lift(f):
+    """
+    Lifts a basic predicate to the SuperBool type
+    stealing the docstring as the rationale message.
+
+    This is largely just goofing around and experimenting
+    since it's a private internal API.
+    """
+    @wraps(f)
+    def inner(value):
+        result = f(value)
+        return SuperBool(result, f.__doc__) if not isinstance(result, SuperBool) else result
+    return inner
+
+
+@lift
+def is_tuple_or_list(value):
+    """Must be either a list or tuple"""
+    return isinstance(value, list) or isinstance(value, tuple)
+
+
+@lift
+def is_str(value):
+    """Must be of type `str`"""
+    return isinstance(value, str)
+
+@lift
+def is_str_or_coll(value):
+    """
+    Colors must be either a hex string or collection of RGB values.
+    e.g.
+        Hex string: #fff0ce
+        RGB Collection: [0, 255, 128] or (0, 255, 128)
+    """
+    return bool(is_str(value)) or bool(is_tuple_or_list(value))
+
+
+@lift
+def has_valid_channel_values(rgb_coll):
+    """Colors in an RGB collection must all be in the range 0-255"""
+    return all([is_0to255(c) and is_int(c) for c in rgb_coll])
+
+
+@lift
+def is_three_channeled(value):
+    """Missing channels! Colors in an RGB collection should be of the form [R,G,B] or (R,G,B)"""
+    return len(value) == 3
+
+@lift
+def is_hex_string(value: str):
+    """Invalid hexadecimal format. Expected: "#FFFFFF" """
+    return isinstance(value, str) and bool(re.match('^#[\dABCDEF]{6}$', value, flags=2))
+
+
+@lift
+def is_bool(value):
+    """Must be of type Boolean"""
+    return isinstance(value, bool)
+
+@lift
+def non_empty_string(value):
+    """Must be a non-empty non-blank string"""
+    return bool(value) and bool(value.strip())
+
+@lift
+def is_tokenization_operator(value):
+    """Operator must be a valid OperatorType i.e. one of: (AND, OR)"""
+    return bool(value) in (OperatorType.AND, OperatorType.OR)
+
+@lift
+def is_tokenizer(value):
+    """Tokenizers must be valid Regular expressions. see: options.PrefixTokenizers"""
+    return bool(non_empty_string(value))
+
+
+@lift
+def is_int(value):
+    """Invalid type. Expected `int`"""
+    return isinstance(value, int)
+
+@lift
+def is_0to255(value):
+    """RGB values must be in the range 0 - 255 (inclusive)"""
+    return 0 <= value <= 255
+
+
+def is_0to20(value):
+    """Precision values must be in the range 0 - 20 (inclusive)"""
+    return 0 <= value <= 20
+
+@lift
+def is_valid_color(value):
+    """Must be either a valid hex string or RGB list"""
+    if is_str(value):
+        return is_hex_string(value)
+    elif is_tuple_or_list(value):
+        return (is_tuple_or_list(value)
+                and is_three_channeled(value)
+                and has_valid_channel_values(value))
+    else:
+        return is_str_or_coll(value)
+
+
+validators = {
+    'label_color': is_valid_color,
+    'label_bg_color': is_valid_color,
+    'help_color': is_valid_color,
+    'help_bg_color': is_valid_color,
+    'error_color': is_valid_color,
+    'error_bg_color': is_valid_color,
+    'show_label': is_bool,
+    'show_help': is_bool,
+    'visible': is_bool,
+    'full_width': is_bool,
+    'height': is_int,
+    'readonly': is_bool,
+    'initial_selection': is_int,
+    'title': non_empty_string,
+    'checkbox_label': non_empty_string,
+    'placeholder': non_empty_string,
+    'empty_message': non_empty_string,
+    'max_size': is_int,
+    'choice_tokenizer': is_tokenizer,
+    'input_tokenizer': is_tokenizer,
+    'ignore_case': is_bool,
+    'operator': is_tokenization_operator,
+    'index_suffix': is_bool,
+    'wildcard': non_empty_string,
+    'default_dir': non_empty_string,
+    'default_file': non_empty_string,
+    'default_path': non_empty_string,
+    'message': non_empty_string,
+    'precision': is_0to20
+}
+
+
+
+def collect_errors(predicates, m):
+    return {
+        k:predicates[k](v).rationale
+        for k,v in m.items()
+        if k in predicates and not predicates[k](v)}
+
+
+def validate(pred, value):
+    result = pred(value)
+    if not result:
+        raise ValueError(result.rationale)
+
+
+
+if __name__ == '__main__':
+    # TODO: there should be tests
+    pass
+    # print(validateColor((1, 'ergerg', 1234)))
+    # print(validateColor(1234))
+    # print(validateColor(123.234))
+    # print(validateColor('123.234'))
+    # print(validateColor('FFFAAA'))
+    # print(validateColor('#FFFAAA'))
+    # print(validateColor([]))
+    # print(validateColor(()))
+    # print(validateColor((1, 2)))
+    # print(validateColor((1, 2, 1234)))
+    # print(is_lifted(lift(is_int)))
+    # print(is_lifted(is_int))
+    # print(OR(is_poop, is_int)('poop'))
+    # print(AND(is_poop, is_lower, is_lower)('pooP'))
+    # print(OR(is_poop, is_int))
+    # print(is_lifted(OR(is_poop, is_int)))
+    # print(validate(is_valid_color, [255, 255, 256]))
+    # print(is_valid_color('#fff000'))
+    # print(is_valid_color([255, 244, 256]))
+    # print(non_empty_string('asdf') and non_empty_string('asdf'))
+    # validate(is_valid_color, 1234)
+
+
diff --git a/gooey/gui/components/sidebar.py b/gooey/gui/components/sidebar.py
index f268a5b9..2132d856 100644
--- a/gooey/gui/components/sidebar.py
+++ b/gooey/gui/components/sidebar.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 from gooey.gui.util import wx_util
 
diff --git a/gooey/gui/components/tabbar.py b/gooey/gui/components/tabbar.py
index e23c76c7..02b3327a 100644
--- a/gooey/gui/components/tabbar.py
+++ b/gooey/gui/components/tabbar.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 from gooey.gui import events
 from gooey.gui.pubsub import pub
diff --git a/gooey/gui/components/util/wrapped_static_text.py b/gooey/gui/components/util/wrapped_static_text.py
index 3a004f3f..301cb373 100644
--- a/gooey/gui/components/util/wrapped_static_text.py
+++ b/gooey/gui/components/util/wrapped_static_text.py
@@ -1,5 +1,5 @@
-import wx
-from wx.lib.wordwrap import wordwrap
+import wx  # type: ignore
+from wx.lib.wordwrap import wordwrap  # type: ignore
 
 
 
diff --git a/gooey/gui/components/widgets/__init__.py b/gooey/gui/components/widgets/__init__.py
index 2710107b..7916c81a 100644
--- a/gooey/gui/components/widgets/__init__.py
+++ b/gooey/gui/components/widgets/__init__.py
@@ -12,3 +12,5 @@
 from .radio_group import RadioGroup
 from .choosers import *
 from .dropdown_filterable import FilterableDropdown
+from .numeric_fields import IntegerField, DecimalField
+from .slider import Slider
diff --git a/gooey/gui/components/widgets/bases.py b/gooey/gui/components/widgets/bases.py
index c1f535ea..11a19bf4 100644
--- a/gooey/gui/components/widgets/bases.py
+++ b/gooey/gui/components/widgets/bases.py
@@ -1,22 +1,26 @@
+import re
 from functools import reduce
+from typing import Optional, Callable, Any, Type, Union
 
-import wx
+import wx  # type: ignore
 
 from gooey.gui import formatters, events
 from gooey.gui.util import wx_util
+from gooey.python_bindings.types import FormField
 from gooey.util.functional import getin, ifPresent
 from gooey.gui.validators import runValidator
 from gooey.gui.components.util.wrapped_static_text import AutoWrappedStaticText
 from gooey.gui.components.mouse import notifyMouseEvent
+from gooey.python_bindings import types as t
 
 
 class BaseWidget(wx.Panel):
-    widget_class = None
+    widget_class: Any
 
     def arrange(self, label, text):
         raise NotImplementedError
 
-    def getWidget(self, parent, **options):
+    def getWidget(self, parent: wx.Window, **options):
         return self.widget_class(parent, **options)
 
     def connectSignal(self):
@@ -28,6 +32,9 @@ def getSublayout(self, *args, **kwargs):
     def setValue(self, value):
         raise NotImplementedError
 
+    def setPlaceholder(self, value):
+        raise NotImplementedError
+
     def receiveChange(self, *args, **kwargs):
         raise NotImplementedError
 
@@ -53,13 +60,14 @@ class TextContainer(BaseWidget):
     #     - This should be broken apart.
     #     - presentation can be ad-hoc or composed
     #     - behavioral just needs a typeclass of get/set/format for Gooey's purposes
-    widget_class = None
+    widget_class = None  # type: ignore
 
     def __init__(self, parent, widgetInfo, *args, **kwargs):
         super(TextContainer, self).__init__(parent, *args, **kwargs)
 
         self.info = widgetInfo
         self._id = widgetInfo['id']
+        self.widgetInfo = widgetInfo
         self._meta = widgetInfo['data']
         self._options = widgetInfo['options']
         self.label = wx.StaticText(self, label=widgetInfo['data']['display_name'])
@@ -72,9 +80,17 @@ def __init__(self, parent, widgetInfo, *args, **kwargs):
         self.SetSizer(self.layout)
         self.bindMouseEvents()
         self.Bind(wx.EVT_SIZE, self.onSize)
+
+        # 1.0.7 initial_value should supersede default when both are present
+        if self._options.get('initial_value') is not None:
+            self.setValue(self._options['initial_value'])
         # Checking for None instead of truthiness means False-evaluaded defaults can be used.
-        if self._meta['default'] is not None:
+        elif self._meta['default'] is not None:
             self.setValue(self._meta['default'])
+
+        if self._options.get('placeholder'):
+            self.setPlaceholder(self._options.get('placeholder'))
+
         self.onComponentInitialized()
 
     def onComponentInitialized(self):
@@ -117,7 +133,9 @@ def arrange(self, *args, **kwargs):
         layout.Add(self.getSublayout(), 0, wx.EXPAND)
         layout.Add(self.error, 1, wx.EXPAND)
 
-        self.error.Hide()
+        # self.error.SetLabel("HELLOOOOO??")
+        # self.error.Show()
+        # print(self.error.Shown)
         return layout
 
 
@@ -151,28 +169,56 @@ def onSize(self, event):
         # self.Layout()
         event.Skip()
 
+    def getUiState(self) -> t.FormField:
+        return t.TextField(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.getWidgetValue(),
+            placeholder=self.widget.widget.GetHint(),
+            error=self.error.GetLabel().replace('\n', ' '),
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+    def syncUiState(self, state: FormField):  # type: ignore
+        self.widget.setValue(state['value'])  # type: ignore
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+
+
+    def getValue(self) -> t.FieldValue:
+        regexFunc: Callable[[str], bool] = lambda x: bool(re.match(userValidator, x))
 
-    def getValue(self):
         userValidator = getin(self._options, ['validator', 'test'], 'True')
         message = getin(self._options, ['validator', 'message'], '')
-        testFunc = eval('lambda user_input: bool(%s)' % userValidator)
+        testFunc = regexFunc \
+                   if getin(self._options, ['validator', 'type'], None) == 'RegexValidator'\
+                   else eval('lambda user_input: bool(%s)' % userValidator)
         satisfies = testFunc if self._meta['required'] else ifPresent(testFunc)
         value = self.getWidgetValue()
 
-        return {
-            'id': self._id,
-            'cmd': self.formatOutput(self._meta, value),
-            'rawValue': value,
-            'test': runValidator(satisfies, value),
-            'error': None if runValidator(satisfies, value) else message,
-            'clitype': 'positional'
+        return t.FieldValue(  # type: ignore
+            id=self._id,
+            cmd=self.formatOutput(self._meta, value),
+            meta=self._meta,
+            rawValue= value,
+            # type=self.info['type'],
+            enabled=self.IsEnabled(),
+            visible=self.IsShown(),
+            test= runValidator(satisfies, value),
+            error=None if runValidator(satisfies, value) else message,
+            clitype=('positional'
                         if self._meta['required'] and not self._meta['commands']
-                        else 'optional'
-        }
+                        else 'optional')
+        )
 
     def setValue(self, value):
         self.widget.SetValue(value)
 
+    def setPlaceholder(self, value):
+        if getattr(self.widget, 'SetHint', None):
+            self.widget.SetHint(value)
+
     def setErrorString(self, message):
         self.error.SetLabel(message)
         self.error.Wrap(self.Size.width)
@@ -191,7 +237,7 @@ def receiveChange(self, metatdata, value):
     def dispatchChange(self, value, **kwargs):
         raise NotImplementedError
 
-    def formatOutput(self, metadata, value):
+    def formatOutput(self, metadata, value) -> str:
         raise NotImplementedError
 
 
@@ -203,8 +249,23 @@ class BaseChooser(TextContainer):
     def setValue(self, value):
         self.widget.setValue(value)
 
+    def setPlaceholder(self, value):
+        self.widget.SetHint(value)
+
     def getWidgetValue(self):
         return self.widget.getValue()
 
     def formatOutput(self, metatdata, value):
         return formatters.general(metatdata, value)
+
+    def getUiState(self) -> t.FormField:
+        btn: wx.Button = self.widget.button  # type: ignore
+        return t.Chooser(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.widget.getValue(),
+            btn_label=btn.GetLabel(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
diff --git a/gooey/gui/components/widgets/basictextconsole.py b/gooey/gui/components/widgets/basictextconsole.py
index c649d001..5840524b 100644
--- a/gooey/gui/components/widgets/basictextconsole.py
+++ b/gooey/gui/components/widgets/basictextconsole.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 class BasicTextConsole(wx.TextCtrl):
     def __init__(self, parent):
diff --git a/gooey/gui/components/widgets/beep_boop.py b/gooey/gui/components/widgets/beep_boop.py
deleted file mode 100644
index cd6a8ae8..00000000
--- a/gooey/gui/components/widgets/beep_boop.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import wx
-
-import wx.lib.inspection
-from gooey.gui.components.widgets.textfield import TextField
-from gooey.gui.components.widgets.textarea import Textarea
-from gooey.gui.components.widgets.password import PasswordField
-from gooey.gui.components.widgets.choosers import FileChooser, FileSaver, DirChooser, DateChooser
-from gooey.gui.components.widgets.dropdown import Dropdown
-from gooey.gui.components.widgets.listbox import Listbox
-
-
-class CCC(wx.Frame):
-    def __init__(self, *args, **kwargs):
-        super(CCC, self).__init__(*args, **kwargs)
-        x = {'data':{'choices':['one', 'tw'], 'display_name': 'foo', 'help': 'bar', 'commands': ['-t']}, 'id': 1, 'options': {}}
-
-        a = TextField(self, x)
-        c = Textarea(self, x)
-        b = PasswordField(self, x)
-        d = DirChooser(self, x)
-        e = FileChooser(self,x)
-        f = FileSaver(self, x)
-        g = DateChooser(self, x)
-        h = Dropdown(self, x)
-        i = Listbox(self, x)
-
-        s = wx.BoxSizer(wx.VERTICAL)
-        s.Add(a, 0, wx.EXPAND)
-        s.Add(b, 0, wx.EXPAND)
-        s.Add(c, 0, wx.EXPAND)
-        s.Add(d, 0, wx.EXPAND)
-        s.Add(e, 0, wx.EXPAND)
-        s.Add(f, 0, wx.EXPAND)
-        s.Add(g, 0, wx.EXPAND)
-        s.Add(h, 0, wx.EXPAND)
-        s.Add(i, 0, wx.EXPAND)
-
-        self.SetSizer(s)
-
-
-
-
-
-app = wx.App()
-
-frame = CCC(None, -1, 'simple.py')
-frame.Show()
-
-app.MainLoop()
-
-
-# import wx
-#
-# class MainWindow(wx.Frame):
-#     def __init__(self, *args, **kwargs):
-#         wx.Frame.__init__(self, *args, **kwargs)
-#
-#         self.panel = wx.Panel(self)
-#
-#         self.label = wx.StaticText(self.panel, label="Label")
-#         self.text = wx.TextCtrl(self.panel)
-#         self.button = wx.Button(self.panel, label="Test")
-#
-#         self.button1 = wx.Button(self.panel, label="ABOVE")
-#         self.button2 = wx.Button(self.panel, label="BELLOW")
-#
-#         self.horizontal = wx.BoxSizer()
-#         self.horizontal.Add(self.label, flag=wx.CENTER)
-#         self.horizontal.Add(self.text, proportion=1, flag=wx.CENTER)
-#         self.horizontal.Add(self.button, flag=wx.CENTER)
-#
-#         self.vertical = wx.BoxSizer(wx.VERTICAL)
-#         self.vertical.Add(self.button1, flag=wx.EXPAND)
-#         self.vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND)
-#         self.vertical.Add(self.button2, flag=wx.EXPAND)
-#
-#         self.panel.SetSizerAndFit(self.vertical)
-#         self.Show()
-#
-#
-# app = wx.App(False)
-# win = MainWindow(None)
-# app.MainLoop()
diff --git a/gooey/gui/components/widgets/checkbox.py b/gooey/gui/components/widgets/checkbox.py
index d5a58443..219db409 100644
--- a/gooey/gui/components/widgets/checkbox.py
+++ b/gooey/gui/components/widgets/checkbox.py
@@ -1,9 +1,10 @@
-import wx
+import wx  # type: ignore
 
 from gooey.gui import formatters
 from gooey.gui.components.widgets.bases import TextContainer
 from gooey.gui.lang.i18n import _
 from gooey.gui.util import wx_util
+from gooey.python_bindings import types as t
 
 
 class CheckBox(TextContainer):
@@ -54,6 +55,25 @@ def hideInput(self):
         self.widget.Hide()
 
 
+    def getUiState(self) -> t.FormField:
+        return t.Checkbox(
+            id=self._id,
+            type='Checkbox',
+            checked=self.widget.GetValue(),
+            error=self.error.GetLabel() or None,  # type: ignore
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+    def syncUiState(self, state: t.Checkbox):  # type: ignore
+        checkbox: wx.CheckBox = self.widget
+        checkbox.SetValue(state['checked'])
+        checkbox.Enable(state['enabled'])
+        self.Show(state['visible'])
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+
+
 
 
 
diff --git a/gooey/gui/components/widgets/choosers.py b/gooey/gui/components/widgets/choosers.py
index dcdd808e..f3ca21dc 100644
--- a/gooey/gui/components/widgets/choosers.py
+++ b/gooey/gui/components/widgets/choosers.py
@@ -16,12 +16,10 @@
 
 
 class FileChooser(BaseChooser):
-    # todo: allow wildcard from argparse
     widget_class = core.FileChooser
 
 
 class MultiFileChooser(BaseChooser):
-    # todo: allow wildcard from argparse
     widget_class = core.MultiFileChooser
 
     def formatOutput(self, metatdata, value):
@@ -29,17 +27,14 @@ def formatOutput(self, metatdata, value):
 
 
 class FileSaver(BaseChooser):
-    # todo: allow wildcard
     widget_class = core.FileSaver
 
 
 class DirChooser(BaseChooser):
-    # todo: allow wildcard
     widget_class = core.DirChooser
 
 
 class MultiDirChooser(BaseChooser):
-    # todo: allow wildcard
     widget_class = core.MultiDirChooser
 
     def formatOutput(self, metadata, value):
@@ -47,7 +42,6 @@ def formatOutput(self, metadata, value):
 
 
 class DateChooser(BaseChooser):
-    # todo: allow wildcard
     widget_class = core.DateChooser
 
 
diff --git a/gooey/gui/components/widgets/command.py b/gooey/gui/components/widgets/command.py
index 34eb14df..7ab015cc 100644
--- a/gooey/gui/components/widgets/command.py
+++ b/gooey/gui/components/widgets/command.py
@@ -1,8 +1,11 @@
 from gooey.gui.components.widgets.textfield import TextField
+from gooey.python_bindings import types as t
 
 
 
 __ALL__ = ('CommandField',)
 
 class CommandField(TextField):
-    pass
+
+    def getUiState(self) -> t.FormField:
+        return t.Command(**super().getUiState())  # type: ignore
diff --git a/gooey/gui/components/widgets/core/chooser.py b/gooey/gui/components/widgets/core/chooser.py
index 1135dce1..03ba649c 100644
--- a/gooey/gui/components/widgets/core/chooser.py
+++ b/gooey/gui/components/widgets/core/chooser.py
@@ -1,5 +1,5 @@
-import wx
-import wx.lib.agw.multidirdialog as MDD
+import wx  # type: ignore
+import wx.lib.agw.multidirdialog as MDD  # type: ignore
 import os
 import re
 
@@ -22,7 +22,7 @@ class Chooser(wx.Panel):
 
     TODO: oh, young me. DRY != Good Abstraction
     TODO: this is another weird inheritance hierarchy that's hard
-          to follow. Why do subclasses rather into, not their parent
+          to follow. Why do subclasses reach into, not their parent
           class, but their _physical_ UI parent to grab the Gooey Options?
           All this could be simplified to make the data flow
           more apparent.
@@ -73,6 +73,9 @@ def processResult(self, result):
     def setValue(self, value):
         self.widget.setValue(value)
 
+    def SetHint(self, value):
+        self.widget.SetHint(value)
+
     def getValue(self):
         return self.widget.getValue()
 
diff --git a/gooey/gui/components/widgets/core/text_input.py b/gooey/gui/components/widgets/core/text_input.py
index b161f7ff..a0fccb48 100644
--- a/gooey/gui/components/widgets/core/text_input.py
+++ b/gooey/gui/components/widgets/core/text_input.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 from gooey.gui.util.filedrop import FileDrop
 from gooey.util.functional import merge
@@ -32,6 +32,9 @@ def setValue(self, value):
     def getValue(self):
         return self.widget.GetValue()
 
+    def SetHint(self, value):
+        self.widget.SetHint(value)
+
     def SetDropTarget(self, target):
         self.widget.SetDropTarget(target)
 
diff --git a/gooey/gui/components/widgets/counter.py b/gooey/gui/components/widgets/counter.py
index 91f095b4..62aa6302 100644
--- a/gooey/gui/components/widgets/counter.py
+++ b/gooey/gui/components/widgets/counter.py
@@ -1,5 +1,7 @@
-from gooey.gui.components.widgets.dropdown import Dropdown
+import wx  # type: ignore
 
+from gooey.gui.components.widgets.dropdown import Dropdown
+from gooey.python_bindings import types as t
 from gooey.gui import formatters
 
 
@@ -9,5 +11,19 @@ def setValue(self, value):
         index = self._meta['choices'].index(value) + 1
         self.widget.SetSelection(index)
 
+    def getUiState(self) -> t.FormField:
+        widget: wx.ComboBox = self.widget
+        return t.Counter(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            selected=self.getWidgetValue(),
+            choices=widget.GetStrings(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+
+
     def formatOutput(self, metadata, value):
         return formatters.counter(metadata, value)
diff --git a/gooey/gui/components/widgets/dialogs/base_dialog.py b/gooey/gui/components/widgets/dialogs/base_dialog.py
index 2e35053c..ffde6a30 100644
--- a/gooey/gui/components/widgets/dialogs/base_dialog.py
+++ b/gooey/gui/components/widgets/dialogs/base_dialog.py
@@ -1,6 +1,6 @@
 from gooey.gui.lang.i18n import _
 
-import wx
+import wx  # type: ignore
 
 from gooey.gui.three_to_four import Constants
 
@@ -47,10 +47,7 @@ def GetPath(self):
     """
       Return the value chosen in the picker.
       The method is called GetPath() instead of getPath() to emulate the WX Pickers API.
-      This allows the Chooser class to work same way with native WX dialogs or childs of BaseDialog.
+      This allows the Chooser class to work same way with native WX dialogs or children of BaseDialog.
     """
 
     return self.pickerGetter(self.picker)
-
-
-
diff --git a/gooey/gui/components/widgets/dropdown.py b/gooey/gui/components/widgets/dropdown.py
index bb104e1e..f43d4a29 100644
--- a/gooey/gui/components/widgets/dropdown.py
+++ b/gooey/gui/components/widgets/dropdown.py
@@ -1,10 +1,12 @@
 from contextlib import contextmanager
 
 from gooey.gui.components.widgets.bases import TextContainer
-import wx
+import wx  # type: ignore
 
 from gooey.gui import formatters
 from gooey.gui.lang.i18n import _
+from gooey.python_bindings import types as t
+from gooey.python_bindings.types import FormField
 
 
 class Dropdown(TextContainer):
@@ -44,6 +46,26 @@ def getWidgetValue(self):
     def formatOutput(self, metadata, value):
         return formatters.dropdown(metadata, value)
 
+
+    def syncUiState(self, state: FormField):
+        self.setOptions(state['choices'])  # type: ignore
+        if state['selected'] is not None:  # type: ignore
+            self.setValue(state['selected'])  # type: ignore
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+
+    def getUiState(self) -> t.FormField:
+        widget: wx.ComboBox = self.widget
+        return t.Dropdown(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            selected=self.getWidgetValue(),
+            choices=widget.GetStrings(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
     @contextmanager
     def retainSelection(self):
         """"
diff --git a/gooey/gui/components/widgets/dropdown_filterable.py b/gooey/gui/components/widgets/dropdown_filterable.py
index 1737684c..4babdf1b 100644
--- a/gooey/gui/components/widgets/dropdown_filterable.py
+++ b/gooey/gui/components/widgets/dropdown_filterable.py
@@ -1,12 +1,15 @@
 from contextlib import contextmanager
 
-import wx
+import wx  # type: ignore
+import wx.html  # type: ignore
 
 import gooey.gui.events as events
+from gooey.gui.components.filtering.prefix_filter import PrefixSearch
+from gooey.gui.components.mouse import notifyMouseEvent
 from gooey.gui.components.widgets.dropdown import Dropdown
 from gooey.gui.lang.i18n import _
 from gooey.gui.pubsub import pub
-from gooey.gui.components.mouse import notifyMouseEvent
+from gooey.python_bindings import types as t
 
 __ALL__ = ('FilterableDropdown',)
 
@@ -14,7 +17,6 @@
 class FilterableDropdown(Dropdown):
     """
     TODO: tests for gooey_options
-    TODO: better search strategy than linear
     TODO: documentation
     A dropdown with auto-complete / filtering behaviors.
 
@@ -33,8 +35,8 @@ class FilterableDropdown(Dropdown):
 
     FAQ:
     Q: Why does it slide down rather than hover over elements like the native ComboBox?
-    A: The only mecahnism for layering in WX is the wx.PopupTransientWindow. There's a long
-       standing issue in wxPython which prevents ListBox/Ctrl from capturing events when
+    A: The only mechanism for layering in WX is the wx.PopupTransientWindow. There's a long
+       standing issue in wxPython which prevents Listbox/Ctrl from capturing events when
        inside of a PopupTransientWindow (see: https://tinyurl.com/y28ngh7v)
 
     Q: Why is visibility handled by changing its size rather than using Show/Hide?
@@ -63,15 +65,22 @@ def interpretState(self, model):
         """
         if self.widget.GetValue() != self.model.displayValue:
             self.widget.ChangeValue(model.displayValue)
-        if self.listbox.GetItems() != self.model.suggestions:
-            self.listbox.SetItems(model.suggestions)
+
+        self.listbox.Clear()
+        self.listbox.SetItemCount(len(self.model.suggestions))
+        if len(self.model.suggestions) == 1:
+            # I have no clue why this is required, but without
+            # manually flicking the virtualized listbox off/on
+            # it won't paint the update when there's only a single
+            # item being displayed
+            self.listbox.Show(False)
+            self.listbox.Show(self.model.suggestionsVisible)
         if model.selectedSuggestion > -1:
             self.listbox.SetSelection(model.selectedSuggestion)
             self.widget.SetInsertionPoint(-1)
             self.widget.SetSelection(999, -1)
         else:
             self.listbox.SetSelection(-1)
-        self.listbox.SetMaxSize(self.model.maxSize)
         self.estimateBestSize()
         self.listbox.Show(self.model.suggestionsVisible)
         self.Layout()
@@ -92,7 +101,8 @@ def getWidget(self, parent, *args, **options):
         self.comboCtrl.OnButtonClick = self.onButton
         self.foo = ListCtrlComboPopup()
         self.comboCtrl.SetPopupControl(self.foo)
-        self.listbox = wx.ListBox(self, choices=self._meta['choices'], style=wx.LB_SINGLE)
+        self.listbox = VirtualizedListBox(self)
+        self.listbox.OnGetItem = self.OnGetItem
         # model is created here because the design of these widget
         # classes is broken.
         self.model = FilterableDropdownModel(self._meta['choices'], self._options, listeners=[self.interpretState])
@@ -101,6 +111,28 @@ def getWidget(self, parent, *args, **options):
         self.listbox.AcceptsFocusFromKeyboard = lambda *args, **kwargs: False
         return self.comboCtrl
 
+    def getUiState(self) -> t.FormField:
+        widget: wx.ComboBox = self.widget
+        return t.DropdownFilterable(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.model.actualValue,
+            choices=self.model.choices,
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+    def syncUiState(self, state: t.DropdownFilterable):  # type: ignore
+        self.setOptions(state['choices'])
+        if state['value'] is not None:
+            self.setValue(state['value'])
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+
+    def OnGetItem(self, n):
+        return self.model.suggestions[n]
+
     def getSublayout(self, *args, **kwargs):
         verticalSizer = wx.BoxSizer(wx.VERTICAL)
         layout = wx.BoxSizer(wx.HORIZONTAL)
@@ -127,7 +159,7 @@ def onButton(self):
             self.model.showSuggestions()
 
     def onClickSuggestion(self, event):
-        self.model.acceptSuggestion(event.String)
+        self.model.acceptSuggestion(self.model.suggestions[event.Selection])
         event.Skip()
 
     def onMouseClick(self, wxEvent):
@@ -162,7 +194,7 @@ def onKeyboardControls(self, event):
                 self.model.generateSuggestions(self.model.displayValue)
                 self.model.showSuggestions()
             else:
-                if self.listbox.GetItems()[0] != self.model.noMatch:
+                if self.listbox.OnGetItem(0) != self.model.noMatch:
                     self.ignore = True
                     if event.GetKeyCode() == wx.WXK_DOWN:
                         self.model.incSelectedSuggestion()
@@ -182,14 +214,26 @@ def estimateBestSize(self):
         of items within it. This is a rough estimate based on the
         current font size.
         """
-        padding = 7
+        padding = 11
         rowHeight = self.listbox.GetFont().GetPixelSize()[1] + padding
         maxHeight = self.model.maxSize[1]
-        self.listbox.SetMaxSize((-1, min(maxHeight, len(self.listbox.GetItems()) * rowHeight)))
+        self.listbox.SetMaxSize((-1, min(maxHeight, len(self.model.suggestions) * rowHeight)))
+        self.listbox.SetMinSize((-1, min(maxHeight, len(self.model.suggestions) * rowHeight)))
         self.listbox.SetSize((-1, -1))
 
 
 
+class VirtualizedListBox(wx.html.HtmlListBox):
+    def __init__(self, *args, **kwargs):
+        super(VirtualizedListBox, self).__init__(*args, **kwargs)
+        self.SetItemCount(1)
+
+    def OnGetItem(self, n):
+        return ''
+
+
+
+
 class FilterableDropdownModel(object):
     """
     The model/state for the FilterableDropdown. While this is still one
@@ -209,10 +253,11 @@ def __init__(self, choices, options, listeners=[], *args, **kwargs):
         self.suggestionsVisible = False
         self.noMatch = options.get('no_matches', _('dropdown.no_matches'))
         self.choices = choices
-        self.suggestions = []
+        self.suggestions = choices
         self.selectedSuggestion = -1
         self.suggestionsVisible = False
         self.maxSize = (-1, options.get('max_size', 80))
+        self.strat = PrefixSearch(choices, options.get('search_strategy', {}))
 
     def __str__(self):
         return str(vars(self))
@@ -229,6 +274,7 @@ def updateChoices(self, choices):
         """Update the available choices in response
         to a dynamic update"""
         self.choices = choices
+        self.strat.updateChoices(choices)
 
     def handleTextInput(self, value):
         if self.dropEvent:
@@ -237,6 +283,7 @@ def handleTextInput(self, value):
             with self.notify():
                 self.actualValue = value
                 self.displayValue = value
+                self.selectedSuggestion = -1
                 self.generateSuggestions(value)
                 self.suggestionsVisible = True
 
@@ -264,8 +311,7 @@ def ignoreSuggestions(self):
             self.selectedSuggestion = -1
 
     def generateSuggestions(self, prompt):
-        prompt = prompt.lower()
-        suggestions = [choice for choice in self.choices if choice.lower().startswith(prompt)]
+        suggestions = self.strat.findMatches(prompt)
         final_suggestions = suggestions if suggestions else [self.noMatch]
         self.suggestions = final_suggestions
 
diff --git a/gooey/gui/components/widgets/listbox.py b/gooey/gui/components/widgets/listbox.py
index 357f1196..c3f4143b 100644
--- a/gooey/gui/components/widgets/listbox.py
+++ b/gooey/gui/components/widgets/listbox.py
@@ -1,7 +1,8 @@
-import wx
+import wx  # type: ignore
 
 from gooey.gui import formatters
 from gooey.gui.components.widgets.bases import TextContainer
+from gooey.python_bindings import types as t
 
 
 class Listbox(TextContainer):
@@ -30,3 +31,24 @@ def getWidgetValue(self):
 
     def formatOutput(self, metadata, value):
         return formatters.listbox(metadata, value)
+
+    def getUiState(self) -> t.FormField:
+        widget: wx.ComboBox = self.widget
+        return t.Listbox(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            selected=self.getWidgetValue(),
+            choices=self._meta['choices'],
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+    def syncUiState(self, state: t.Listbox):  # type: ignore
+        widget: wx.ComboBox = self.widget
+        widget.Clear()
+        widget.AppendItems(state.get('choices', []))
+        for string in state['selected']:
+            widget.SetStringSelection(string)
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
diff --git a/gooey/gui/components/widgets/numeric_fields.py b/gooey/gui/components/widgets/numeric_fields.py
new file mode 100644
index 00000000..c5bb1c77
--- /dev/null
+++ b/gooey/gui/components/widgets/numeric_fields.py
@@ -0,0 +1,75 @@
+import wx  # type: ignore
+
+from gooey.gui import formatters
+from gooey.gui.components.widgets.bases import TextContainer
+from gooey.python_bindings import types as t
+
+class IntegerField(TextContainer):
+    """
+    An integer input field
+    """
+    widget_class = wx.SpinCtrl
+    def getWidget(self, *args, **options):
+        widget = self.widget_class(self,
+                             value='',
+                             min=self._options.get('min', 0),
+                             max=self._options.get('max', 100))
+        return widget
+
+    def getWidgetValue(self):
+        return self.widget.GetValue()
+
+    def setValue(self, value):
+        self.widget.SetValue(int(value))
+
+    def formatOutput(self, metatdata, value):
+        # casting to string so that the generic formatter
+        # doesn't treat 0 as false/None
+        return formatters.general(metatdata, str(value))
+
+    def getUiState(self) -> t.FormField:
+        widget: wx.SpinCtrl = self.widget
+        return t.IntegerField(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.getWidgetValue(),
+            min=widget.GetMin(),
+            max=widget.GetMax(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+class DecimalField(IntegerField):
+    """
+    A decimal input field
+    """
+    widget_class = wx.SpinCtrlDouble
+
+    def getWidget(self, *args, **options):
+        widget = self.widget_class(self,
+                             value='',
+                             min=self._options.get('min', 0),
+                             max=self._options.get('max', 100),
+                             inc=self._options.get('increment', 0.01))
+        widget.SetDigits(self._options.get('precision', widget.GetDigits()))
+        return widget
+
+
+    def setValue(self, value):
+        self.widget.SetValue(value)
+
+    def getUiState(self) -> t.FormField:
+        widget: wx.SpinCtrlDouble = self.widget
+        return t.IntegerField(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.getWidgetValue(),
+            min=widget.GetMin(),
+            max=widget.GetMax(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
+
+
diff --git a/gooey/gui/components/widgets/password.py b/gooey/gui/components/widgets/password.py
index e132f23a..1c0e82a1 100644
--- a/gooey/gui/components/widgets/password.py
+++ b/gooey/gui/components/widgets/password.py
@@ -1,12 +1,15 @@
 from gooey.gui.components.widgets.core.text_input import PasswordInput
 from gooey.gui.components.widgets.textfield import TextField
-
+from gooey.python_bindings import types as t
 
 __ALL__ = ('PasswordField',)
 
 class PasswordField(TextField):
-    widget_class = PasswordInput
+    widget_class = PasswordInput  # type: ignore
 
     def __init__(self, *args, **kwargs):
         super(PasswordField, self).__init__(*args, **kwargs)
 
+    def getUiState(self) -> t.FormField:  # type: ignore
+        return t.PasswordField(**super().getUiState())  # type: ignore
+
diff --git a/gooey/gui/components/widgets/radio_group.py b/gooey/gui/components/widgets/radio_group.py
index 2c52172d..7be743ad 100644
--- a/gooey/gui/components/widgets/radio_group.py
+++ b/gooey/gui/components/widgets/radio_group.py
@@ -1,12 +1,17 @@
-import wx
+from typing import Optional
+
+import wx  # type: ignore
 from gooey.gui.components.widgets.bases import BaseWidget
 from gooey.gui.lang.i18n import _
 from gooey.gui.util import wx_util
 from gooey.gui.components.widgets import CheckBox
-from gooey.util.functional import getin, findfirst, merge
+from gooey.util.functional import getin, merge
+from gooey.python_bindings import types as t
 
 
 class RadioGroup(BaseWidget):
+    """
+    """
 
     def __init__(self, parent, widgetInfo, *args, **kwargs):
         super(RadioGroup, self).__init__(parent, *args, **kwargs)
@@ -43,6 +48,33 @@ def getValue(self):
             # not active so that the expected interface is satisfied
             return self.widgets[0].getValue()
 
+
+    def syncUiState(self, state: t.RadioGroup):
+        if state['selected'] is not None:
+            self.radioButtons[state['selected']].SetValue(True)
+        for option, widget in zip(state['options'], self.widgets):
+            widget.syncUiState(option)
+        # Fit required here to force WX to actually
+        # show newly Enabled/Shown things for some reason.
+        self.Fit()
+
+    def getUiState(self):
+        return t.RadioGroup(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            error=self.error.GetLabel(),
+            enabled=self.Enabled,
+            visible=self.Shown,
+            selected=self.getSelectedIndex(),
+            options=[x.getUiState() for x in self.widgets]
+        )
+
+    def getSelectedIndex(self) -> Optional[int]:
+        for index, btn in enumerate(self.radioButtons):
+            if btn.GetValue():
+                return index
+        return None
+
     def setErrorString(self, message):
         for button, widget in zip(self.radioButtons, self.widgets):
             if button.GetValue():  # is Checked
@@ -111,16 +143,24 @@ def applyStyleRules(self):
         # state of all the buttons and resetting each button's state as we go.
         # it's wonky as hell
         states = [x.GetValue() for x in self.radioButtons]
+        for widget in self.widgets:
+            widget.Enable()
         for button, selected, widget in zip(self.radioButtons, states, self.widgets):
             if isinstance(widget, CheckBox):
                 widget.hideInput()
             if not selected: # not checked
                 widget.Disable()
             else:
-                widget.Enable()
+                # More "I don't understand" style code
+                # Under some conditions, Enable() doesn't cascade
+                # as listed in the docs. We have to manually drill
+                # into the children to enable everything.
+                widget = widget
+                while widget:
+                    widget.Enable()
+                    widget = getattr(widget, 'widget', None)
             button.SetValue(selected)
 
-
     def handleImplicitCheck(self):
         """
         Checkboxes are hidden when inside of a RadioGroup as a selection of
diff --git a/gooey/gui/components/widgets/richtextconsole.py b/gooey/gui/components/widgets/richtextconsole.py
index ee01eb0a..66a9f22a 100644
--- a/gooey/gui/components/widgets/richtextconsole.py
+++ b/gooey/gui/components/widgets/richtextconsole.py
@@ -1,7 +1,9 @@
-import wx
-import wx.richtext
-import colored
+import wx  # type: ignore
+import wx.richtext  # type: ignore
+import colored  # type: ignore
 import re
+from gooey.python_bindings import types as t
+
 
 kColorList = ["#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0",
     "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", "#000000",
diff --git a/gooey/gui/components/widgets/slider.py b/gooey/gui/components/widgets/slider.py
new file mode 100644
index 00000000..583e8fa9
--- /dev/null
+++ b/gooey/gui/components/widgets/slider.py
@@ -0,0 +1,40 @@
+import wx  # type: ignore
+
+from gooey.gui import formatters
+from gooey.gui.components.widgets.bases import TextContainer
+from gooey.python_bindings import types as t
+
+
+class Slider(TextContainer):
+    """
+    An integer input field
+    """
+    widget_class = wx.Slider
+    def getWidget(self, *args, **options):
+        widget = self.widget_class(self,
+                                   minValue=self._options.get('min', 0),
+                                   maxValue=self._options.get('max', 100),
+                                   style=wx.SL_MIN_MAX_LABELS | wx.SL_VALUE_LABEL)
+        return widget
+
+    def getWidgetValue(self):
+        return self.widget.GetValue()
+
+    def setValue(self, value):
+        self.widget.SetValue(value)
+
+    def formatOutput(self, metatdata, value):
+        return formatters.general(metatdata, str(value))
+
+    def getUiState(self) -> t.FormField:
+        widget: wx.Slider = self.widget
+        return t.Slider(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.getWidgetValue(),
+            min=widget.GetMin(),
+            max=widget.GetMax(),
+            error=self.error.GetLabel() or None,
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
diff --git a/gooey/gui/components/widgets/textarea.py b/gooey/gui/components/widgets/textarea.py
index fe1cd408..27dc42af 100644
--- a/gooey/gui/components/widgets/textarea.py
+++ b/gooey/gui/components/widgets/textarea.py
@@ -1,10 +1,13 @@
-import wx
+import os
+import wx  # type: ignore
 from functools import reduce
 
 from gooey.gui.components.widgets.core.text_input import MultilineTextInput
 from gooey.gui.components.widgets.textfield import TextField
 from gooey.gui.components.widgets.bases import TextContainer
 from gooey.gui import formatters
+from gooey.python_bindings import types as t
+from gooey.python_bindings.types import FormField
 
 
 class Textarea(TextContainer):
@@ -32,7 +35,21 @@ def setValue(self, value):
         self.widget.AppendText(str(value))
         self.widget.SetInsertionPoint(0)
 
-    def formatOutput(self, metatdata, value):
-        return formatters.general(metatdata, value)
-
-
+    def formatOutput(self, metatdata, value: str):
+        return formatters.general(metatdata, value.replace('\n', os.linesep))
+
+    def syncUiState(self, state: FormField):
+        self.setValue(state['value'])  # type: ignore
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+
+    def getUiState(self) -> t.FormField:
+        return t.TextField(
+            id=self._id,
+            type=self.widgetInfo['type'],
+            value=self.getWidgetValue(),
+            placeholder=self.widget.GetHint(),
+            error=self.error.GetLabel().replace('\n', ' '),
+            enabled=self.IsEnabled(),
+            visible=self.IsShown()
+        )
diff --git a/gooey/gui/components/widgets/textfield.py b/gooey/gui/components/widgets/textfield.py
index b286bfb2..5b5955a1 100644
--- a/gooey/gui/components/widgets/textfield.py
+++ b/gooey/gui/components/widgets/textfield.py
@@ -1,10 +1,9 @@
-import wx
+import wx  # type: ignore
+
+from gooey.gui import formatters
 from gooey.gui.components.widgets.bases import TextContainer
-from gooey.gui import formatters, events
 from gooey.gui.components.widgets.core.text_input import TextInput
-from gooey.gui.pubsub import pub
-from gooey.util.functional import getin
-
+from gooey.python_bindings import types as t
 
 class TextField(TextContainer):
     widget_class = TextInput
@@ -15,6 +14,18 @@ def getWidgetValue(self):
     def setValue(self, value):
         self.widget.setValue(str(value))
 
+    def setPlaceholder(self, value):
+        self.widget.SetHint(value)
+
     def formatOutput(self, metatdata, value):
         return formatters.general(metatdata, value)
 
+    def syncUiState(self, state: t.TextField):  # type: ignore
+        textctr: wx.TextCtrl = self.widget.widget
+        textctr.SetValue(state['value'])
+        textctr.SetHint(state['placeholder'])
+        textctr.Enable(state['enabled'])
+        self.Show(state['visible'])
+        self.error.SetLabel(state['error'] or '')
+        self.error.Show(state['error'] is not None and state['error'] is not '')
+        self.Layout()
diff --git a/gooey/gui/constants.py b/gooey/gui/constants.py
new file mode 100644
index 00000000..51f8e321
--- /dev/null
+++ b/gooey/gui/constants.py
@@ -0,0 +1,4 @@
+
+VALUE_PLACEHOLDER = '::gooey/placeholder'
+RADIO_PLACEHOLDER = '::gooey/radio-placeholder'
+
diff --git a/gooey/gui/containers/application.py b/gooey/gui/containers/application.py
index d0d59d10..b232f0ec 100644
--- a/gooey/gui/containers/application.py
+++ b/gooey/gui/containers/application.py
@@ -1,12 +1,37 @@
 """
 Primary orchestration and control point for Gooey.
 """
-
+import queue
 import sys
-
-import wx
-from wx.adv import TaskBarIcon
-
+import threading
+from contextlib import contextmanager
+from functools import wraps
+from json import JSONDecodeError
+from pprint import pprint
+from subprocess import CalledProcessError
+from threading import Thread, get_ident
+from typing import Mapping, Dict, Type, Iterable
+
+import six
+import wx  # type: ignore
+
+from gooey.gui.state import FullGooeyState
+from gooey.python_bindings.types import PublicGooeyState
+from rewx.widgets import set_basic_props
+
+from gooey.gui.components.mouse import notifyMouseEvent
+from gooey.gui.state import initial_state, present_time, form_page, ProgressEvent, TimingEvent
+from gooey.gui import state as s
+from gooey.gui.three_to_four import Constants
+from rewx.core import Component, Ref, updatewx, patch
+from typing_extensions import TypedDict
+
+from rewx import wsx, render, create_element, mount, update
+from rewx import components as c
+from wx.adv import TaskBarIcon  # type: ignore
+import signal
+
+from gooey import Events
 from gooey.gui import cli
 from gooey.gui import events
 from gooey.gui import seeder
@@ -25,7 +50,17 @@
 from gooey.gui.util import wx_util
 from gooey.gui.util.wx_util import transactUI
 from gooey.python_bindings import constants
+from gooey.python_bindings.types import Failure, Success, CommandDetails, Try
+from gooey.util.functional import merge, associn, assoc
+from gooey.gui.image_repository import loadImages
+from gooey.gui import host
+
 
+from threading import Lock
+
+from gooey.util.functional import associnMany
+
+lock = Lock()
 
 class GooeyApplication(wx.Frame):
     """
@@ -45,6 +80,27 @@ def __init__(self, buildSpec, *args, **kwargs):
         self.navbar = self.buildNavigation()
         self.footer = Footer(self, buildSpec)
         self.console = Console(self, buildSpec)
+
+        self.props = {
+            'background_color': self.buildSpec['header_bg_color'],
+            'title': self.buildSpec['program_name'],
+            'subtitle': self.buildSpec['program_description'],
+            'height': self.buildSpec['header_height'],
+            'image_uri': self.buildSpec['images']['configIcon'],
+            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)}
+
+        state = form_page(initial_state(self.buildSpec))
+
+        self.fprops = {
+            'buttons': state['buttons'],
+            'progress': state['progress'],
+            'timing': state['timing'],
+            'bg_color': self.buildSpec['footer_bg_color']
+        }
+
+        # self.hhh = render(create_element(RHeader, self.props), self)
+        # self.fff = render(create_element(RFooter, self.fprops), self)
+        # patch(self.hhh, create_element(RHeader, {**self.props, 'image_uri': self.buildSpec['images']['runningIcon']}))
         self.layoutComponent()
         self.timer = Timing(self)
 
@@ -54,6 +110,7 @@ def __init__(self, buildSpec, *args, **kwargs):
             self.buildSpec.get('hide_progress_msg'),
             self.buildSpec.get('encoding'),
             self.buildSpec.get('requires_shell'),
+            self.buildSpec.get('shutdown_signal', signal.SIGTERM)
         )
 
         pub.subscribe(events.WINDOW_START, self.onStart)
@@ -67,60 +124,87 @@ def __init__(self, buildSpec, *args, **kwargs):
         pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
         pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining)
         # Top level wx close event
-        self.Bind(wx.EVT_CLOSE, self.onClose)
+        # self.Bind(wx.EVT_CLOSE, self.onClose)
 
-        if self.buildSpec['poll_external_updates']:
-            self.fetchExternalUpdates()
+        # TODO: handle child focus for per-field level validation.
+        # self.Bind(wx.EVT_CHILD_FOCUS, self.handleFocus)
 
         if self.buildSpec.get('auto_start', False):
             self.onStart()
 
+
     def applyConfiguration(self):
         self.SetTitle(self.buildSpec['program_name'])
         self.SetBackgroundColour(self.buildSpec.get('body_bg_color'))
 
+
     def onStart(self, *args, **kwarg):
         """
         Verify user input and kick off the client's program if valid
         """
+        # navigates away from the button because a
+        # disabled focused button still looks enabled.
+        self.footer.cancel_button.Disable()
+        self.footer.start_button.Disable()
+        self.footer.start_button.Navigate()
+        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):
+            # TODO: make this wx thread safe so that it can
+            # actually run asynchronously
+            Thread(target=self.onStartAsync).run()
+        else:
+            Thread(target=self.onStartAsync).run()
+
+    def onStartAsync(self, *args, **kwargs):
         with transactUI(self):
-            config = self.navbar.getActiveConfig()
-            config.resetErrors()
-            if config.isValid():
-                if self.buildSpec['clear_before_run']:
-                    self.console.clear()
-                self.clientRunner.run(self.buildCliString())
-                self.showConsole()
-            else:
-                config.displayErrors()
-                self.Layout()
-        
+            try:
+                errors = self.validateForm().getOrThrow()
+                if errors:  # TODO
+                    config = self.navbar.getActiveConfig()
+                    config.setErrors(errors)
+                    self.Layout()
+                    # TODO: account for tabbed layouts
+                    # TODO: scroll the first error into view
+                    # TODO: rather than just snapping to the top
+                    self.configs[0].Scroll(0, 0)
+                else:
+                    if self.buildSpec['clear_before_run']:
+                        self.console.clear()
+                    self.clientRunner.run(self.buildCliString())
+                    self.showConsole()
+            except CalledProcessError as e:
+                self.showError()
+                self.console.appendText(str(e))
+                self.console.appendText(
+                    '\n\nThis failure happens when Gooey tries to invoke your '
+                    'code for the VALIDATE_FORM event and receives an expected '
+                    'error code in response.'
+                )
+                wx.CallAfter(modals.showFailure)
+            except JSONDecodeError as e:
+                self.showError()
+                self.console.appendText(str(e))
+                self.console.appendText(
+                    '\n\nGooey was unable to parse the response to the VALIDATE_FORM event. '
+                    'This can happen if you have additional logs to stdout beyond what Gooey '
+                    'expects.'
+                )
+                wx.CallAfter(modals.showFailure)
+            # for some reason, we have to delay the re-enabling of
+            # the buttons by a few ms otherwise they pickup pending
+            # events created while they were disabled. Trial and error
+            # let to this solution.
+            wx.CallLater(20, self.footer.start_button.Enable)
+            wx.CallLater(20, self.footer.cancel_button.Enable)
+
+
     def onEdit(self):
         """Return the user to the settings screen for further editing"""
         with transactUI(self):
-            if self.buildSpec['poll_external_updates']:
-                self.fetchExternalUpdates()
+            for config in self.configs:
+                config.resetErrors()
             self.showSettings()
 
 
-    def buildCliString(self):
-        """
-        Collect all of the required information from the config screen and
-        build a CLI string which can be used to invoke the client program
-        """
-        config = self.navbar.getActiveConfig()
-        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
-        positional = config.getPositionalArgs()
-        optional = config.getOptionalArgs()
-        return cli.buildCliString(
-            self.buildSpec['target'],
-            group['command'],
-            positional,
-            optional,
-            suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']
-        )
-
-
     def onComplete(self, *args, **kwargs):
         """
         Display the appropriate screen based on the success/fail of the
@@ -173,6 +257,60 @@ def onClose(self, *args, **kwargs):
         else:
             self.destroyGooey()
 
+    def buildCliString(self) -> str:
+        """
+        Collect all of the required information from the config screen and
+        build a CLI string which can be used to invoke the client program
+        """
+        cmd = self.getCommandDetails()
+        return cli.cliCmd(
+            cmd.target,
+            cmd.subcommand,
+            cmd.positionals,
+            cmd.optionals,
+            suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']
+        )
+
+    def validateForm(self) -> Try[Mapping[str, str]]:
+        config = self.navbar.getActiveConfig()
+        localErrors: Mapping[str, str] = config.getErrors()
+        dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()
+
+        combineErrors = lambda m: merge(localErrors, m)
+        return dynamicResult.map(combineErrors)
+
+
+    def fetchDynamicValidations(self) -> Try[Mapping[str, str]]:
+        # only run the dynamic validation if the user has
+        # specifically subscribed to that event
+        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):
+            cmd = self.getCommandDetails()
+            return seeder.communicate(cli.formValidationCmd(
+                cmd.target,
+                cmd.subcommand,
+                cmd.positionals,
+                cmd.optionals
+            ), self.buildSpec['encoding'])
+        else:
+            # shim response if nothing to do.
+            return Success({})
+
+
+    def getCommandDetails(self) -> CommandDetails:
+        """
+        Temporary helper for getting the state of the current Config.
+
+        To be deprecated upon (the desperately needed) refactor.
+        """
+        config = self.navbar.getActiveConfig()
+        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
+        return CommandDetails(
+            self.buildSpec['target'],
+            group['command'],
+            config.getPositionalValues(),
+            config.getOptionalValues(),
+        )
+
 
     def shouldStopExecution(self):
         return not self.buildSpec['show_stop_warning'] or modals.confirmForceStop()
@@ -182,27 +320,20 @@ def destroyGooey(self):
         self.Destroy()
         sys.exit()
 
-    def fetchExternalUpdates(self):
-        """
-        !Experimental!
-        Calls out to the client code requesting seed values to use in the UI
-        !Experimental!
-        """
-        seeds = seeder.fetchDynamicProperties(
-            self.buildSpec['target'],
-            self.buildSpec['encoding']
-        )
-        for config in self.configs:
-            config.seedUI(seeds)
+    def block(self, **kwargs):
+        pass
+
 
     def layoutComponent(self):
         sizer = wx.BoxSizer(wx.VERTICAL)
+        # sizer.Add(self.hhh, 0, wx.EXPAND)
         sizer.Add(self.header, 0, wx.EXPAND)
         sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
 
         sizer.Add(self.navbar, 1, wx.EXPAND)
         sizer.Add(self.console, 1, wx.EXPAND)
         sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
+        # sizer.Add(self.fff, 0, wx.EXPAND)
         sizer.Add(self.footer, 0, wx.EXPAND)
         self.SetMinSize((400, 300))
         self.SetSize(self.buildSpec['default_size'])
@@ -222,7 +353,6 @@ def layoutComponent(self):
             self.taskbarIcon.SetIcon(icon)
 
 
-
     def buildNavigation(self):
         """
         Chooses the appropriate layout navigation component based on user prefs
@@ -261,7 +391,8 @@ def showConsole(self):
         self.header.setTitle(_("running_title"))
         self.header.setSubtitle(_('running_msg'))
         self.footer.showButtons('stop_button')
-        self.footer.progress_bar.Show(True)
+        if not self.buildSpec.get('disable_progress_bar_animation', False):
+            self.footer.progress_bar.Show(True)
         self.footer.time_remaining_text.Show(False)
         if self.buildSpec.get('timing_options')['show_time_remaining']:
             self.timer.start()
@@ -285,7 +416,6 @@ def showComplete(self):
             self.footer.time_remaining_text.Show(False)
 
 
-
     def showSuccess(self):
         self.showComplete()
         self.header.setImage('check_mark')
@@ -310,3 +440,7 @@ def showForceStopped(self):
         self.header.setSubtitle(_('finished_forced_quit'))
 
 
+
+
+
+
diff --git a/gooey/gui/events.py b/gooey/gui/events.py
index bca961ce..9e35727d 100644
--- a/gooey/gui/events.py
+++ b/gooey/gui/events.py
@@ -5,7 +5,7 @@
 that tie everything together.
 """
 
-import wx
+import wx  # type: ignore
 
 WINDOW_STOP     = wx.Window.NewControlId()
 WINDOW_CANCEL   = wx.Window.NewControlId()
diff --git a/gooey/gui/formatters.py b/gooey/gui/formatters.py
index 93c7b75f..84bec598 100644
--- a/gooey/gui/formatters.py
+++ b/gooey/gui/formatters.py
@@ -3,18 +3,87 @@
 import itertools
 
 from gooey.gui.util.quoting import quote
+from gooey.python_bindings.types import EnrichedItem, FormField
+from gooey.gui.constants import VALUE_PLACEHOLDER, RADIO_PLACEHOLDER
+from gooey.util.functional import assoc, associnMany
+
+
+def value(field: FormField):
+    if field['type'] in ['Checkbox', 'BlockCheckbox']:
+        return field['checked']   # type: ignore
+    elif field['type'] in ['Dropdown', 'Listbox', 'Counter']:
+        return field['selected']   # type: ignore
+    elif field['type'] == 'RadioGroup':
+        if field['selected'] is not None:  # type: ignore
+            return value(field['options'][field['selected']])  # type: ignore
+        else:
+            return None
+    else:
+        return field['value'] # type: ignore
+
+
+def add_placeholder(field: FormField, placeholder=VALUE_PLACEHOLDER):
+    """
+    TODO: Docs about placeholders
+    """
+    if field['type'] in ['Checkbox', 'CheckBox', 'BlockCheckbox']:
+        # there's no sane placeholder we can make for this one, as
+        # it's kind of a nonsensical case: a required optional flag.
+        # We set it to True here, which is equally nonsensical, but
+        # ultimately will allow the validation to pass. We have no
+        # way of passing a placeholder without even MORE monket patching
+        # of the user's parser to rewrite the action type
+        return assoc(field, 'checked', True)
+    elif field['type'] in ['Dropdown', 'Listbox', 'Counter']:
+        return assoc(field, 'selected', placeholder)
+    elif field['type'] == 'RadioGroup':
+        # We arbitrarily attach a placeholder for first RadioGroup option
+        # and mark it as the selected one.
+        return {
+            **field,
+            'selected': 0,
+            'options': [
+                add_placeholder(field['options'][0], placeholder=RADIO_PLACEHOLDER),  # type: ignore
+                *field['options'][1:]  # type: ignore
+            ]
+        }
+    else:
+        return assoc(field, 'value', placeholder)
+
+
+def formatArgument(item: EnrichedItem):
+    if item['type'] in ['Checkbox', 'CheckBox', 'BlockCheckbox']:
+        return checkbox(item['data'], value(item['field']))
+    elif item['type'] == 'MultiFileChooser':
+        return multiFileChooser(item['data'], value(item['field']))
+    elif item['type'] == 'Textarea':
+        return textArea(item['data'], value(item['field']))
+    elif item['type'] == 'CommandField':
+        return textArea(item['data'], value(item['field']))
+    elif item['type'] == 'Counter':
+        return counter(item['data'], value(item['field']))
+    elif item['type'] == 'Dropdown':
+        return dropdown(item['data'], value(item['field']))
+    elif item['type'] == 'Listbox':
+        return listbox(item['data'], value(item['field']))
+    elif item['type'] == 'RadioGroup':
+        selected = item['field']['selected']  # type: ignore
+        if selected is not None:
+            formField = item['field']['options'][selected]  # type: ignore
+            argparseDefinition = item['data']['widgets'][selected]  # type: ignore
+            return formatArgument(assoc(argparseDefinition, 'field', formField))  # type: ignore
+        else:
+            return None
+    else:
+        return general(item['data'], value(item['field']))
 
 
-def checkbox(metadata, value):
-    return metadata['commands'][0] if value else None
+def placeholder(item: EnrichedItem):
+    pass
 
 
-def radioGroup(metadata, value):
-    # TODO
-    try:
-        return self.commands[self._value.index(True)][0]
-    except ValueError:
-        return None
+def checkbox(metadata, value):
+    return metadata['commands'][0] if value else None
 
 
 def multiFileChooser(metadata, value):
diff --git a/gooey/gui/host.py b/gooey/gui/host.py
new file mode 100644
index 00000000..e75efbb1
--- /dev/null
+++ b/gooey/gui/host.py
@@ -0,0 +1,45 @@
+from concurrent.futures import ThreadPoolExecutor
+from threading import Thread
+from typing import Callable, Dict, Any
+
+from gooey.gui import seeder
+from gooey.gui import state as s
+from gooey.gui.state import FullGooeyState
+from gooey.python_bindings.types import Try, PublicGooeyState
+
+
+def communicateFormValidation(state: FullGooeyState, callback: Callable[[Try[Dict[str, str]]], None]) -> None:
+    communicateAsync(s.buildFormValidationCmd(state), state, callback)
+
+
+def communicateSuccessState(state: FullGooeyState, callback: Callable[[Try[PublicGooeyState]], None]) -> None:
+    communicateAsync(s.buildOnSuccessCmd(state), state, callback)
+
+
+def communicateErrorState(state: FullGooeyState, callback: Callable[[Try[PublicGooeyState]], None]) -> None:
+    communicateAsync(s.buildOnErrorCmd(state), state, callback)
+
+
+def fetchFieldValidation():
+    pass
+
+
+
+def fetchFieldAction():
+    pass
+
+def fetchFormAction():
+    pass
+
+
+def communicateAsync(cmd: str, state: FullGooeyState, callback: Callable[[Any], None]):
+    """
+    Callable MUST be wrapped in wx.CallAfter if its going to
+    modify the UI.
+    """
+    def work():
+        result = seeder.communicate(cmd, state['encoding'])
+        callback(result)
+    thread = Thread(target=work)
+    thread.start()
+
diff --git a/gooey/gui/imageutil.py b/gooey/gui/imageutil.py
index 7d112192..4154eecb 100644
--- a/gooey/gui/imageutil.py
+++ b/gooey/gui/imageutil.py
@@ -3,8 +3,8 @@
 '''
 
 import six
-from PIL import Image
-import wx
+from PIL import Image  # type: ignore
+import wx  # type: ignore
 
 from gooey.gui.three_to_four import bitmapFromBufferRGBA
 
diff --git a/gooey/gui/processor.py b/gooey/gui/processor.py
index 1310cf97..d43cda6a 100644
--- a/gooey/gui/processor.py
+++ b/gooey/gui/processor.py
@@ -1,19 +1,43 @@
 import os
 import re
+import signal
 import subprocess
 import sys
 from functools import partial
 from threading import Thread
 
+import psutil  # type: ignore
+
 from gooey.gui import events
 from gooey.gui.pubsub import pub
 from gooey.gui.util.casting import safe_float
-from gooey.gui.util.taskkill import taskkill
 from gooey.util.functional import unit, bind
+from gooey.python_bindings.types import GooeyParams
+
+
+try:
+    import _winapi
+    creationflag = subprocess.CREATE_NEW_PROCESS_GROUP
+except ModuleNotFoundError:
+    # default Popen creation flag
+    creationflag = 0
 
 
 class ProcessController(object):
-    def __init__(self, progress_regex, progress_expr, hide_progress_msg,encoding, shell=True):
+
+    @classmethod
+    def of(cls, params: GooeyParams):
+        return cls(
+            params.get('progress_regex'),
+            params.get('progress_expr'),
+            params.get('hide_progress_msg'),
+            params.get('encoding'),
+            params.get('requires_shell'),
+            params.get('shutdown_signal', signal.SIGTERM)
+        )
+
+    def __init__(self, progress_regex, progress_expr, hide_progress_msg,
+                 encoding, shell=True, shutdown_signal=signal.SIGTERM, testmode=False):
         self._process = None
         self.progress_regex = progress_regex
         self.progress_expr = progress_expr
@@ -21,6 +45,8 @@ def __init__(self, progress_regex, progress_expr, hide_progress_msg,encoding, sh
         self.encoding = encoding
         self.wasForcefullyStopped = False
         self.shell_execution = shell
+        self.shutdown_signal = shutdown_signal
+        self.testMode = testmode
 
     def was_success(self):
         self._process.communicate()
@@ -32,31 +58,58 @@ def poll(self):
         return self._process.poll()
 
     def stop(self):
+        """
+        Sends a signal of the user's choosing (default SIGTERM) to
+        the child process.
+        """
         if self.running():
             self.wasForcefullyStopped = True
-            taskkill(self._process.pid)
+            self.send_shutdown_signal()
+
+    def send_shutdown_signal(self):
+        self._send_signal(self.shutdown_signal)
+
+    def _send_signal(self, sig):
+        parent = psutil.Process(self._process.pid)
+        for child in parent.children(recursive=True):
+            child.send_signal(sig)
+        parent.send_signal(sig)
 
     def running(self):
         return self._process and self.poll() is None
 
     def run(self, command):
+        """
+        Kicks off the user's code in a subprocess.
+
+        Implementation Note: CREATE_NEW_SUBPROCESS is required to have signals behave sanely
+        on windows. See the signal_support module for full background.
+        """
         self.wasForcefullyStopped = False
         env = os.environ.copy()
         env["GOOEY"] = "1"
         env["PYTHONIOENCODING"] = self.encoding
+        # TODO: why is this try/catch here..?
         try:
             self._process = subprocess.Popen(
                 command.encode(sys.getfilesystemencoding()),
                 stdout=subprocess.PIPE, stdin=subprocess.PIPE,
-                stderr=subprocess.STDOUT, shell=self.shell_execution, env=env)
+                stderr=subprocess.STDOUT, shell=self.shell_execution, env=env,
+                creationflags=creationflag)
         except:
             self._process = subprocess.Popen(
                 command,
                 stdout=subprocess.PIPE, stdin=subprocess.PIPE,
-                stderr = subprocess.STDOUT, shell = self.shell_execution, env=env)
+                stderr = subprocess.STDOUT, shell = self.shell_execution, env=env,
+                creationflags=creationflag
+            )
 
-        t = Thread(target=self._forward_stdout, args=(self._process,))
-        t.start()
+        # the message pump depends on the wx instance being initiated and its
+        # mainloop running (to dispatch pubsub messages). This makes testing difficult
+        # so we only spin up the thread when we're not testing.
+        if not self.testMode:
+            t = Thread(target=self._forward_stdout, args=(self._process,))
+            t.start()
 
     def _forward_stdout(self, process):
         '''
@@ -107,3 +160,4 @@ def _eval_progress(self, match):
             return int(eval(self.progress_expr, {}, _locals))
         except:
             return None
+
diff --git a/gooey/gui/pubsub.py b/gooey/gui/pubsub.py
index 41d42b8c..0557244f 100644
--- a/gooey/gui/pubsub.py
+++ b/gooey/gui/pubsub.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 from collections import defaultdict
 
 __ALL__ = ['pub']
diff --git a/gooey/gui/seeder.py b/gooey/gui/seeder.py
index 6add706c..659f99cc 100644
--- a/gooey/gui/seeder.py
+++ b/gooey/gui/seeder.py
@@ -2,22 +2,37 @@
 Util for talking to the client program in order to retrieve
 dynamic defaults for the UI
 """
-import json
 import subprocess
+from json import JSONDecodeError
+from subprocess import CalledProcessError
 
+from gooey.python_bindings.types import Try, Success, Failure
+from gooey.python_bindings.coms import deserialize_inbound
 
-def fetchDynamicProperties(target, encoding):
+
+def communicate(cmd, encoding) -> Try:
     """
-    Sends a gooey-seed-ui request to the client program it retrieve
-    dynamically generated defaults with which to seed the UI
+    Invoke the processes specified by `cmd`.
+    Assumes that the process speaks JSON over stdout. Non-json response
+    are treated as an error.
+
+    Implementation Note: I don't know why, but `Popen` is like ~5-6x faster
+    than `check_output`. in practice, it means waiting for ~1/10th
+    of a second rather than ~7/10ths of a second. A
+    difference which is pretty weighty when there's a
+    user waiting on the other end.
     """
-    # TODO: this needs to apply the same argpase_to_json data cleaning rules
-    cmd = '{} {}'.format(target, 'gooey-seed-ui --ignore-gooey')
-    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-    if proc.returncode != 0:
-        out, _ = proc.communicate()
-        return json.loads(out.decode(encoding))
-    else:
-        # TODO: useful feedback
-        return {}
+    try:
+        proc = subprocess.Popen(
+            cmd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE
+        )
+        out, err = proc.communicate()
+        if out and proc.poll() == 0:
+            return Success(deserialize_inbound(out, encoding))
+        else:
+            return Failure(CalledProcessError(proc.returncode, cmd, output=out, stderr=err))
+    except JSONDecodeError as e:
+        return Failure(e)
 
diff --git a/gooey/gui/state.py b/gooey/gui/state.py
new file mode 100644
index 00000000..83b460e7
--- /dev/null
+++ b/gooey/gui/state.py
@@ -0,0 +1,418 @@
+import json
+from base64 import b64encode
+from typing import Optional, List, Dict, Any, Union, Callable
+
+from typing_extensions import TypedDict
+import wx
+
+from gooey.gui import events
+from gooey.gui.lang.i18n import _
+from gooey.python_bindings.types import GooeyParams, Item, Group, TopLevelParser, EnrichedItem, \
+    FieldValue
+from gooey.util.functional import associn, assoc, associnMany, compact
+from gooey.gui.formatters import formatArgument
+from gooey.python_bindings.types import FormField
+from gooey.gui.constants import VALUE_PLACEHOLDER
+from gooey.gui.formatters import add_placeholder
+from gooey.python_bindings.types import CommandPieces, PublicGooeyState
+
+
+class TimingEvent(TypedDict):
+    elapsed_time: Optional[str]
+    estimatedRemaining: Optional[str]
+
+class ProgressEvent(TypedDict):
+    progress: Optional[int]
+
+class ButtonState(TypedDict):
+    id: str
+    style: str
+    label_id: str
+    show: bool
+    enabled: bool
+
+class ProgressState(TypedDict):
+    show: bool
+    range: int
+    value: int
+
+class TimingState(TypedDict):
+    show: bool
+    elapsedTime: Optional[str]
+    estimated_remaining: Optional[str]
+
+class GooeyState(GooeyParams):
+    fetchingUpdate: bool
+    screen: str
+    title: str
+    subtitle: str
+    images: Dict[str, str]
+    image: str
+    buttons: List[ButtonState]
+    progress: ProgressState
+    timing: TimingState
+    subcommands: List[str]
+    activeSelection: int
+    show_error_alert: bool
+
+class FullGooeyState(GooeyState):
+    forms: Dict[str, List[FormField]]
+    widgets: Dict[str, Dict[str, Any]]
+
+
+
+
+
+def extract_items(groups: List[Group]) -> List[Item]:
+    if not groups:
+        return []
+    group = groups[0]
+    return group['items'] \
+           + extract_items(groups[1:]) \
+           + extract_items(group['groups'])
+
+
+def widgets(descriptor: TopLevelParser) -> List[Item]:
+    return extract_items(descriptor['contents'])
+
+
+def enrichValue(formState: List[FormField], items: List[Item]) -> List[EnrichedItem]:
+    formIndex = {k['id']:k for k in formState}
+    return [EnrichedItem(field=formIndex[item['id']], **item) for item in items]  # type: ignore
+
+
+def positional(items: List[Union[Item, EnrichedItem]]):
+    return [item for item in items if item['cli_type'] == 'positional']
+
+
+def optional(items: List[Union[Item, EnrichedItem]]):
+    return [item for item in items if item['cli_type'] != 'positional']
+
+
+def cli_pieces(state: FullGooeyState, formatter=formatArgument) -> CommandPieces:
+    parserName = state['subcommands'][state['activeSelection']]
+    parserSpec = state['widgets'][parserName]
+    formState = state['forms'][parserName]
+    subcommand = parserSpec['command'] if parserSpec['command'] != '::gooey/default' else ''
+    items = enrichValue(formState, widgets(parserSpec))
+    positional_args = [formatter(item) for item in positional(items)]  # type: ignore
+    optional_args = [formatter(item) for item in optional(items)]      # type: ignore
+    ignoreFlag = '' if state['suppress_gooey_flag'] else '--ignore-gooey'
+    return CommandPieces(
+        target=state['target'],
+        subcommand=subcommand,
+        positionals=compact(positional_args),
+        optionals=compact(optional_args),
+        ignoreFlag=ignoreFlag
+    )
+
+
+def activeFormState(state: FullGooeyState):
+    subcommand = state['subcommands'][state['activeSelection']]
+    return state['forms'][subcommand]
+
+
+def buildInvocationCmd(state: FullGooeyState):
+    pieces = cli_pieces(state)
+    return u' '.join(compact([
+        pieces.target,
+        pieces.subcommand,
+        *pieces.optionals,
+        pieces.ignoreFlag,
+        '--' if pieces.positionals else '',
+        *pieces.positionals]))
+
+
+def buildFormValidationCmd(state: FullGooeyState):
+    pieces = cli_pieces(state, formatter=cmdOrPlaceholderOrNone)
+    serializedForm = json.dumps({'active_form': activeFormState(state)})
+    b64ecoded = b64encode(serializedForm.encode('utf-8'))
+    return ' '.join(compact([
+        pieces.target,
+        pieces.subcommand,
+        *pieces.optionals,
+        '--gooey-validate-form',
+        '--gooey-state ' + b64ecoded.decode('utf-8'),
+        '--' if pieces.positionals else '',
+        *pieces.positionals]))
+
+
+def buildOnCompleteCmd(state: FullGooeyState, was_success: bool):
+    pieces = cli_pieces(state)
+    serializedForm = json.dumps({'active_form': activeFormState(state)})
+    b64ecoded = b64encode(serializedForm.encode('utf-8'))
+    return u' '.join(compact([
+        pieces.target,
+        pieces.subcommand,
+        *pieces.optionals,
+        '--gooey-state ' + b64ecoded.decode('utf-8'),
+        '--gooey-run-is-success' if was_success else '--gooey-run-is-failure',
+        '--' if pieces.positionals else '',
+        *pieces.positionals]))
+
+
+def buildOnSuccessCmd(state: FullGooeyState):
+    return buildOnCompleteCmd(state, True)
+
+def buildOnErrorCmd(state: FullGooeyState):
+    return buildOnCompleteCmd(state, False)
+
+
+def cmdOrPlaceholderOrNone(item: EnrichedItem) -> Optional[str]:
+    # Argparse has a fail-fast-and-exit behavior for any missing
+    # values. This poses a problem for dynamic validation, as we
+    # want to collect _all_ errors to be more useful to the user.
+    # As such, if there is no value currently available, we pass
+    # through a stock placeholder values which allows GooeyParser
+    # to handle it being missing without Argparse exploding due to
+    # it actually being missing.
+    if item['cli_type'] == 'positional':
+        return formatArgument(item) or VALUE_PLACEHOLDER
+    elif item['cli_type'] != 'positional' and item['required']:
+        # same rationale applies here. We supply the argument
+        # along with a fixed placeholder (when relevant i.e. `store`
+        # actions)
+        return formatArgument(item) or formatArgument(assoc(item, 'field', add_placeholder(item['field'])))
+    else:
+        # Optional values are, well, optional. So, like usual, we send
+        # them if present or drop them if not.
+        return formatArgument(item)
+
+
+
+
+def combine(state: GooeyState, params: GooeyParams, formState: List[FormField]) -> FullGooeyState:
+    """
+    I'm leaving the refactor of the form elements to another day.
+    For now, we'll just merge in the state of the form fields as tracked
+    in the UI into the main state blob as needed.
+    """
+    subcommand = list(params['widgets'].keys())[state['activeSelection']]
+    return FullGooeyState(**{
+        **state,
+        **params,
+        'forms': {subcommand: formState}
+    })
+
+
+def enable_buttons(state, to_enable: List[str]):
+    updated = [{**btn, 'enabled': btn['label_id'] in to_enable}
+               for btn in state['buttons']]
+    return assoc(state, 'buttons', updated)
+
+
+
+def activeCommand(state, params: GooeyParams):
+    """
+    Retrieve the active sub-parser command as determined by the
+    current selection.
+    """
+    return list(params['widgets'].keys())[state['activeSelection']]
+
+
+def mergeExternalState(state: FullGooeyState, extern: PublicGooeyState) -> FullGooeyState:
+    # TODO: insane amounts of helpful validation
+    subcommand = state['subcommands'][state['activeSelection']]
+    formItems: List[FormField] = state['forms'][subcommand]
+    hostForm: List[FormField] = extern['active_form']
+    return associn(state, ['forms', subcommand], hostForm)
+
+
+def show_alert(state: FullGooeyState):
+    return assoc(state, 'show_error_alert', True)
+
+def has_errors(state: FullGooeyState):
+    """
+    Searches through the form elements (including down into
+    RadioGroup's internal options to find the presence of
+    any errors.
+    """
+    return any([item['error'] or any(x['error'] for x in item.get('options', []))
+                for items in state['forms'].values()
+                for item in items])
+
+
+def initial_state(params: GooeyParams) -> GooeyState:
+    buttons = [
+        ('cancel', events.WINDOW_CANCEL, wx.ID_CANCEL),
+        ('start', events.WINDOW_START, wx.ID_OK),
+        ('stop', events.WINDOW_STOP, wx.ID_OK),
+        ('edit', events.WINDOW_EDIT,wx.ID_OK),
+        ('restart', events.WINDOW_RESTART, wx.ID_OK),
+        ('close', events.WINDOW_CLOSE, wx.ID_OK),
+    ]
+    # helping out the type system
+    params: Dict[str, Any] = params
+    return GooeyState(
+        **params,
+        fetchingUpdate=False,
+        screen='FORM',
+        title=params['program_name'],
+        subtitle=params['program_description'],
+        image=params['images']['configIcon'],
+        buttons=[ButtonState(
+            id=event_id,
+            style=style,
+            label_id=label,
+            show=label in ('cancel', 'start'),
+            enabled=True)
+            for label, event_id, style in buttons],
+        progress=ProgressState(
+            show=False,
+            range=100,
+            value=0 if params['progress_regex'] else -1
+        ),
+        timing=TimingState(
+            show=False,
+            elapsed_time=None,
+            estimatedRemaining=None,
+        ),
+        show_error_alert=False,
+        subcommands=list(params['widgets'].keys()),
+        activeSelection=0
+    )
+
+def header_props(state, params):
+    return {
+            'background_color': params['header_bg_color'],
+            'title': params['program_name'],
+            'subtitle': params['program_description'],
+            'height': params['header_height'],
+            'image_uri': ims['images']['configIcon'],
+            'image_size': (six.MAXSIZE, params['header_height'] - 10)
+    }
+
+
+def form_page(state):
+    return {
+        **state,
+        'buttons': [{**btn, 'show': btn['label_id'] in ('start', 'cancel')}
+                    for btn in state['buttons']]
+    }
+
+
+def consoleScreen(_: Callable[[str], str], state: GooeyState):
+    return {
+        **state,
+        'screen': 'CONSOLE',
+        'title': _("running_title"),
+        'subtitle': _('running_msg'),
+        'image': state['images']['runningIcon'],
+        'buttons': [{**btn,
+                     'show': btn['label_id'] == 'stop',
+                     'enabled': True}
+                    for btn in state['buttons']],
+        'progress': {
+            'show': not state['disable_progress_bar_animation'],
+            'range': 100,
+            'value': 0 if state['progress_regex'] else -1
+        },
+        'timing': {
+            'show': state['timing_options']['show_time_remaining'],
+            'elapsed_time': None,
+            'estimatedRemaining': None
+        },
+        'show_error_alert': False
+     }
+
+
+def editScreen(_: Callable[[str], str], state: FullGooeyState):
+    use_buttons = ('cancel', 'start')
+    return associnMany(
+        state,
+        ('screen', 'FORM'),
+        ('buttons', [{**btn,
+                      'show': btn['label_id'] in use_buttons,
+                      'enabled': True}
+                     for btn in state['buttons']]),
+        ('image', state['images']['configIcon']),
+        ('title', state['program_name']),
+        ('subtitle', state['program_description']))
+
+
+def beginUpdate(state: GooeyState):
+    return {
+        **enable_buttons(state, ['cancel']),
+        'fetchingUpdate': True
+    }
+
+def finishUpdate(state: GooeyState):
+    return {
+        **enable_buttons(state, ['cancel', 'start']),
+        'fetchingUpdate': False
+    }
+
+
+def finalScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:
+    use_buttons = ('edit', 'restart', 'close')
+    return associnMany(
+        state,
+        ('screen', 'CONSOLE'),
+        ('buttons', [{**btn,
+                      'show': btn['label_id'] in use_buttons,
+                      'enabled': True}
+                     for btn in state['buttons']]),
+        ('image', state['images']['successIcon']),
+        ('title', _('finished_title')),
+        ('subtitle', _('finished_msg')),
+        ('progress.show', False),
+        ('timing.show', not state['timing_options']['hide_time_remaining_on_complete']))
+
+
+def successScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:
+    return associnMany(
+        finalScreen(_, state),
+        ('image', state['images']['successIcon']),
+        ('title', _('finished_title')),
+        ('subtitle', _('finished_msg')))
+
+
+def errorScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:
+    return associnMany(
+        finalScreen(_, state),
+        ('image', state['images']['errorIcon']),
+        ('title', _('finished_title')),
+        ('subtitle', _('finished_error')))
+
+
+def interruptedScreen(_: Callable[[str], str], state: GooeyState):
+    next_state = errorScreen(_, state) if state['force_stop_is_error'] else successScreen(_, state)
+    return assoc(next_state, 'subtitle', _('finished_forced_quit'))
+
+
+def updateProgress(state, event: ProgressEvent):
+    return associn(state, ['progress', 'value'], event['progress'] or 0)
+
+
+def updateTime(state, event):
+    return associnMany(
+        state,
+        ('timing.elapsed_time', event['elapsed_time']),
+        ('timing.estimatedRemaining', event['estimatedRemaining'])
+    )
+
+
+
+
+
+
+def update_time(state, event: TimingEvent):
+    return {
+        **state,
+        'timer': {
+            **state['timer'],
+            'elapsed_time': event['elapsed_time'],
+            'estimatedRemaining': event['estimatedRemaining']
+        }
+    }
+
+
+def present_time(timer):
+    estimate_time_remaining = timer['estimatedRemaining']
+    elapsed_time_value = timer['elapsed_time']
+    if elapsed_time_value is None:
+        return ''
+    elif estimate_time_remaining is not None:
+        return f'{elapsed_time_value}<{estimate_time_remaining}'
+    else:
+        return f'{elapsed_time_value}'
diff --git a/gooey/gui/three_to_four.py b/gooey/gui/three_to_four.py
index 100f7709..5f4c8e80 100644
--- a/gooey/gui/three_to_four.py
+++ b/gooey/gui/three_to_four.py
@@ -2,9 +2,9 @@
 Util for supporting WxPython 3 & 4
 '''
 
-import wx
+import wx  # type: ignore
 try:
-    import wx.adv
+    import wx.adv  # type: ignore
 except ImportError:
     pass
 
diff --git a/gooey/gui/util/filedrop.py b/gooey/gui/util/filedrop.py
index 7b9f355c..a71931a1 100644
--- a/gooey/gui/util/filedrop.py
+++ b/gooey/gui/util/filedrop.py
@@ -1,4 +1,4 @@
-import wx
+import wx  # type: ignore
 
 
 class FileDrop(wx.FileDropTarget):
diff --git a/gooey/gui/util/taskkill.py b/gooey/gui/util/taskkill.py
deleted file mode 100644
index 46e5b578..00000000
--- a/gooey/gui/util/taskkill.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-import os
-import signal
-
-
-if sys.platform.startswith("win"):
-  def taskkill(pid):
-    os.system('taskkill /F /PID {:d} /T >NUL 2>NUL'.format(pid))
-else:  # POSIX
-  import psutil
-  def taskkill(pid):
-    parent = psutil.Process(pid)
-    for child in parent.children(recursive=True):
-      child.kill()
-    parent.kill()
diff --git a/gooey/gui/util/time.py b/gooey/gui/util/time.py
index 89fb98b2..f6644634 100644
--- a/gooey/gui/util/time.py
+++ b/gooey/gui/util/time.py
@@ -1,7 +1,7 @@
 """
 Module for evaluating time elapsed & time remaining from progress
 """
-import wx
+import wx  # type: ignore
 from gooey.gui.pubsub import pub
 from gooey.gui import events
 
diff --git a/gooey/gui/util/wx_util.py b/gooey/gui/util/wx_util.py
index 6d122174..06b0ab8f 100644
--- a/gooey/gui/util/wx_util.py
+++ b/gooey/gui/util/wx_util.py
@@ -1,13 +1,25 @@
 """
 Collection of Utility methods for creating often used, pre-styled wx Widgets
 """
+from functools import wraps
 
-import wx
+import wx  # type: ignore
 from contextlib import contextmanager
 
 from gooey.gui.three_to_four import Constants
 
 
+def callafter(f):
+    """
+    Wraps the supplied function in a wx.CallAfter
+    for Thread-safe interop with WX.
+    """
+    @wraps(f)
+    def inner(*args, **kwargs):
+        wx.CallAfter(f, *args, **kwargs)
+    return inner
+
+
 @contextmanager
 def transactUI(obj):
     """
@@ -21,8 +33,6 @@ def transactUI(obj):
         obj.Thaw()
 
 
-
-
 styles = {
     'h0': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False),
     'h1': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False),
diff --git a/gooey/gui/validation.py b/gooey/gui/validation.py
new file mode 100644
index 00000000..28f94ad7
--- /dev/null
+++ b/gooey/gui/validation.py
@@ -0,0 +1,30 @@
+from typing import Mapping
+
+from gooey import Events
+from gooey.python_bindings.types import Try
+from gooey.util.functional import merge
+
+
+def validateForm(self) -> Try[Mapping[str, str]]:  # or Exception
+    config = self.navbar.getActiveConfig()
+    localErrors: Mapping[str, str] = config.getErrors()
+    dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()
+
+    combineErrors = lambda m: merge(localErrors, m)
+    return dynamicResult.map(combineErrors)
+
+
+def fetchDynamicValidations(self) -> Try[Mapping[str, str]]:
+    # only run the dynamic validation if the user has
+    # specifically subscribed to that event
+    if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):
+        cmd = self.getCommandDetails()
+        return seeder.communicate2(cli.formValidationCmd(
+            cmd.target,
+            cmd.subcommand + 'baba',
+            cmd.positionals,
+            cmd.optionals
+        ), self.buildSpec['encoding'])
+    else:
+        # shim response if nothing to do.
+        return Success({})
\ No newline at end of file
diff --git a/gooey/gui/validators.py b/gooey/gui/validators.py
index c26c2d11..de808a4b 100644
--- a/gooey/gui/validators.py
+++ b/gooey/gui/validators.py
@@ -1,6 +1,6 @@
 
 
-def runValidator(f, value):
+def runValidator(f, value) -> bool:
     """
     Attempt to run the user supplied validation function
 
diff --git a/gooey/languages/dutch.json b/gooey/languages/dutch.json
index 65e2cb83..32e35aad 100644
--- a/gooey/languages/dutch.json
+++ b/gooey/languages/dutch.json
@@ -1,12 +1,23 @@
 {
-  "browse": "Blader",
+  "browse": "Bladeren",
   "cancel": "Annuleren",
+  "checkbox_label": "Inschakelen",
+  "choose_colour": "Kies een kleur",
   "choose_date": "Kies een datum",
   "choose_file": "Kies een bestand",
   "choose_folder": "Kies een map",
+  "choose_folders_msg": "Kies één of meer mappen:",
+  "choose_folders_title": "Mappen bladeren",
   "choose_one": "Kies één",
+  "choose_time": "Kies een tijd",
   "close": "Afsluiten",
   "close_program": "Wilt u het programma afsluiten?",
+  "dialog_button_no": "Nee",
+  "dialog_button_ok": "OK",
+  "dialog_button_yes": "Ja",
+  "dropdown": {
+    "no_matches": "Geen overeenkomsten gevonden"
+  },
   "edit": "Bewerken",
   "enter_filename": "Voer bestandsnaam in",
   "error_required_fields": "Voer alle verplichte velden in!",
@@ -16,13 +27,17 @@
   "finished_forced_quit": "Afgebroken",
   "finished_msg": "Alles is klaar, u kunt het programma veilig afsluiten",
   "finished_title": "Klaar",
+  "ok": "OK",
   "open_file": "Bestand openen",
+  "open_files": "Bestanden openen",
   "optional_args_msg": "Optionele velden",
   "required_args_msg": "Verplichte velden",
   "restart": "Herstarten",
   "running_msg": "Wacht alstublieft tot het programma klaar is, dit kan even duren",
   "running_title": "Bezig",
+  "select_date": "Kies een datum",
   "select_option": "Selecteer een optie",
+  "select_time": "Kies een tijd",
   "settings_title": "Opties",
   "simple_config": "Voer commandoregel-argumenten in",
   "start": "Start",
diff --git a/gooey/languages/portuguese.json b/gooey/languages/portuguese.json
index 37854cf8..ae8ca76e 100644
--- a/gooey/languages/portuguese.json
+++ b/gooey/languages/portuguese.json
@@ -1,12 +1,17 @@
 {
   "browse": "Procurar",
   "cancel": "Cancelar",
-  "choose_date": "(translate me) Choose Date",
-  "choose_file": "(translate me) Choose file",
-  "choose_folder": "(translate me) Choose folder",
-  "choose_one": "(translate me) Choose One",
+  "checkbox_label": "Habilitar",
+  "choose_colour": "Escolha uma cor",
+  "choose_date": "Escolha uma data",
+  "choose_time": "Escolha um horário",
+  "choose_file": "Escolha um arquivo",
+  "choose_folder": "Escolha uma pasta",
+  "choose_folders_msg": "Escolha uma ou mais pastas:",
+  "choose_folders_title": "Procurar por pastas",
+  "choose_one": "Escolha um",
   "close": "Fechar",
-  "close_program": "Fechar o Programa?",
+  "close_program": "Fechar o programa?",
   "edit": "Editar",
   "enter_filename": "Entre com o nome do arquivo",
   "error_required_fields": "Você deve preencher todos os campos na seção obrigatória!",
@@ -16,22 +21,30 @@
   "finished_forced_quit": "Terminado pelo usuário",
   "finished_msg": "Acabou! Você agora pode fechar o programa com segurança.",
   "finished_title": "Finalizado",
+  "dropdown.no_matches": "Nenhum resultado encontrado",
+  "ok": "Ok",
   "open_file": "Abrir arquivo",
+  "open_files": "Abrir arquivos",
   "optional_args_msg": "Argumentos Opcionais",
   "required_args_msg": "Argumentos Obrigatórios",
   "restart": "Reiniciar",
   "running_msg": "Aguarde enquanto a aplicação executa suas tarefas. \nIsto pode demorar alguns instantes",
   "running_title": "Em Execução",
+  "select_date": "Selecione uma data",
+  "select_time": "Selecione um horário",
   "select_option": "Selecione uma opção",
   "settings_title": "Configurações",
-  "simple_config": "Entre com os argumentos de linha de comando",
+  "simple_config": "Insira os argumentos da linha de comando",
   "start": "Iniciar",
   "status": "Status",
   "stop": "Parar",
-  "stop_task": "Interromper a tarefa?",
+  "stop_task": "Parar a execução?",
   "success_message": "Programa finalizado com sucesso!\nPressione OK para sair",
   "sure_you_want_to_exit": "Você tem certeza que deseja sair?",
   "sure_you_want_to_stop": "Você tem certeza que deseja interromper a tarefa?\nA interrupção pode corromper os seus dados!",
-  "uh_oh": "\nOps! Parece que temos um problema. \nEnvie-nos o erro abaixo para que saibamos o que aconteceu.\n\n{} \t\t\n\t\t",
-  "validation_failed": "Um ou mais campos falharam na validação"
+  "uh_oh": "\nOps! Parece que ocorreu um problmea. \nCopie o texto da janela de status e envie para o desenvolvedor para que ele saiba o que deu errado.\n",
+  "validation_failed": "Um ou mais campos não puderam ser validados",
+  "dialog_button_yes": "Sim",
+  "dialog_button_no": "Não",
+  "dialog_button_ok": "OK"
 }
diff --git a/gooey/languages/serbian.json b/gooey/languages/serbian.json
new file mode 100644
index 00000000..da0fb32d
--- /dev/null
+++ b/gooey/languages/serbian.json
@@ -0,0 +1,48 @@
+{
+  "browse": "Pretraži",
+  "cancel": "Otkaži",
+  "checkbox_label": "Uključi",
+  "choose_colour": "Odaberite boju",
+  "choose_date": "Odaberite datum",
+  "choose_file": "Odaberite datoteku",
+  "choose_folder": "Odaberite direktorijum",
+  "choose_folders_msg": "Odaberite jedan ili više direktorijuma:",
+  "choose_folders_title": "Pregled direktorijuma",
+  "choose_one": "Odaberite jedan element",
+  "close": "Zatvori",
+  "close_program": "Zatvori program?",
+  "edit": "Uredi",
+  "enter_filename": "Unesite ime datoteke",
+  "error_required_fields": "Morate popuniti sva polja u obaveznoj sekciji!",
+  "error_title": "Greška",
+  "execution_finished": "Izvršavanje završeno",
+  "finished_error": "Greška se desila prilikom izvršavanja.",
+  "finished_forced_quit": "Izvršavanje prekinuto od strane korisnika",
+  "finished_msg": "Izvršavanje uspešno! Sada možete zatvoriti program.",
+  "finished_title": "Završeno",
+  "dropdown.no_matches": "Nisu pronađeni elementi koji zadovoljavaju kriterijum",
+  "ok": "Ok",
+  "open_file": "Otvori datoteku",
+  "open_files": "Otvori datoteke",
+  "optional_args_msg": "Opcioni argument",
+  "required_args_msg": "Obavezan argument",
+  "restart": "Ponovno pokretanje",
+  "running_msg": "Molimo sačekajte dok aplikacija izvrši sve zadatke. \nOvo može potrajati nekoliko trenutaka",
+  "running_title": "Radni",
+  "select_date": "Odaberite datum",
+  "select_option": "Odaberite opciju",
+  "settings_title": "Podešavanja",
+  "simple_config": "Unesite argumente komandne linije",
+  "start": "Start",
+  "status": "Status",
+  "stop": "Stop",
+  "stop_task": "Zaustavi operaciju?",
+  "success_message": "Program izvršen uspešno!",
+  "sure_you_want_to_exit": "Jeste li sigurni da želite da izađete?",
+  "sure_you_want_to_stop": "Jeste li sigurni da želite zaustaviti izvršavanje zadatka? \nPrekid može učiniti vaše podatke neupotrebljivima!",
+  "uh_oh": "\nOh ne! Izgleda da je došlo do problema. \nKopirajte tekst iz status prozora kako bi pomogli autoru da uvidi i ispravi grešku.\n",
+  "validation_failed": "Jedno ili više polja nisu u skladu sa validacijom.",
+  "dialog_button_yes": "Da",
+  "dialog_button_no": "Ne",
+  "dialog_button_ok": "OK"
+}
diff --git a/gooey/languages/tamil.json b/gooey/languages/tamil.json
new file mode 100644
index 00000000..6659be73
--- /dev/null
+++ b/gooey/languages/tamil.json
@@ -0,0 +1,50 @@
+{
+  "browse": "உலாவு",
+  "cancel": "ரத்துசெய்",
+  "checkbox_label": "இயக்கு",
+  "choose_colour": "வண்ணத்தைத் தேர்வுசெய்க",
+  "choose_date": "தேதியைத் தேர்வுசெய்க",
+  "choose_time": "நேரத்தைத் தேர்வுசெய்க",
+  "choose_file": "கோப்பை தேர்வுசெய்க",
+  "choose_folder": "கோப்புறையைத் தேர்வுசெய்க",
+  "choose_folders_msg": "ஒன்று அல்லது அதற்கு மேற்பட்ட கோப்புறைகளைத் தேர்வுசெய்க:",
+  "choose_folders_title": "கோப்புறைகளுக்கு உலாவுக",
+  "choose_one": "ஒன்றை தேர்ந்தெடு",
+  "close": "மூடு",
+  "close_program": "நிரலை மூடவா?",
+  "edit": "திருத்து",
+  "enter_filename": "கோப்பு பெயரை உள்ளிடவும்",
+  "error_required_fields": "தேவையான பிரிவில் உள்ள அனைத்து உரைப்பகுதியும் நிரப்ப வேண்டும்!",
+  "error_title": "பிழை",
+  "execution_finished": "செயல்படுத்தல் முடிந்தது",
+  "finished_error": "தவறு நிகழ்ந்துவிட்டது.",
+  "finished_forced_quit": "பயனரால் நிறுத்தப்பட்டது",
+  "finished_msg": "அனைத்தும் முடிந்தது! நீங்கள் இப்போது நிரலை பாதுகாப்பாக மூடலாம்.",
+  "finished_title": "முடிந்தது",
+  "dropdown.no_matches": "பொருத்தங்கள் எதுவும் கிடைக்கவில்லை",
+  "ok": "சரி",
+  "open_file": "கோப்பைத் திறக்க",
+  "open_files": "கோப்புகளைத் திறக்க",
+  "optional_args_msg": "விருப்ப வாதங்கள்",
+  "required_args_msg": "தேவையான வாதங்கள்",
+  "restart": "மறுதொடக்கம்",
+  "running_msg": "பயன்பாடு அதன் பணிகளைச் செய்யும்போது காத்திருக்கவும். \nஇதற்கு சில தருணங்கள் ஆகலாம்",
+  "running_title": "இயங்குகிறது",
+  "select_date": "தேதியைத் தேர்ந்தெடுக்கவும்",
+  "select_time": "நேரத்தைத் தேர்ந்தெடுக்கவும்",
+  "select_option": "விருப்பத்தைத் தேர்ந்தெடுக்கவும்",
+  "settings_title": "அமைப்புகள்",
+  "simple_config": "கட்டளை வரி வாதங்களை உள்ளிடவும்",
+  "start": "தொடங்கு",
+  "status": "நிலை",
+  "stop": "நிறுத்து",
+  "stop_task": "பணியை நிறுத்தவா?",
+  "success_message": "நிரல் வெற்றிகரமாக முடிந்தது!",
+  "sure_you_want_to_exit": "நிச்சயமாக நீங்கள் வெளியேற வேண்டுமா?",
+  "sure_you_want_to_stop": "நீங்கள் நிச்சயமாக பணியை நிறுத்த விரும்புகிறீர்களா? \nதடங்கல் உங்கள் தரவை சிதைக்கும்!",
+  "uh_oh": "\nஅட டா! சிக்கல் இருப்பதாகத் தெரிகிறது. \nதவறு என்ன என்பதை உங்கள் படைப்பாளருக்கு தெரியப்படுத்த, நிலை சாளரத்திலிருந்து உரையை நகலெடுக்கவும்.\n",
+  "validation_failed": "ஒன்று அல்லது அதற்கு மேற்பட்ட புலங்கள் சரிபார்ப்பில் தோல்வியுற்றன.",
+  "dialog_button_yes": "ஆம்",
+  "dialog_button_no": "இல்லை",
+  "dialog_button_ok": "சரி"
+}
diff --git a/gooey/python_bindings/argparse_to_json.py b/gooey/python_bindings/argparse_to_json.py
index 5e6ad44b..a209aef5 100644
--- a/gooey/python_bindings/argparse_to_json.py
+++ b/gooey/python_bindings/argparse_to_json.py
@@ -12,14 +12,16 @@
     _StoreFalseAction,
     _StoreTrueAction,
     _StoreAction,
-    _SubParsersAction)
+    _SubParsersAction,
+    _VersionAction, _MutuallyExclusiveGroup)
 from collections import OrderedDict
 from functools import partial
 from uuid import uuid4
 
 from gooey.python_bindings.gooey_parser import GooeyParser
 from gooey.util.functional import merge, getin, identity, assoc
-
+from gooey.gui.components.options.validators import validators
+from gooey.gui.components.options.validators import collect_errors
 
 VALID_WIDGETS = (
     'FileChooser',
@@ -38,7 +40,10 @@
     'Textarea',
     'PasswordField',
     'Listbox',
-    'FilterableDropdown'
+    'FilterableDropdown',
+    'IntegerField',
+    'DecimalField',
+    'Slider'
 )
 
 
@@ -100,8 +105,8 @@ def convert(parser, **kwargs):
 
     group_defaults = {
         'legacy': {
-            'required_cols': kwargs['num_required_cols'],
-            'optional_cols': kwargs['num_optional_cols']
+            'required_cols': kwargs['required_cols'],
+            'optional_cols': kwargs['optional_cols']
         },
         'columns': 2,
         'padding': 10,
@@ -273,16 +278,21 @@ def categorize2(groups, widget_dict, options):
 def categorize(actions, widget_dict, options):
     _get_widget = partial(get_widget, widget_dict)
     for action in actions:
+        if is_version(action):
+            yield action_to_json(action, _get_widget(action, 'CheckBox'), options)
 
-        if is_mutex(action):
+        elif is_mutex(action):
             yield build_radio_group(action, widget_dict, options)
 
         elif is_standard(action):
             yield action_to_json(action, _get_widget(action, 'TextField'), options)
         
-        elif is_file(action):
+        elif is_writemode_file(action):
             yield action_to_json(action, _get_widget(action, 'FileSaver'), options)
 
+        elif is_readmode_file(action):
+            yield action_to_json(action, _get_widget(action, 'FileChooser'), options)
+
         elif is_choice(action):
             yield action_to_json(action, _get_widget(action, 'Dropdown'), options)
 
@@ -348,6 +358,20 @@ def is_file(action):
     ''' action with FileType '''
     return isinstance(action.type, argparse.FileType)
 
+def is_readmode_file(action):
+    return is_file(action) and 'r' in action.type._mode
+
+def is_writemode_file(action):
+    # FileType uses the same modes as the builtin `open`
+    # as such, all modes that aren't explicitly `r` (which is
+    # also the default) are writable or read/writable, thus
+    # making a FileChooser a good choice.
+    return is_file(action) and 'r' not in action.type._mode
+
+def is_version(action):
+    return isinstance(action, _VersionAction) or issubclass(type(action), _VersionAction)
+
+
 def is_standard(action):
     """ actions which are general "store" instructions.
     e.g. anything which has an argument style like:
@@ -359,15 +383,20 @@ def is_standard(action):
     )
     return (not action.choices
             and not isinstance(action.type, argparse.FileType)
-            and not isinstance(action, _CountAction)
-            and not isinstance(action, _HelpAction)
+            and not isinstance(action, (_CountAction, _HelpAction))
+            # subclass checking is to handle the GooeyParser case
+            # where Action get wrapped in a custom class
+            and not issubclass(type(action), boolean_actions)
             and type(action) not in boolean_actions)
 
 
 def is_flag(action):
     """ _actions which are either storeconst, store_bool, etc.. """
+    # TODO: refactor to isinstance tuple form
     action_types = [_StoreTrueAction, _StoreFalseAction, _StoreConstAction]
-    return any(list(map(lambda Action: isinstance(action, Action), action_types)))
+    return (any(list(map(lambda Action: isinstance(action, Action), action_types)))
+            or issubclass(type(action), (_StoreTrueAction, _StoreFalseAction, _StoreConstAction)))
+
 
 
 def is_counter(action):
@@ -388,8 +417,9 @@ def choose_name(name, subparser):
 
 
 def build_radio_group(mutex_group, widget_group, options):
+  dests = [action.dest for action in mutex_group._group_actions]
   return {
-    'id': str(uuid4()),
+    'id': 'group_' + '_'.join(dests),
     'type': 'RadioGroup',
     'cli_type': 'optional',
     'group_name': 'Choose Option',
@@ -419,23 +449,32 @@ def action_to_json(action, widget, options):
 
     base = merge(item_default, {
         'validator': {
+            'type': 'ExpressionValidator',
             'test': validator,
             'message': error_msg
         },
     })
 
-    default = handle_default(action, widget)
+    if (options.get(action.dest) or {}).get('initial_value') != None:
+        value = options[action.dest]['initial_value']
+        options[action.dest]['initial_value'] = handle_initial_values(action, widget, value)
+    default = handle_initial_values(action, widget, action.default)
     if default == argparse.SUPPRESS:
         default = None
 
+
+
+    final_options = merge(base, options.get(action.dest) or {})
+    validate_gooey_options(action, widget, final_options)
+
     return {
-        'id': action.option_strings[0] if action.option_strings else action.dest,
+        'id': action.dest,
         'type': widget,
         'cli_type': choose_cli_type(action),
         'required': action.required,
         'data': {
             'display_name': action.metavar or action.dest,
-            'help': action.help,
+            'help': (action.help or '').replace('%%', '%'),
             'required': action.required,
             'nargs': action.nargs or '',
             'commands': action.option_strings,
@@ -443,11 +482,33 @@ def action_to_json(action, widget, options):
             'default': default,
             'dest': action.dest,
         },
-        'options': merge(base, options.get(action.dest) or {})
+        'options': final_options
     }
 
 
+def validate_gooey_options(action, widget, options):
+    """Very basic field validation / sanity checking for
+    the time being.
 
+    Future plans are to assert against the options and actions together
+    to facilitate checking that certain options like `initial_selection` in
+    RadioGroups map to a value which actually exists (rather than exploding
+    at runtime with an unhelpful error)
+
+    Additional problems with the current approach is that no feedback is given
+    as to WHERE the issue took place (in terms of stacktrace). Which means we should
+    probably explode in GooeyParser proper rather than trying to collect all the errors here.
+    It's not super ideal in that the user will need to run multiple times to
+    see all the issues, but, ultimately probably less annoying that trying to
+    debug which `gooey_option` key had an issue in a large program.
+
+    That said "better is the enemy of done." This is good enough for now. It'll be
+    a TODO: better validation 
+    """
+    errors = collect_errors(validators, options)
+    if errors:
+        from pprint import pformat
+        raise ValueError(str(action.dest) + str(pformat(errors)))
 
 
 def choose_cli_type(action):
@@ -464,7 +525,6 @@ def coerce_default(default, widget):
         'Dropdown': safe_string,
         'Counter': safe_string
     }
-
     # Issue #321:
     # Defaults for choice types must be coerced to strings
     # to be able to match the stringified `choices` used by `wx.ComboBox`
@@ -475,7 +535,7 @@ def coerce_default(default, widget):
     return dispatcher.get(widget, identity)(cleaned)
 
 
-def handle_default(action, widget):
+def handle_initial_values(action, widget, value):
     handlers = [
         [textinput_with_nargs_and_list_default, coerse_nargs_list],
         [is_widget('Listbox'), clean_list_defaults],
@@ -484,8 +544,8 @@ def handle_default(action, widget):
     ]
     for matches, apply_coercion in handlers:
         if matches(action, widget):
-            return apply_coercion(action.default)
-    return clean_default(action.default)
+            return apply_coercion(value)
+    return clean_default(value)
 
 
 def coerse_nargs_list(default):
@@ -495,7 +555,7 @@ def coerse_nargs_list(default):
     instance, ['one two', 'three'] => "one two" "three"
 
     This only applies when the target widget is a text input. List
-    based widgets such as ListBox should keep their defaults in list form
+    based widgets such as Listbox should keep their defaults in list form
 
     Without this transformation, `add_arg('--foo', default=['a b'], nargs='*')` would show up in
     the UI as the literal string `['a b']` brackets and all.
@@ -549,7 +609,7 @@ def clean_default(default):
         return default
     except TypeError as e:
         # see: Issue #377
-        # if is ins't json serializable (i.e. primitive data) there's nothing
+        # if is isn't json serializable (i.e. primitive data) there's nothing
         # useful for Gooey to do with it (since Gooey deals in primitive data
         # types). So the correct behavior is dropping them. This affects ONLY
         # gooey, not the client code.
diff --git a/gooey/python_bindings/coms.py b/gooey/python_bindings/coms.py
new file mode 100644
index 00000000..e30d42a8
--- /dev/null
+++ b/gooey/python_bindings/coms.py
@@ -0,0 +1,50 @@
+"""
+Because Gooey communicates with the host program
+over stdin/out, we have to be able to differentiate what's
+coming from gooey and structured, versus what is arbitrary
+junk coming from the host's own logging.
+
+To do this, we just prefix all written by gooey with the
+literal string 'gooey::'. This lets us dig through all the
+noisy stdout to find just the structured Gooey data we're
+after.
+"""
+
+import json
+from base64 import b64decode
+from typing import Dict, Any
+
+from gooey.python_bindings.schema import validate_public_state
+from gooey.python_bindings.types import PublicGooeyState
+
+prefix = 'gooey::'
+
+
+def serialize_outbound(out: PublicGooeyState):
+    """
+    Attaches a prefix to whatever is about to be written
+    to stdout so that we can differentiate it in the
+    sea of other stdout writes
+    """
+    return prefix + json.dumps(out)
+
+
+def deserialize_inbound(stdout: bytes, encoding):
+    """
+    Deserializes the incoming stdout payload after
+    finding the relevant sections give the gooey prefix.
+    e.g.
+    std='foo\nbar\nstarting run\ngooey::{active_form: [...]}\n'
+    => {active_form: [...]}
+    """
+    data = json.loads(stdout.decode(encoding).split(prefix)[-1])
+    return validate_public_state(data)
+
+
+def decode_payload(x):
+    """
+    To avoid quoting shenanigans, the json state sent from
+    Gooey is b64ecoded for ease of CLI transfer. Argparse will
+    usually barf when trying to parse json directly
+    """
+    return json.loads(b64decode(x))
diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py
index 5b48a4c1..394ad43b 100644
--- a/gooey/python_bindings/config_generator.py
+++ b/gooey/python_bindings/config_generator.py
@@ -1,5 +1,6 @@
 import os
 import sys
+import signal
 import warnings
 import textwrap
 from gooey.python_bindings import argparse_to_json
@@ -23,7 +24,7 @@
     }],
 }
 
-
+# TODO: deprecate me
 def create_from_parser(parser, source_path, **kwargs):
 
   run_cmd = kwargs.get('target')
@@ -33,73 +34,7 @@ def create_from_parser(parser, source_path, **kwargs):
     else:
       run_cmd = '{} -u {}'.format(quote(sys.executable), quote(source_path))
 
-  build_spec = {
-      'language':             kwargs.get('language', 'english'),
-      'target':               run_cmd,
-      # when running with a custom target, there is no need to inject
-      # --ignore-gooey into the CLI args
-      'suppress_gooey_flag':  kwargs.get('suppress_gooey_flag') or False,
-      'program_name':         kwargs.get('program_name') or os.path.basename(sys.argv[0]).replace('.py', ''),
-      'program_description':  kwargs.get('program_description') or '',
-      'sidebar_title':        kwargs.get('sidebar_title', 'Actions'),
-      'default_size':         kwargs.get('default_size', (610, 530)),
-      'auto_start':           kwargs.get('auto_start', False),
-      'show_advanced':        kwargs.get('advanced', True),
-      'run_validators':       kwargs.get('run_validators', True),
-      'encoding':             kwargs.get('encoding', 'utf-8'),
-      'show_stop_warning':    kwargs.get('show_stop_warning', True),
-      'show_success_modal':   kwargs.get('show_success_modal', True),
-      'show_failure_modal':   kwargs.get('show_failure_modal', True),
-      'force_stop_is_error':  kwargs.get('force_stop_is_error', True),
-      'poll_external_updates':kwargs.get('poll_external_updates', False),
-      'return_to_config':     kwargs.get('return_to_config', False),
-      'show_restart_button':  kwargs.get('show_restart_button', True),
-      'requires_shell':       kwargs.get('requires_shell', True),
-      'menu':                 kwargs.get('menu', []),
-      'clear_before_run':     kwargs.get('clear_before_run', False),
-      'fullscreen':           kwargs.get('fullscreen', False),
-
-      # Legacy/Backward compatibility interop
-      'use_legacy_titles':    kwargs.get('use_legacy_titles', True),
-      'num_required_cols':    kwargs.get('required_cols', 1),
-      'num_optional_cols':    kwargs.get('optional_cols', 3),
-      'manual_start':         False,
-      'monospace_display':    kwargs.get('monospace_display', False),
-
-      'image_dir':            kwargs.get('image_dir'),
-      'language_dir':         kwargs.get('language_dir'),
-      'progress_regex':       kwargs.get('progress_regex'),
-      'progress_expr':        kwargs.get('progress_expr'),
-      'hide_progress_msg':    kwargs.get('hide_progress_msg', False),
-      'timing_options':       merge_dictionaries(gooey_decorator.defaults.get('timing_options'),kwargs.get('timing_options')),
-      'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation'),
-      'disable_stop_button':  kwargs.get('disable_stop_button'),
-
-      # Layouts
-      'navigation':           kwargs.get('navigation', constants.SIDEBAR),
-      'show_sidebar':         kwargs.get('show_sidebar', False),
-      'tabbed_groups':        kwargs.get('tabbed_groups', False),
-      'group_by_type':        kwargs.get('group_by_type', True),
-
-      # styles
-      'body_bg_color':        kwargs.get('body_bg_color', '#f0f0f0'),
-      'header_bg_color':      kwargs.get('header_bg_color', '#ffffff'),
-      'header_height':        kwargs.get('header_height', 90),
-      'header_show_title':    kwargs.get('header_show_title', True),
-      'header_show_subtitle': kwargs.get('header_show_subtitle', True),
-      'header_image_center':  kwargs.get('header_image_center', False),
-      'footer_bg_color':      kwargs.get('footer_bg_color', '#f0f0f0'),
-      'sidebar_bg_color':     kwargs.get('sidebar_bg_color', '#f2f2f2'),
-
-      # font family, weight, and size are determined at runtime
-      'terminal_panel_color': kwargs.get('terminal_panel_color', '#F0F0F0'),
-      'terminal_font_color':  kwargs.get('terminal_font_color', '#000000'),
-      'terminal_font_family': kwargs.get('terminal_font_family', None),
-      'terminal_font_weight': get_font_weight(kwargs),
-      'terminal_font_size':   kwargs.get('terminal_font_size', None),
-      'richtext_controls':    kwargs.get('richtext_controls', False),
-      'error_color':          kwargs.get('error_color', '#ea7878')
-  }
+  build_spec = {**kwargs, 'target': run_cmd}
 
   if build_spec['monospace_display']:
       warnings.warn('Gooey Option `monospace_display` is a legacy option.\n'
@@ -110,7 +45,7 @@ def create_from_parser(parser, source_path, **kwargs):
   build_spec['program_description'] = build_spec['program_description'] or parser.description or ''
 
   layout_data = (argparse_to_json.convert(parser, **build_spec)
-                   if build_spec['show_advanced']
+                   if build_spec['advanced']
                    else default_layout.items())
 
   build_spec.update(layout_data)
@@ -123,34 +58,3 @@ def create_from_parser(parser, source_path, **kwargs):
 
 
 
-def get_font_weight(kwargs):
-    error_msg = textwrap.dedent('''
-    Unknown font weight {}. 
-    
-    The available weights can be found in the `constants` module. 
-    They're prefixed with "FONTWEIGHT" (e.g. `FONTWEIGHT_BOLD`)
-    
-    example code:    
-    
-    ```
-    from gooey import constants
-    @Gooey(terminal_font_weight=constants.FONTWEIGHT_NORMAL)
-    ```   
-    ''')
-    weights = {
-        constants.FONTWEIGHT_THIN,
-        constants.FONTWEIGHT_EXTRALIGHT,
-        constants.FONTWEIGHT_LIGHT,
-        constants.FONTWEIGHT_NORMAL,
-        constants.FONTWEIGHT_MEDIUM,
-        constants.FONTWEIGHT_SEMIBOLD,
-        constants.FONTWEIGHT_BOLD,
-        constants.FONTWEIGHT_EXTRABOLD,
-        constants.FONTWEIGHT_HEAVY,
-        constants.FONTWEIGHT_EXTRAHEAVY
-    }
-    weight = kwargs.get('terminal_font_weight', constants.FONTWEIGHT_NORMAL)
-    if weight not in weights:
-        raise ValueError(error_msg.format(weight))
-    return weight
-
diff --git a/gooey/python_bindings/constants.py b/gooey/python_bindings/constants.py
index be122f4c..92d9f5b6 100644
--- a/gooey/python_bindings/constants.py
+++ b/gooey/python_bindings/constants.py
@@ -1,3 +1,4 @@
+from collections import namedtuple
 
 SIDEBAR = 'SIDEBAR'
 TABBED = 'TABBED'
@@ -14,3 +15,14 @@
 FONTWEIGHT_EXTRABOLD = 800
 FONTWEIGHT_HEAVY = 900
 FONTWEIGHT_EXTRAHEAVY = 1000
+
+
+Events = namedtuple('Events', [
+    'VALIDATE_FORM', 'ON_SUCCESS', 'ON_ERROR'
+])('VALIDATE_FORM', 'ON_SUCCESS', 'ON_ERROR')
+
+# class Events:
+#     VALIDATE_FORM = 'VALIDATE_FORM'
+#     ON_SUCCESS = 'ON_SUCCESS'
+#     ON_ERROR = 'ON_ERROR'
+
diff --git a/gooey/python_bindings/control.py b/gooey/python_bindings/control.py
new file mode 100644
index 00000000..9717828a
--- /dev/null
+++ b/gooey/python_bindings/control.py
@@ -0,0 +1,206 @@
+"""
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+!!!!!!!!!!!DEBUGGING NOTE!!!!!!!!!!!
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+PyCharm will inject addition params into stdout when starting
+a new process. This can make debugging VERY VERY CONFUSING as
+the thing being injected starts complaining about unknown
+arguments...
+
+TL;DR: disable the "Attaach to subprocess automatically" option
+in the Debugger settings, and the world will be sane again.
+
+See: https://youtrack.jetbrains.com/issue/PY-24929
+and: https://www.jetbrains.com/help/pycharm/2017.1/python-debugger.html
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+!!!!!!!!!!!DEBUGGING NOTE!!!!!!!!!!!
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+"""
+import json
+import os
+import sys
+import traceback
+from argparse import ArgumentParser
+from copy import deepcopy
+from typing import List, Dict
+
+from gooey.python_bindings.dynamics import monkey_patch_for_form_validation
+from gooey.python_bindings.dynamics import patch_argument, collect_errors
+from gooey.python_bindings.types import GooeyParams
+from gooey.python_bindings.coms import serialize_outbound, decode_payload
+from gooey.python_bindings.types import PublicGooeyState
+from . import cmd_args
+from . import config_generator
+
+
+def noop(*args, **kwargs):
+    """
+    No-op for dev/null-ing handlers which
+    haven't been specified by the user.
+    """
+    return None
+
+
+def bypass_gooey(params):
+    """
+    Bypasses all the Gooey machinery and runs the user's code directly.
+    """
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        # We previously mutated sys.argv directly to remove
+        # the --ignore-gooey flag. But this caused lots of issues
+        # See: https://github.com/chriskiehl/Gooey/issues/686
+        # So, we instead modify the parser to transparently
+        # consume the extra token.
+        patched_parser = patch_argument(self, '--ignore-gooey', action='store_true')
+        args = patched_parser.original_parse_args(args, namespace)  # type: ignore
+        # removed from the arg object so the user doesn't have
+        # to deal with it or be confused by it
+        del args.ignore_gooey
+        return args
+    return parse_args
+
+
+def boostrap_gooey(params: GooeyParams):
+    """Bootstraps the Gooey UI."""
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        # This import is delayed so it is not in the --ignore-gooey codepath.
+        from gooey.gui import bootstrap
+        source_path = sys.argv[0]
+
+        build_spec = None
+        if params['load_build_config']:
+            try:
+                exec_dir = os.path.dirname(sys.argv[0])
+                open_path = os.path.join(exec_dir, params['load_build_config'])  # type: ignore
+                build_spec = json.load(open(open_path, "r"))
+            except Exception as e:
+                print('Exception loading Build Config from {0}: {1}'.format(params['load_build_config'], e))
+                sys.exit(1)
+
+        if not build_spec:
+            if params['use_cmd_args']:
+                cmd_args.parse_cmd_args(self, args)
+
+            build_spec = config_generator.create_from_parser(
+                self,
+                source_path,
+                **params)
+
+        if params['dump_build_config']:
+            config_path = os.path.join(os.path.dirname(sys.argv[0]), 'gooey_config.json')
+            print('Writing Build Config to: {}'.format(config_path))
+            with open(config_path, 'w') as f:
+                f.write(json.dumps(build_spec, indent=2))
+        bootstrap.run(build_spec)
+    return parse_args
+
+
+def validate_form(params: GooeyParams, write=print, exit=sys.exit):
+    """
+    Validates the user's current form.
+    """
+    def merge_errors(state: PublicGooeyState, errors: Dict[str, str]) -> PublicGooeyState:
+        changes = deepcopy(state['active_form'])
+        for item in changes:
+            if item['type'] == 'RadioGroup':
+                for subitem in item['options']:  # type: ignore
+                    subitem['error'] = errors.get(subitem['id'], None)
+                item['error'] = any(x['error'] for x in item['options'])   # type: ignore
+            else:
+                item['error'] = errors.get(item['id'], None)  # type: ignore
+
+        return PublicGooeyState(active_form=changes)
+
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        error_registry: Dict[str, Exception] = {}
+        patched_parser = monkey_patch_for_form_validation(error_registry, self)
+        try:
+            args = patched_parser.original_parse_args(args, namespace)  # type: ignore
+            state = args.gooey_state
+            next_state = merge_errors(state, collect_errors(patched_parser, error_registry, vars(args)))
+            write(serialize_outbound(next_state))
+            exit(0)
+        except Exception as e:
+            write(e)
+            exit(1)
+    return parse_args
+
+
+def validate_field(params):
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        raise NotImplementedError
+    return parse_args
+
+
+def handle_completed_run(params, write=print, exit=sys.exit):
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        # because we're running under the context of a successful
+        # invocation having just completed, the arguments supplied to
+        # the parser to trigger it are thus, by definition, safe to parse.
+        # So, we don't need any error patching monkey business and just need
+        # to attach our specific arg to parse the extra option Gooey passes
+
+        patch_argument(self, '--gooey-state', action='store', type=decode_payload)
+        patch_argument(self, '--gooey-run-is-success', default=False, action='store_true')
+        patch_argument(self, '--gooey-run-is-failure', default=False, action='store_true')
+
+        try:
+            args = self.original_parse_args(args, namespace)  # type: ignore
+            form_state = args.gooey_state
+            was_success = args.gooey_run_is_success
+            # removing the injected gooey value so as not
+            # to clutter the user's object
+            del args.gooey_state
+            del args.gooey_run_is_success
+            del args.gooey_run_is_failure
+            if was_success:
+                next_state = getattr(self, 'on_gooey_success', noop)(args, form_state)  # type: ignore
+            else:
+                next_state = getattr(self, 'on_gooey_error', noop)(args, form_state)  # type: ignore
+            write(serialize_outbound(next_state))
+            exit(0)
+        except Exception as e:
+            write(''.join(traceback.format_stack()))
+            write(e)
+            exit(1)
+    return parse_args
+
+
+def handle_error(params):
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        raise NotImplementedError
+    return parse_args
+
+
+def handle_field_update(params):
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        raise NotImplementedError
+    return parse_args
+
+
+def handle_submit(params):
+    def parse_args(self: ArgumentParser, args=None, namespace=None):
+        raise NotImplementedError
+    return parse_args
+
+
+def choose_hander(params: GooeyParams, cliargs: List[str]):
+    """
+    Dispatches to the appropriate handler based on values
+    found in the CLI arguments
+    """
+    with open('tmp.txt', 'w') as f:
+        f.write(str(sys.argv))
+    if '--gooey-validate-form' in cliargs:
+        return validate_form(params)
+    elif '--gooey-run-is-success' in cliargs or '--gooey-run-is-failure' in cliargs:
+        return handle_completed_run(params)
+    elif '--ignore-gooey' in cliargs:
+        return bypass_gooey(params)
+    else:
+        return boostrap_gooey(params)
+
+
+
diff --git a/gooey/python_bindings/dynamics.py b/gooey/python_bindings/dynamics.py
new file mode 100644
index 00000000..a7c591fd
--- /dev/null
+++ b/gooey/python_bindings/dynamics.py
@@ -0,0 +1,279 @@
+"""
+All things Dynamic Updates & Validation.
+
+Hear me all ye who enter!
+=========================
+
+This is a module of disgusting hacks and monkey patching. Control flow
+is all over the place and a comprised of hodgepodge of various strategies.
+This is all because Argparse's internal parsing design (a) really,
+really, REALLY wants to fail and sys.exit at the first error it
+finds, and (b) does these program ending validations at seemingly random
+points throughout its code base. Meaning, there is no single centralized
+validation module, class, or function which could be overridden in order to
+achieve the desired behavior.
+
+All that means is that it takes a fair amount of indirect, non-standard, and
+gross monkey-patching to get Argparse to collect all its errors as it parses
+rather than violently explode each time it finds one.
+
+For additional background, see the original design here:
+https://github.com/chriskiehl/Gooey/issues/755
+
+
+"""
+from argparse import ArgumentParser, _SubParsersAction, _MutuallyExclusiveGroup
+from functools import wraps
+from typing import Union, Any, Mapping, Dict, Callable
+
+from gooey.python_bindings.types import Success, Failure, Try, InvalidChoiceException
+from gooey.python_bindings.argparse_to_json import is_subparser
+from gooey.util.functional import lift, identity, merge
+from gooey.gui.constants import VALUE_PLACEHOLDER
+from gooey.python_bindings.constants import Events
+from gooey.python_bindings.coms import decode_payload
+from gooey.gui.constants import RADIO_PLACEHOLDER
+
+unexpected_exit_explanations = f'''
++=======================+
+|Gooey Unexpected Error!|
++=======================+
+
+Gooey encountered an unexpected error while trying to communicate 
+with your program to process one of the {Events._fields} events.
+
+These features are new and experimental! You may have encountered a bug! 
+
+You can open a ticket with a small reproducible example here
+https://github.com/chriskiehl/Gooey/issues
+'''  # type: ignore
+
+
+deserialize_failure_explanations = f'''
++==================================+
+|Gooey Event Deserialization Error!|
++==================================+
+
+Gooey was unable to deserialize the payload returned from your 
+program when processing one of the {Events._fields} events. 
+
+The payload *MUST* be in the `GooeyPublicState` schema. You can 
+view the type information in `gooey.python_bindings.types.py`
+
+Note, these features are new an experimental. This may be a bug on 
+Gooey's side! 
+
+You can open a ticket with a small reproducible example here:  
+https://github.com/chriskiehl/Gooey/issues
+'''
+
+
+def check_value(registry: Dict[str, Exception], original_fn):
+    """
+    A Monkey Patch for `Argparse._check_value` which changes its
+    behavior from one which throws an exception, to one which swallows
+    the exception and silently records the failure.
+
+    For certain argument types, Argparse calls a
+    one-off `check_value` method. This method is inconvenient for us
+    as it either returns nothing or throws an ArgumentException (thus leading
+    to a sys.exit). Because our goal is to collect ALL
+    errors for the entire parser, we must patch around this behavior.
+    """
+    @wraps(original_fn)
+    def inner(self, action, value: Union[Any, Success, Failure]):
+        def update_reg(_self, _action, _value):
+            try:
+                original_fn(_action, _value)
+            except Exception as e:
+                # check_value exclusively handles validating that the
+                # supplied argument is a member of the `choices` set.
+                # by default, it pops an exception containing all of the
+                # available choices. However, since we're in a UI environment
+                # all of that is redundant information. It's also *way too much*
+                # information for things like FilterableDropdown. Thus we just
+                # remap it to a 'simple' exception here.
+                error = InvalidChoiceException("Selected option is not a valid choice")
+                # IMPORTANT! note that this mutates the
+                # reference that is passed in!
+                registry[action.dest] = error
+
+        # Inside of Argparse, `type_func` gets applied before the calls
+        # to `check_value`. A such, depending on the type, this may already
+        # be a lifted value.
+        if isinstance(value, Success) and not isinstance(value, Failure):
+            update_reg(self, action, value.value)
+        elif isinstance(value, list) and all(x.isSuccess() for x in value):
+            update_reg(self, action, [x.value for x in value])
+        else:
+            update_reg(self, action, value)
+    return inner
+
+
+def patch_args(*args, **kwargs):
+    def inner(parser):
+        return patch_argument(parser, *args, **kwargs)
+    return inner
+
+def patch_argument(parser, *args, **kwargs):
+    """
+    Mutates the supplied parser to append the arguments (args, kwargs) to
+    the root parser and all subparsers.
+
+    Example: `patch_argument(parser, '--ignore-gooey', action='store_true')
+
+    This is used to punch additional cli arguments into the user's
+    existing parser definition. By adding our arguments everywhere it allows
+    us to use the `parse_args` machinery 'for free' without needing to
+    worry about context shifts (like a repeated `dest` between subparsers).
+    """
+    parser.add_argument(*args, **kwargs)
+    subparsers = list(filter(is_subparser, parser._actions))
+    if subparsers:
+        for sub in subparsers[0].choices.values():  # type: ignore
+            patch_argument(sub, *args, **kwargs)
+    return parser
+
+
+def patch_all_parsers(patch_fn: Callable[[ArgumentParser], None], parser):
+    subparsers = list(filter(is_subparser, parser._actions))
+    if subparsers:
+        for sub in subparsers[0].choices.values():  # type: ignore
+            patch_all_parsers(patch_fn, sub)
+    return parser
+
+
+def recursively_patch_parser(parser, fn, *args):
+    fn(parser, *args)
+    subparsers = list(filter(is_subparser, parser._actions))
+    if subparsers:
+        for sub in subparsers[0].choices.values():  # type: ignore
+            recursively_patch_parser(sub, fn, *args)
+    return parser
+
+
+def recursively_patch_actions(parser, fn):
+    for action in parser._actions:
+        if issubclass(type(action), _SubParsersAction):
+            for subparser in action.choices.values():
+                recursively_patch_actions(subparser, fn)
+        else:
+            fn(action)
+
+def lift_action_type(action):
+    """"""
+    action.type = lift(action.type or identity)
+
+def lift_actions_mutating(parser):
+    """
+    Mutates the supplied parser to lift all of its (likely) partial
+    functions into total functions. See module docs for additional
+    background. TL;DR: we have to "trick" Argparse into thinking
+    every value is valid so that it doesn't short-circuit and sys.exit
+    when it encounters a validation error. As such, we wrap everything
+    in an Either/Try, and defer deciding the actual success/failure of
+    the type transform until later in the execution when we have control.
+    """
+    recursively_patch_actions(parser, lift_action_type)
+    # for action in parser._actions:
+    #     if issubclass(type(action), _SubParsersAction):
+    #         for subparser in action.choices.values():
+    #             lift_actions_mutating(subparser)
+    #     else:
+    #         action.type = lift(action.type or identity)
+
+
+def collect_errors(parser, error_registry: Dict[str, Exception], args: Dict[str, Try]) -> Dict[str, str]:
+    """
+    Merges all the errors from the Args mapping and error registry
+    into a final dict.
+    """
+    # As is a theme throughout this module, to avoid Argparse
+    # short-circuiting during parse-time, we pass a placeholder string
+    # for required positional arguments which haven't yet been provided
+    # by the user. So, what's happening here is that we're now collecting
+    # all the args which have the placeholders so that we can flag them
+    # all as required and missing.
+    # Again, to be hyper clear, this is about being able to collect ALL
+    # errors, versus just ONE error (Argparse default).
+    required_but_missing = {k: 'This field is required'
+                            for k, v in args.items()
+                            if isinstance(v, Success) and v.value == VALUE_PLACEHOLDER}
+
+    mutexes_required_but_missing = collect_mutex_errors(parser, args)
+
+    errors = {k: str(v.error)
+              for k, v in args.items()
+              if v is not None and isinstance(v, Failure)}
+    # Secondary errors are those which get frustratingly applied by
+    # Argparse in a way which can't be easily tracked with patching
+    # or higher order functions. See: `check_value` for more details.
+    secondary = {k: str(e) for k, e in error_registry.items() if e}
+    return merge(required_but_missing, errors, secondary, mutexes_required_but_missing)
+
+
+def collect_mutex_errors(parser, args: Dict[str, Try]):
+    """
+    RadioGroups / MutuallyExclusiveGroup require extra care.
+    Mutexes are not normal actions. They're not argument targets
+    themselves, they have no `dest`, they're just parse-time containers
+    for arguments. As such, there's no top-level argument destination
+    we can tie a single error to. So, the strategy here is to mark _all_ of
+    a radio group's children with an error if *any* of them are missing.
+
+    It's a bit clunky, but what we've got to work with.
+    """
+    def dest_targets(group: _MutuallyExclusiveGroup):
+        return [action.dest for action in group._group_actions]
+
+    mutexes_missing = {dest for dest, v in args.items()
+                     if isinstance(v, Success) and v.value == RADIO_PLACEHOLDER}
+
+    return {dest: 'One of these must be provided'
+            for group in parser._mutually_exclusive_groups
+            for dest in dest_targets(group)
+            # if the group is required and we've got one of its
+            # children marked as missing
+            if group.required and set(dest_targets(group)).intersection(mutexes_missing)}
+
+
+
+
+
+
+
+
+
+def patch(obj, old_fn, new_fn):
+    setattr(obj, old_fn, new_fn.__get__(obj, ArgumentParser))
+
+def monkey_patch_check_value(parser, new_fn):
+    parser._check_value = new_fn.__get__(parser, ArgumentParser)
+    return parser
+
+def monkey_patch(patcher, error_registry: Dict[str, Exception], parser):
+    lift_actions_mutating(parser)
+    patcher(parser)
+    new_check_value = check_value(error_registry, parser._check_value)
+    # https://stackoverflow.com/questions/28127874/monkey-patching-python-an-instance-method
+    # parser._check_value = new_check_value.__get__(parser, ArgumentParser)
+
+    return parser
+
+
+
+def monkey_patch_for_form_validation(error_registry: Dict[str, Exception], parser):
+    """
+    Applies all the crufty monkey patching required to
+    process a validate_form event
+    """
+    lift_actions_mutating(parser)
+    patch_argument(parser, '--gooey-validate-form', action='store_true')
+    patch_argument(parser, '--gooey-state', action='store', type=decode_payload)
+    new_check_value = check_value(error_registry, parser._check_value)
+    recursively_patch_parser(parser, monkey_patch_check_value, new_check_value)
+    # https://stackoverflow.com/questions/28127874/monkey-patching-python-an-instance-method
+    # patch(parser, '_check_value', new_check_value)
+    # parser._check_value = new_check_value.__get__(parser, ArgumentParser)
+    return monkey_patch_check_value(parser, new_check_value)
+
diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py
index 17d3f3a1..ec17a6b4 100644
--- a/gooey/python_bindings/gooey_decorator.py
+++ b/gooey/python_bindings/gooey_decorator.py
@@ -1,148 +1,51 @@
-'''
-Created on Jan 24, 2014
-
-@author: Chris
-
-TODO: this
-'''
-
-import json
-import os
+"""
+Created on Jan 24, 2014  <-- so long ago!
+"""
 import sys
 from argparse import ArgumentParser
+from functools import wraps
 
-from gooey.gui.util.freeze import getResourcePath
-from gooey.util.functional import merge
-from . import config_generator
-from . import cmd_args
+from gooey.python_bindings.control import choose_hander
+from gooey.python_bindings.parameters import gooey_params
+from gooey.python_bindings.types import GooeyParams
 
 IGNORE_COMMAND = '--ignore-gooey'
 
-# TODO: use these defaults in the decorator and migrate to a flat **kwargs
-#       They're pulled out here for wiring up instances in the tests.
-#       Some fiddling is needed before I can make the changes to make the swap to
-#       `defaults` + **kwargs overrides.
-defaults = {
-    'advanced': True,
-    'language': 'english',
-    'auto_start': False,  # TODO: add this to the docs. Used to be `show_config=True`
-    'target': None,
-    'program_name': None,
-    'program_description': None,
-    'default_size': (610, 530),
-    'use_legacy_titles': True,
-    'required_cols': 2,
-    'optional_cols': 2,
-    'dump_build_config': False,
-    'load_build_config': None,
-    'monospace_display': False,  # TODO: add this to the docs
-    'image_dir': '::gooey/default',
-    'language_dir': getResourcePath('languages'),
-    'progress_regex': None,  # TODO: add this to the docs
-    'progress_expr': None,  # TODO: add this to the docs
-    'hide_progress_msg': False,  # TODO: add this to the docs
-    'disable_progress_bar_animation': False,
-    'disable_stop_button': False,
-    'group_by_type': True,
-    'header_height': 80,
-    'navigation': 'SIDEBAR', # TODO: add this to the docs
-    'tabbed_groups': False,
-    'use_cmd_args': False,
-    'timing_options': {
-      'show_time_remaining': False,
-      'hide_time_remaining_on_complete': True
-    }
-}
-
-# TODO: kwargs all the things
-def Gooey(f=None,
-          advanced=True,
-          language='english',
-          auto_start=False,  # TODO: add this to the docs. Used to be `show_config=True`
-          target=None,
-          program_name=None,
-          program_description=None,
-          default_size=(610, 530),
-          use_legacy_titles=True,
-          required_cols=2,
-          optional_cols=2,
-          dump_build_config=False,
-          load_build_config=None,
-          monospace_display=False,  # TODO: add this to the docs
-          image_dir='::gooey/default',
-          language_dir=getResourcePath('languages'),
-          progress_regex=None,  # TODO: add this to the docs
-          progress_expr=None,  # TODO: add this to the docs
-          hide_progress_msg=False,  # TODO: add this to the docs
-          disable_progress_bar_animation=False,
-          disable_stop_button=False,
-          group_by_type=True,
-          header_height=80,
-          navigation='SIDEBAR', # TODO: add this to the docs
-          tabbed_groups=False,
-          use_cmd_args=False,
-          **kwargs):
-  '''
-  Decorator for client code's main function.
-  Serializes argparse data to JSON for use with the Gooey front end
-  '''
-
-  params = merge(locals(), locals()['kwargs'])
-
-  def build(payload):
-    def run_gooey(self, args=None, namespace=None):
-      # This import is delayed so it is not in the --ignore-gooey codepath.
-      from gooey.gui import application
-      source_path = sys.argv[0]
-
-      build_spec = None
-      if load_build_config:
-        try:
-          exec_dir = os.path.dirname(sys.argv[0])
-          open_path = os.path.join(exec_dir,load_build_config)
-          build_spec = json.load(open(open_path, "r"))
-        except Exception as e:
-          print('Exception loading Build Config from {0}: {1}'.format(load_build_config, e))
-          sys.exit(1)
-
-      if not build_spec:
-        if use_cmd_args:
-          cmd_args.parse_cmd_args(self, args)
-
-        build_spec = config_generator.create_from_parser(
-          self,
-          source_path,
-          payload_name=payload.__name__,
-          **params)
 
-      if dump_build_config:
-        config_path = os.path.join(os.path.dirname(sys.argv[0]), 'gooey_config.json')
-        print('Writing Build Config to: {}'.format(config_path))
-        with open(config_path, 'w') as f:
-          f.write(json.dumps(build_spec, indent=2))
-      application.run(build_spec)
+def Gooey(f=None, **gkwargs):
+    """
+    Decoration entry point for the Gooey process.
+    See types.GooeyParams for kwargs options
+    """
+    params: GooeyParams = gooey_params(**gkwargs)
 
-    def inner2(*args, **kwargs):
-      ArgumentParser.original_parse_args = ArgumentParser.parse_args
-      ArgumentParser.parse_args = run_gooey
-      return payload(*args, **kwargs)
+    @wraps(f)
+    def inner(*args, **kwargs):
+        parser_handler = choose_hander(params, gkwargs.get('cli', sys.argv))
+        # monkey patch parser
+        ArgumentParser.original_parse_args = ArgumentParser.parse_args
+        ArgumentParser.parse_args = parser_handler
+        # return the wrapped, now monkey-patched, user function
+        # to be later invoked
+        return f(*args, **kwargs)
 
-    inner2.__name__ = payload.__name__
-    return inner2
+    def thunk(func):
+        """
+        This just handles the case where the decorator is called
+        with arguments (i.e. @Gooey(foo=bar) rather than @Gooey).
 
-  def run_without_gooey(func):
-    return lambda *args, **kwargs: func(*args, **kwargs)
+        Cause python is weird, when a decorator is called (e.g. @decorator())
+        rather than just declared (e.g. @decorator), in complete and utter
+        defiance of what your lying eyes see, it changes from a higher order
+        function, to a function that takes an arbitrary argument *and then*
+        returns a higher order function. i.e.
 
-  if IGNORE_COMMAND in sys.argv:
-    sys.argv.remove(IGNORE_COMMAND)
-    if callable(f):
-      return run_without_gooey(f)
-    return run_without_gooey
+        decorate :: (a -> b) -> (a -> b)
+        decorate() :: c -> (a -> b) -> (a -> b)
 
-  if callable(f):
-    return build(f)
-  return build
+        wat.
+        """
+        return Gooey(func, **params)
 
+    return inner if callable(f) else thunk
 
-if __name__ == '__main__':
-  pass
diff --git a/gooey/python_bindings/gooey_parser.py b/gooey/python_bindings/gooey_parser.py
index a99a214b..6b4bebed 100644
--- a/gooey/python_bindings/gooey_parser.py
+++ b/gooey/python_bindings/gooey_parser.py
@@ -1,6 +1,5 @@
 from argparse import ArgumentParser, _SubParsersAction
 from argparse import _MutuallyExclusiveGroup, _ArgumentGroup
-from textwrap import dedent
 
 
 class GooeySubParser(_SubParsersAction):
@@ -21,6 +20,7 @@ def add_argument(self, *args, **kwargs):
         widget = kwargs.pop('widget', None)
         metavar = kwargs.pop('metavar', None)
         options = kwargs.pop('gooey_options', None)
+
         action = super(GooeyArgumentGroup, self).add_argument(*args, **kwargs)
         self.parser._actions[-1].metavar = metavar
         self.widgets[self.parser._actions[-1].dest] = widget
@@ -54,18 +54,60 @@ def add_argument(self, *args, **kwargs):
         widget = kwargs.pop('widget', None)
         metavar = kwargs.pop('metavar', None)
         options = kwargs.pop('gooey_options', None)
+
         super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs)
         self.parser._actions[-1].metavar = metavar
         self.widgets[self.parser._actions[-1].dest] = widget
         self.options[self.parser._actions[-1].dest] = options
 
 
+class MyArgumentParser(ArgumentParser):
+    def __init__(self, **kwargs):
+        self._errors = []
+        super(MyArgumentParser, self).__init__(**kwargs)
+
+    def error(self, message):
+        self._errors.append(message)
+
+
+def lift_relevant(**kwargs):
+    """
+    Lifts the user's (likely) partial function into
+    total one of type `String -> Either Error a`
+    """
+    try:
+        # Not all Parser Actions accept a type function. Rather
+        # than track what allows what explicitly, we just try to
+        # pass the `type` var to constructor. If is doesn't
+        # explode, then we're good and we use the lifted type. Otherwise
+        # we use the original kwargs
+        p = ArgumentParser()
+        lifted_kwargs = {**kwargs, 'type': lift(kwargs.get('type', identity))}
+        p.add_argument('-a', **lifted_kwargs)
+        return lifted_kwargs
+    except TypeError as e:
+        return kwargs
+
+
+def cls_wrapper(cls, **options):
+    def inner(*args, **kwargs):
+        class ActionWrapper(cls):
+            def __call__(self, p, namespace, values, option_string, **qkwargs):
+                # print('hello from', options, namespace, values, option_string, qkwargs)
+                super(ActionWrapper, self).__call__(p, namespace, values, option_string, **qkwargs)
+        return ActionWrapper(*args, **kwargs)
+    return inner
+
 
 class GooeyParser(object):
     def __init__(self, **kwargs):
+        on_success = kwargs.pop('on_success', None)
+        on_error = kwargs.pop('on_error', None)
         self.__dict__['parser'] = ArgumentParser(**kwargs)
         self.widgets = {}
         self.options = {}
+        self.on_gooey_success = on_success
+        self.on_gooey_error = on_error
         if 'parents' in kwargs:
             for parent in kwargs['parents']:
                 if isinstance(parent, self.__class__):
@@ -89,7 +131,17 @@ def add_argument(self, *args, **kwargs):
         metavar = kwargs.pop('metavar', None)
         options = kwargs.pop('gooey_options', None)
 
+        # TODO: move this to the control module. No need to do it
+        #       at creation time.
+        # lifted_kwargs = lift_relevant(**kwargs)
+        #
+        # action_cls = self.parser._pop_action_class(kwargs)
+        # enhanced_action = cls_wrapper(action_cls, **(options if options else {}))
+        #
+        # action = self.parser.add_argument(*args, **{**lifted_kwargs, 'action': enhanced_action})
+
         action = self.parser.add_argument(*args, **kwargs)
+
         self.parser._actions[-1].metavar = metavar
 
         action_dest = self.parser._actions[-1].dest
@@ -167,3 +219,4 @@ def __getattr__(self, item):
 
     def __setattr__(self, key, value):
         return setattr(self.parser, key, value)
+
diff --git a/gooey/python_bindings/parameters.py b/gooey/python_bindings/parameters.py
new file mode 100644
index 00000000..83e9fb32
--- /dev/null
+++ b/gooey/python_bindings/parameters.py
@@ -0,0 +1,152 @@
+import signal
+import sys
+import textwrap
+
+import os
+from typing import List
+
+from gooey.python_bindings.constants import Events
+from gooey.python_bindings import constants
+from gooey.gui.util.freeze import getResourcePath
+from gooey.python_bindings.types import GooeyParams
+from gooey.util.functional import merge
+
+
+
+def _get_font_weight(kwargs):
+    error_msg = textwrap.dedent('''
+    Unknown font weight {}. 
+
+    The available weights can be found in the `constants` module. 
+    They're prefixed with "FONTWEIGHT" (e.g. `FONTWEIGHT_BOLD`)
+
+    example code:    
+
+    ```
+    from gooey import constants
+    @Gooey(terminal_font_weight=constants.FONTWEIGHT_NORMAL)
+    ```   
+    ''')
+    weights = {
+        constants.FONTWEIGHT_THIN,
+        constants.FONTWEIGHT_EXTRALIGHT,
+        constants.FONTWEIGHT_LIGHT,
+        constants.FONTWEIGHT_NORMAL,
+        constants.FONTWEIGHT_MEDIUM,
+        constants.FONTWEIGHT_SEMIBOLD,
+        constants.FONTWEIGHT_BOLD,
+        constants.FONTWEIGHT_EXTRABOLD,
+        constants.FONTWEIGHT_HEAVY,
+        constants.FONTWEIGHT_EXTRAHEAVY
+    }
+    weight = kwargs.get('terminal_font_weight', constants.FONTWEIGHT_NORMAL)
+    if weight not in weights:
+        raise ValueError(error_msg.format(weight))
+    return weight
+
+
+# python can't type kwargs? wtf..
+def gooey_params(**kwargs) -> GooeyParams:
+    """
+    Builds the full GooeyParams object from an arbitrary subset of supplied values
+    """
+    return GooeyParams(**{  # type: ignore
+        'show_preview_warning': kwargs.get('show_preview_warning', True),
+        'language': kwargs.get('language', 'english'),
+        'target': kwargs.get('target'),
+
+        'dump_build_config': kwargs.get('dump_build_config', False),
+        'load_build_config': kwargs.get('load_build_config'),
+        'use_cmd_args': kwargs.get('use_cmd_args', False),
+
+        'suppress_gooey_flag': kwargs.get('suppress_gooey_flag') or False,
+        # TODO: I should not read from the environment.
+        # remains here for legacy reasons pending refactor
+        'program_name': kwargs.get('program_name') or os.path.basename(sys.argv[0]).replace('.py', ''),
+        'program_description': kwargs.get('program_description') or '',
+        'sidebar_title': kwargs.get('sidebar_title', 'Actions'),
+        'default_size': kwargs.get('default_size', (610, 530)),
+        'auto_start': kwargs.get('auto_start', False),
+        'advanced': kwargs.get('advanced', True),
+        'run_validators': kwargs.get('run_validators', True),
+        'encoding': kwargs.get('encoding', 'utf-8'),
+        'show_stop_warning': kwargs.get('show_stop_warning', True),
+        'show_success_modal': kwargs.get('show_success_modal', True),
+        'show_failure_modal': kwargs.get('show_failure_modal', True),
+        'force_stop_is_error': kwargs.get('force_stop_is_error', True),
+        'poll_external_updates': kwargs.get('poll_external_updates', False),
+        'return_to_config': kwargs.get('return_to_config', False),
+        'show_restart_button': kwargs.get('show_restart_button', True),
+        'requires_shell': kwargs.get('requires_shell', True),
+        'menu': kwargs.get('menu', []),
+        'clear_before_run': kwargs.get('clear_before_run', False),
+        'fullscreen': kwargs.get('fullscreen', False),
+
+        'use_legacy_titles': kwargs.get('use_legacy_titles', True),
+        'required_cols': kwargs.get('required_cols', 2),
+        'optional_cols': kwargs.get('optional_cols', 2),
+        'manual_start': False,
+        'monospace_display': kwargs.get('monospace_display', False),
+
+        'image_dir': kwargs.get('image_dir', '::gooey/default'),
+        # TODO: this directory resolution shouldn't happen here!
+        # TODO: leaving due to legacy for now
+        'language_dir': kwargs.get('language_dir', getResourcePath('languages')),
+        'progress_regex': kwargs.get('progress_regex'),
+        'progress_expr': kwargs.get('progress_expr'),
+        'hide_progress_msg': kwargs.get('hide_progress_msg', False),
+
+        'timing_options': merge({
+            'show_time_remaining': False,
+            'hide_time_remaining_on_complete': True
+        }, kwargs.get('timing_options', {})),
+        'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation', False),
+        'disable_stop_button': kwargs.get('disable_stop_button'),
+        'shutdown_signal': kwargs.get('shutdown_signal', signal.SIGTERM),
+        'use_events': parse_events(kwargs.get('use_events', [])),
+
+
+        'navigation': kwargs.get('navigation', constants.SIDEBAR),
+        'show_sidebar': kwargs.get('show_sidebar', False),
+        'tabbed_groups': kwargs.get('tabbed_groups', False),
+        'group_by_type': kwargs.get('group_by_type', True),
+
+
+        'body_bg_color': kwargs.get('body_bg_color', '#f0f0f0'),
+        'header_bg_color': kwargs.get('header_bg_color', '#ffffff'),
+        'header_height': kwargs.get('header_height', 90),
+        'header_show_title': kwargs.get('header_show_title', True),
+        'header_show_subtitle': kwargs.get('header_show_subtitle', True),
+        'header_image_center': kwargs.get('header_image_center', False),
+        'footer_bg_color': kwargs.get('footer_bg_color', '#f0f0f0'),
+        'sidebar_bg_color': kwargs.get('sidebar_bg_color', '#f2f2f2'),
+
+        'terminal_panel_color': kwargs.get('terminal_panel_color', '#F0F0F0'),
+        'terminal_font_color': kwargs.get('terminal_font_color', '#000000'),
+        'terminal_font_family': kwargs.get('terminal_font_family', None),
+        'terminal_font_weight': _get_font_weight(kwargs),
+        'terminal_font_size': kwargs.get('terminal_font_size', None),
+        'richtext_controls': kwargs.get('richtext_controls', False),
+        'error_color': kwargs.get('error_color', '#ea7878'),
+        # TODO: remove. Only useful for testing
+        'cli': kwargs.get('cli', sys.argv),
+    })
+
+
+def parse_events(events: List[str]) -> List[str]:
+    if not isinstance(events, list):
+        raise TypeError(
+            f"`use_events` requires a list of events. You provided "
+            "{events}. \n"
+            "Example: \n"
+            "\tfrom gooey import Events"
+            "\t@Gooey(use_events=[Events.VALIDATE_FORM]")
+
+    unknown_events = set(events) - set(Events)
+    if unknown_events:
+        raise ValueError(
+            f'nrecognized event(s) were passed to `use_events`: {unknown_events}\n'
+            f'Must be one of {Events._fields}\n'
+            f'Consider using the `Events` object: `from gooey import Events`')
+    else:
+        return events
diff --git a/gooey/python_bindings/schema.py b/gooey/python_bindings/schema.py
new file mode 100644
index 00000000..54454a03
--- /dev/null
+++ b/gooey/python_bindings/schema.py
@@ -0,0 +1,22 @@
+from typing import Dict, Any
+
+from gooey.python_bindings.types import PublicGooeyState
+from gooey.python_bindings import types as t
+
+
+def validate_public_state(state: Dict[str, Any]) -> PublicGooeyState:
+    """
+    Very, very minimal validation the shape of the incoming state
+    is inline with the PublicGooeyState type.
+
+    TODO: turn this into something useful.
+    """
+    top_level_keys = PublicGooeyState.__annotations__.keys()
+    assert set(top_level_keys) == set(state.keys())
+    for item in state['active_form']:
+        assert 'type' in item
+        expected_keys = getattr(t, item['type']).__annotations__.keys()
+        a = set(expected_keys)
+        b = set(item.keys())
+        assert set(expected_keys) == set(item.keys())
+    return state
diff --git a/gooey/python_bindings/signal_support.py b/gooey/python_bindings/signal_support.py
new file mode 100644
index 00000000..72b70622
--- /dev/null
+++ b/gooey/python_bindings/signal_support.py
@@ -0,0 +1,91 @@
+"""
+Utilities for patching Windows so that CTRL-C signals
+can be received by process groups.
+
+The best resource for understanding why this is required is the Python
+Issue here: https://bugs.python.org/issue13368
+
+**The official docs from both Python and Microsoft cannot be trusted due to inaccuracies**
+
+The two sources directly conflict with regards to what signals are possible on Windows
+under which circumstances.
+
+
+Python's docs:
+    https://bugs.python.org/issue13368
+    On Windows, SIGTERM is an alias for terminate(). CTRL_C_EVENT and
+    CTRL_BREAK_EVENT can be sent to processes started with a creationflags
+    parameter which includes CREATE_NEW_PROCESS_GROUP.
+
+Microsoft's docs:
+    https://docs.microsoft.com/en-us/windows/console/generateconsolectrlevent
+    Generates a CTRL+C signal. This signal cannot be generated for process groups.
+
+Another piece of the puzzle:
+    https://docs.microsoft.com/en-us/windows/console/handlerroutine#remarks
+    Each console process has its own list of HandlerRoutine functions. Initially,
+    this list contains only a default handler function that calls ExitProcess. A
+    console process adds or removes additional handler functions by calling the
+    SetConsoleCtrlHandler function, which does not affect the list of handler
+    functions for other processes.
+
+The most important line here is: "Initially, this list contains only the default
+handler function that calls exit Process"
+
+So, despite what Microsoft's docs for ctrlcevent state, it IS possible to send
+the ctrl+c signal to process groups **IFF** the leader for that process group has
+the appropriate handler installed.
+
+We can solve this transparently in Gooey land by installing the handler on behalf
+of the user when required.
+"""
+import sys
+import signal
+from textwrap import dedent
+
+
+def requires_special_handler(platform, requested_signal):
+    """
+    Checks whether we need to attach additional handlers
+    to this process to deal with ctrl-C signals
+    """
+    return platform.startswith("win") and requested_signal == signal.CTRL_C_EVENT
+
+
+def install_handler():
+    """
+    Adds a Ctrl+C 'handler routine'. This allows the CTRL-C
+    signal to be received even when the process was created in
+    a new process group.
+
+    See module docstring for additional info.
+    """
+    assert sys.platform.startswith("win")
+    import ctypes
+
+    help_msg = dedent('''
+    Please open an issue here: https://github.com/chriskiehl/Gooey/issues
+        
+    GOOD NEWS: THERE IS A WORK AROUND: 
+    
+    Gooey only needs to attach special Windows handlers for the CTRL-C event. Consider using 
+    the `CTRL_BREAK_EVENT` or `SIGTERM` instead to get past this error. 
+    
+    See the Graceful Shutdown docs for information on how to catch alternative signals. 
+    
+    https://github.com/chriskiehl/Gooey/tree/master/docs 
+    ''')
+
+    try:
+        kernel32 = ctypes.WinDLL('kernel32')
+        if kernel32.SetConsoleCtrlHandler(None, 0) == 0:
+            raise Exception(dedent('''
+            Gooey was unable to install the handler required for processing
+            CTRL-C signals. This is a **very** unexpected failure. 
+            ''') + help_msg)
+    except OSError as e:
+        raise OSError(dedent('''
+            Gooey failed while trying to find the kernel32 module. 
+            Gooey requires this module in order to attach handlers for CTRL-C signal. 
+            Not being able to find this is **very** unexpected. 
+            ''') + help_msg)
diff --git a/gooey/python_bindings/types.py b/gooey/python_bindings/types.py
new file mode 100644
index 00000000..4cf040c7
--- /dev/null
+++ b/gooey/python_bindings/types.py
@@ -0,0 +1,388 @@
+from typing import Optional, Tuple, List, Union, Mapping, Any, TypeVar, Generic, Dict
+
+from dataclasses import dataclass
+from typing_extensions import TypedDict
+
+
+class MenuHtmlDialog(TypedDict):
+    type: str
+    menuTitle: str
+    caption: Optional[str]
+    html: str
+
+class MenuLink(TypedDict):
+    type: str
+    menuTitle: str
+    url: str
+
+
+class MenuMessageDialog(TypedDict):
+    type: str
+    menuTitle: str
+    message: str
+    caption: Optional[str]
+
+class MenuAboutDialog(TypedDict):
+    type: str
+    menuTitle: str
+    name: Optional[str]
+    description: Optional[str]
+    version: Optional[str]
+    copyright: Optional[str]
+    license: Optional[str]
+    website: Optional[str]
+    developer: Optional[str]
+
+
+MenuItem = Union[
+    MenuLink,
+    MenuMessageDialog,
+    MenuAboutDialog,
+    MenuHtmlDialog
+]
+
+
+
+class TimingOptions(TypedDict):
+    show_time_remaining: bool
+    hide_time_remaining_on_complete: bool
+
+
+class GooeyParams(TypedDict):
+    # when running with a custom target, there is no need to inject
+    # --ignore-gooey into the CLI args
+    show_preview_warning: bool
+    suppress_gooey_flag: bool
+    advanced: bool
+    language: str
+    target: Optional[str]
+    program_name: Optional[str]
+    program_description: Optional[str]
+    sidebar_title: str
+    default_size: Tuple[int, int]
+    auto_start: bool
+    show_advanced: bool
+    run_validators: bool
+    encoding: str
+    show_stop_warning: bool
+    show_success_modal: bool
+    show_failure_modal: bool
+    force_stop_is_error: bool
+    poll_external_updates: bool  # BEING DEPRECATED
+    return_to_config: bool
+    show_restart_button: bool
+    requires_shell: bool
+    menu: List[MenuItem]
+    clear_before_run: bool
+    fullscreen: bool
+    # Legacy/Backward compatibility interop
+    use_legacy_titles: bool
+    required_cols: int
+    optional_cols: int
+    manual_start: bool
+    monospace_display: bool
+
+    image_dir: str
+    language_dir: str
+    progress_regex: Optional[str]
+    progress_expr: Optional[str]
+    hide_progress_msg: bool
+    timing_options: TimingOptions
+    disable_progress_bar_animation: bool
+    disable_stop_button: bool
+    shutdown_signal: int
+    use_events: List[str]
+
+    # Layouts
+    navigation: str
+    show_sidebar: bool
+    tabbed_groups: bool
+    group_by_type: bool
+
+    # styles
+    body_bg_color: str
+    header_bg_color: str
+    header_height: int
+    header_show_title: bool
+    header_show_subtitle: bool
+    header_image_center: bool
+    footer_bg_color: str
+    sidebar_bg_color: str
+
+    # font family, weight, and size are determined at runtime
+    terminal_panel_color: str
+    terminal_font_color: str
+    terminal_font_family: Optional[str]
+    terminal_font_weight: Optional[int]
+    terminal_font_size: Optional[int]
+    richtext_controls: bool
+    error_color: str
+
+    use_cmd_args: bool
+    dump_build_config: bool
+    load_build_config: Optional[str]
+
+# TODO:
+# use the syntax here rather than inheritance, as the latter is a type error
+# https://jdkandersson.com/2020/01/27/python-typeddict-arbitrary-key-names-with-totality/
+# class BuildSpecification(GooeyParams):
+#     target: str
+#     widgets: str
+
+
+class BasicField(TypedDict):
+    id: str
+    type: str
+    # required: bool
+    # positional: bool
+    error: Optional[str]
+    enabled: bool
+    visible: bool
+
+class Dropdown(BasicField):
+    selected: int
+    choices: List[str]
+
+class Chooser(BasicField):
+    btn_label: str
+    value: str
+
+
+class MultiFileChooser(Chooser):
+    pass
+
+class FileChooser(Chooser):
+    pass
+
+class FileSaver(Chooser):
+    pass
+
+class DirChooser(Chooser):
+    pass
+
+class MultiDirChooser(Chooser):
+    pass
+
+class DateChooser(Chooser):
+    pass
+
+class TimeChooser(Chooser):
+    pass
+
+class ColourChooser(Chooser):
+    pass
+
+class Command(BasicField):
+    value: str
+    placeholder: str
+
+class Counter(BasicField):
+    selected: int
+    choices: List[str]
+
+class DropdownFilterable(BasicField):
+    value: str
+    choices: List[str]
+
+class Listbox(BasicField):
+    selected: List[str]
+    choices: List[str]
+
+class IntegerField(BasicField):
+    value: str
+    min: int
+    max: int
+
+class DecimalField(BasicField):
+    value: float
+    min: float
+    max: float
+
+class Slider(BasicField):
+    value: float
+    min: float
+    max: float
+
+class Textarea(BasicField):
+    value: float
+    height: int
+
+class TextField(BasicField):
+    value: str
+    placeholder: str
+
+
+class PasswordField(TextField):
+    pass
+
+
+class Checkbox(BasicField):
+    checked: bool
+
+
+class RadioGroup(BasicField):
+    selected: Optional[int]
+    options: List['FormField']
+
+
+FormField = Union[
+    Textarea,
+    Slider,
+    Command,
+    Counter,
+    Checkbox,
+    TextField,
+    Dropdown,
+    Chooser,
+    RadioGroup,
+    DropdownFilterable,
+    Listbox,
+    IntegerField
+]
+
+
+
+
+class FieldValue(TypedDict):
+    """
+    The current value of a widget in the UI.
+    TODO: Why are things like cmd and cli type tracked IN the
+    UI and returned as part of the getValue() call?
+    What the hell, young me?
+    """
+    id: str
+    cmd: Optional[str]
+    rawValue: str
+    placeholder: str
+    positional: bool
+    required: bool
+    enabled: bool
+    visible: bool
+    test: bool
+    error: Optional[str]
+    clitype: str
+    meta: Any
+
+
+class PublicGooeyState(TypedDict):
+    """
+    A minimal representation of Gooey's current UI state
+    """
+    active_form: List[FormField]
+
+
+class Group(TypedDict):
+    name: str
+    items: List['Item']
+    groups: List['Group']
+    description: str
+    options: Dict[Any, Any]
+
+
+class Item(TypedDict):
+    id: str
+    type: str
+    cli_type: str
+    group_name: str
+    required: bool
+    options: Dict[Any, Any]
+    data: 'ItemData'
+
+
+class EnrichedItem(Item):
+    """
+    An argparse item paired with its associated Gooey form
+    field and current state.
+    """
+    field: FormField
+
+
+ItemData = Union['StandardData', 'RadioData']
+
+class StandardData(TypedDict):
+    display_name: str
+    help: str
+    required: bool
+    nargs: str
+    commands: List[str]
+    choices: List[str]
+    default: Union[str, List[str]]
+    dest: str
+
+class RadioData(TypedDict):
+    commands: List[List[str]]
+    widgets: List[Item]
+
+
+class TopLevelParser(TypedDict):
+    command: str
+    name: str
+    help: Optional[str]
+    description: str
+    contents: List[Group]
+
+A = TypeVar('A')
+
+
+## TODO: dynamic types
+
+@dataclass(frozen=True, eq=True)
+class CommandDetails:
+    target: str
+    subcommand: str
+    positionals: List[FieldValue]
+    optionals: List[FieldValue]
+
+@dataclass(frozen=True, eq=True)
+class CommandPieces:
+    target: str
+    subcommand: str
+    positionals: List[str]
+    optionals: List[str]
+    ignoreFlag: str
+
+@dataclass(frozen=True, eq=True)
+class Success(Generic[A]):
+    value: A
+
+    def map(self, f):
+        return Success(f(self.value))
+    def flatmap(self, f):
+        return f(self.value)
+    def onSuccess(self, f):
+        f(self.value)
+        return self
+    def onError(self, f):
+        return self
+    def isSuccess(self):
+        return True
+    def getOrThrow(self):
+        return self.value
+
+@dataclass(frozen=True, eq=True)
+class Failure:
+    error: Exception
+
+    def map(self, f):
+        return Failure(self.error)
+    def flatmap(self, f):
+        return Failure(self.error)
+    def onSuccess(self, f):
+        return self
+    def onError(self, f):
+        f(self.error)
+        return self
+    def isSuccess(self):
+        return False
+    def getOrThrow(self):
+        raise self.error
+
+Try = Union[Success[A], Failure]
+
+
+
+ValidationResponse = Mapping[str, str]
+
+
+class InvalidChoiceException(ValueError):
+    pass
diff --git a/gooey/tests/__init__.py b/gooey/tests/__init__.py
index c2b3b5c1..4868a4c6 100644
--- a/gooey/tests/__init__.py
+++ b/gooey/tests/__init__.py
@@ -31,12 +31,47 @@
 ```
 """
 import wx
+import locale
+import platform
+
+class TestApp(wx.App):
+    """
+    Stolen from the mailing list here:
+    https://groups.google.com/g/wxpython-users/c/q5DSyyuKluA
+
+    Wx started randomly exploding with locale issues while running
+    the tests. For whatever reason, manually setting it in InitLocale
+    seems to solve the problem.
+    """
+    def __init__(self, with_c_locale=None, **kws):
+        if with_c_locale is None:
+            with_c_locale = (platform.system() == 'Windows')
+        self.with_c_locale = with_c_locale
+        wx.App.__init__(self, **kws)
+
+    def InitLocale(self):
+        """over-ride wxPython default initial locale"""
+        if self.with_c_locale:
+            self._initial_locale = None
+            locale.setlocale(locale.LC_ALL, 'C')
+        else:
+            lang, enc = locale.getdefaultlocale()
+            self._initial_locale = wx.Locale(lang, lang[:2], lang)
+            locale.setlocale(locale.LC_ALL, lang)
+
+    def OnInit(self):
+        self.createApp()
+        return True
+
+    def createApp(self):
+        return True
+
 
 app = None
 
 def setUpModule():
     global app
-    app = wx.App()
+    app = TestApp()
 
 def tearDownModule():
     global app
diff --git a/gooey/tests/all_widgets.py b/gooey/tests/all_widgets.py
index 7ad3e359..b8198380 100644
--- a/gooey/tests/all_widgets.py
+++ b/gooey/tests/all_widgets.py
@@ -1,101 +1,52 @@
-from gooey import Gooey
-from gooey import GooeyParser
-
-
-@Gooey(
-    sidebar_title="Your Custom Title",
-    show_sidebar=True,
-    dump_build_config=True,
-    show_success_modal=False,
-    force_stop_is_error=False,
-    language='chinese'
-)
-def main():
-    desc = "Example application to show Gooey's various widgets"
-    parser = GooeyParser(description=desc, add_help=False)
-
-    parser.add_argument('--textfield', default=2, widget="TextField", gooey_options={
-        'validator': {
-            'test': 'int(user_input) > 5',
-            'message': 'number must be greater than 5'
-        }
-    })
-    parser.add_argument('--textarea', default="oneline twoline", widget='Textarea')
-    parser.add_argument('--password', default="hunter42", widget='PasswordField')
-    parser.add_argument('--commandfield', default="cmdr", widget='CommandField')
-    parser.add_argument('--dropdown',
-                        choices=["one", "two"], default="two", widget='Dropdown')
-    parser.add_argument('--listboxie',
-                    nargs='+',
-                    default=['Option three', 'Option four'],
-                    choices=['Option one', 'Option two', 'Option three',
-                             'Option four'],
-                    widget='Listbox',
-                    gooey_options={
-                        'height': 300,
-                        'validate': '',
-                        'heading_color': '',
-                        'text_color': '',
-                        'hide_heading': True,
-                        'hide_text': True,
-                    }
-                )
-    parser.add_argument('-c', '--counter', default=3, action='count', widget='Counter')
-    #
-    parser.add_argument("-o", "--overwrite", action="store_true",
-                                default=True,
-                                widget='CheckBox')
-
-    ### Mutex Group ###
-    verbosity = parser.add_mutually_exclusive_group(
-        required=True,
-        gooey_options={
-            'initial_selection': 1
-        }
-    )
-    verbosity.add_argument(
-        '--mutexone',
-        default=True,
-        action='store_true',
-        help="Show more details")
-
-    verbosity.add_argument(
-        '--mutextwo',
-        default='mut-2',
-        widget='TextField')
-
-    parser.add_argument("--filechooser", default="fc-value", widget='FileChooser')
-    parser.add_argument("--filesaver", default="fs-value", widget='FileSaver')
-    parser.add_argument("--dirchooser", default="dc-value", widget='DirChooser')
-    parser.add_argument("--datechooser", default="2015-01-01", widget='DateChooser')
-    parser.add_argument("--colourchooser", default="#000000", widget='ColourChooser')
-
-    dest_vars = [
-        'textfield',
-        'textarea',
-        'password',
-        'commandfield',
-        'dropdown',
-        'listboxie',
-        'counter',
-        'overwrite',
-        'mutextwo',
-        'filechooser',
-        'filesaver',
-        'dirchooser',
-        'datechooser',
-        'colourchooser'
-
-    ]
-
-
-    args = parser.parse_args()
-    import time
-    time.sleep(3)
-    for i in dest_vars:
-        assert getattr(args, i) is not None
-    print("Success")
-
-
-if __name__ == '__main__':
-    main()
+"""
+Parser containing all Gooey widgets.
+"""
+
+from gooey import GooeyParser
+
+
+parser = GooeyParser()
+
+parser.add_argument('--textfield', default=2, widget="TextField")
+parser.add_argument('--textarea', default="oneline twoline", widget='Textarea')
+parser.add_argument('--password', default="hunter42", widget='PasswordField')
+parser.add_argument('--commandfield', default="cmdr", widget='CommandField')
+parser.add_argument('--dropdown', choices=["one", "two"], default="two", widget='Dropdown')
+parser.add_argument(
+    '--listboxie',
+    nargs='+',
+    default=['three', 'four'],
+    choices=['one', 'two', 'three', 'four'],
+    widget='Listbox',
+    gooey_options={
+        'height': 300,
+        'validate': '',
+        'heading_color': '',
+        'text_color': '',
+        'hide_heading': True,
+        'hide_text': True,
+    }
+)
+parser.add_argument('--counter', default=3, action='count', widget='Counter')
+parser.add_argument("--overwrite1", action="store_true", default=True, widget='CheckBox')
+parser.add_argument("--overwrite2", action="store_true", default=True, widget='BlockCheckbox')
+
+verbosity = parser.add_mutually_exclusive_group(
+    gooey_options={
+        'initial_selection': 0
+    }
+)
+verbosity.add_argument('--mutexone', default='hello')
+
+parser.add_argument('--mutextwo', default='3', widget='Slider')
+parser.add_argument('--mutextwo', default='1', widget='IntegerField')
+parser.add_argument('--mutextwo', default='4', widget='DecimalField')
+
+parser.add_argument("--filechooser", default="fc-value", widget='FileChooser')
+parser.add_argument("--filesaver", default="fs-value", widget='FileSaver')
+parser.add_argument("--dirchooser", default="dc-value", widget='DirChooser')
+parser.add_argument("--datechooser", default="2015-01-01", widget='DateChooser')
+parser.add_argument("--colourchooser", default="#000000", widget='ColourChooser')
+
+
+
diff --git a/gooey/tests/dynamics/__init__.py b/gooey/tests/dynamics/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/tests/dynamics/files/__init__.py b/gooey/tests/dynamics/files/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/tests/dynamics/files/basic.py b/gooey/tests/dynamics/files/basic.py
new file mode 100644
index 00000000..75474eb5
--- /dev/null
+++ b/gooey/tests/dynamics/files/basic.py
@@ -0,0 +1,24 @@
+from argparse import ArgumentParser
+
+from gooey import Events, Gooey
+
+
+with open('tmp.txt', 'w') as f:
+    import sys
+    f.write(str(sys.argv))
+
+def make_parser():
+    parser = ArgumentParser()
+    parser.add_argument('foo', type=int)
+    return parser
+
+@Gooey(use_events=[Events.VALIDATE_FORM])
+def main():
+    parser = make_parser()
+    print(parser.parse_args())
+    print('DONE')
+
+
+if __name__ == '__main__':
+    main()
+
diff --git a/gooey/tests/dynamics/files/lifecycles.py b/gooey/tests/dynamics/files/lifecycles.py
new file mode 100644
index 00000000..6b65e9c2
--- /dev/null
+++ b/gooey/tests/dynamics/files/lifecycles.py
@@ -0,0 +1,41 @@
+from argparse import ArgumentParser
+
+from gooey import Events, Gooey, GooeyParser
+from gooey import types as t
+
+
+with open('tmp.txt', 'w') as f:
+    import sys
+    f.write(str(sys.argv))
+
+
+
+def handle_success(args, state: t.PublicGooeyState):
+    field = state['active_form'][0]
+    field['value'] = 'success'
+    return {**state, 'active_form': [field]}
+
+
+def handle_error(args, state: t.PublicGooeyState):
+    field = state['active_form'][0]
+    field['value'] = 'error'
+    return {**state, 'active_form': [field]}
+
+
+def make_parser():
+    parser = GooeyParser(on_error=handle_error, on_success=handle_success)
+    parser.add_argument('foo')
+    return parser
+
+@Gooey(use_events=[Events.ON_ERROR, Events.ON_SUCCESS])
+def main():
+    parser = make_parser()
+    args = parser.parse_args()
+    if args.foo == 'fail':
+        raise Exception('EXCEPTION')
+    print('DONE')
+
+
+if __name__ == '__main__':
+    main()
+
diff --git a/gooey/tests/dynamics/files/tmp.txt b/gooey/tests/dynamics/files/tmp.txt
new file mode 100644
index 00000000..75379a20
--- /dev/null
+++ b/gooey/tests/dynamics/files/tmp.txt
@@ -0,0 +1 @@
+['C:/Users/Chris/Documents/Gooey/gooey/tests/dynamics/files/basic.py', '--ignore-gooey', '--', '1234']
\ No newline at end of file
diff --git a/gooey/tests/dynamics/test_dynamics.py b/gooey/tests/dynamics/test_dynamics.py
new file mode 100644
index 00000000..37bcea2f
--- /dev/null
+++ b/gooey/tests/dynamics/test_dynamics.py
@@ -0,0 +1,69 @@
+import unittest
+from argparse import ArgumentParser
+from typing import Dict
+from unittest.mock import MagicMock
+
+from python_bindings.dynamics import patch_argument, monkey_patch_for_form_validation
+
+
+class TestDynamicUpdates(unittest.TestCase):
+
+    def tearDown(self):
+        """
+        Undoes the monkey patching after every tests
+        """
+        if hasattr(ArgumentParser, 'original_parse_args'):
+            ArgumentParser.parse_args = ArgumentParser.original_parse_args
+
+    def test_patch_argument(self):
+        """
+        Asserting that regardless of parser complexity, we attach our
+        new argument at every level.
+        """
+        parser = ArgumentParser()
+        subparsers = parser.add_subparsers()
+        # multiple subparsers
+        a = subparsers.add_parser('a')
+        b = subparsers.add_parser('b')
+
+        a.add_argument('--level-1')
+        b.add_argument('--level-1')
+
+        # deeply nested subparsers
+        a_subparsers = a.add_subparsers()
+        b_subparsers = b.add_subparsers()
+
+        # nested args:
+        a__nested = a_subparsers.add_parser('a1')
+        b__nested = b_subparsers.add_parser('b1')
+
+        a__nested.add_argument('--level-2')
+        b__nested.add_argument('--level-2')
+
+        # sanity check / showing the parser behavior
+        # we've got two levels of parser nesting, each level
+        # has some options available.
+        mock = MagicMock()
+        ArgumentParser.error = mock
+        assert parser.parse_args('a --level-1 some-value'.split())
+        assert parser.parse_args('b --level-1 some-value'.split())
+        assert parser.parse_args('a a1 --level-2 some-value'.split())
+        assert parser.parse_args('b b1 --level-2 some-value'.split())
+        assert not mock.called
+
+        # if we try passing an arbitrary unknown flag we explode
+        # patching over the `error` method which usually sys.exit's
+        # for any errors.
+        parser.parse_args('a --level-1 some-value --some-flag'.split())
+        assert mock.called
+
+        patch_argument(parser, '--some-flag', action='store_true')
+        mock.reset_mock()
+        # now ever call combination accepts the flag we added
+        assert parser.parse_args('--some-flag'.split())
+        assert parser.parse_args('a --level-1 some-value --some-flag'.split())
+        assert parser.parse_args('b --level-1 some-value --some-flag'.split())
+        assert parser.parse_args('a a1 --level-2 some-value --some-flag'.split())
+        assert parser.parse_args('b b1 --level-2 some-value --some-flag'.split())
+        assert not mock.called
+
diff --git a/gooey/tests/dynamics/test_live_updates.py b/gooey/tests/dynamics/test_live_updates.py
new file mode 100644
index 00000000..36248815
--- /dev/null
+++ b/gooey/tests/dynamics/test_live_updates.py
@@ -0,0 +1,101 @@
+import sys
+import unittest
+from copy import deepcopy
+
+from gooey import Events
+from gooey.tests.harness import instrumentGooey
+from gooey.tests import *
+
+class TestLiveDynamicUpdates(unittest.TestCase):
+
+    def test_validate_form(self):
+        """
+        Integration testing the Dynamic Validation features.
+        """
+        # Because it's a live test, nothing is mocked. This basic.py file
+        # will be called via subprocess as part of the test. As such, we
+        # grab both its path on disk (for use as a target for Gooey) as
+        # well as its parser instance (so that we can bootstrap)
+        from gooey.tests.dynamics.files import basic
+        params = {
+            'target': '{} -u {}'.format(sys.executable, basic.__file__),
+            'use_events': [Events.VALIDATE_FORM],
+        }
+        with instrumentGooey(basic.make_parser(), **params) as (app, frame, gapp):
+            # the parser has a single arg of type int.
+            # We purposefully give it invalid input for the sake of the test.
+            gapp.getActiveConfig().widgetsMap['foo'].setValue('not a number')
+            # and make sure we're not somehow starting with an error
+            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')
+            gapp.onStart()
+
+            # All subprocess calls ultimately pump though wx's event queue
+            # so we have to kick off the mainloop and let it run long enough
+            # to let the subprocess complete and the event queue flush
+            wx.CallLater(2500, app.ExitMainLoop)
+            app.MainLoop()
+            # after the subprocess call is complete, our UI should have
+            # been updated with the data dynamically returned from the
+            # basic.py invocation.
+            self.assertIn('invalid literal', gapp.getActiveFormState()[0]['error'])
+
+
+    def test_validate_form_without_errors(self):
+        from gooey.tests.dynamics.files import basic
+        params = {
+            'target': '{} -u {}'.format(sys.executable, basic.__file__),
+            'use_events': [Events.VALIDATE_FORM],
+            # setting to false because it interferes with the test
+            'show_success_modal': False
+        }
+        with instrumentGooey(basic.make_parser(), **params) as (app, frame, gapp):
+            gapp.getActiveConfig().widgetsMap['foo'].setValue('10')  # valid int
+            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')
+            gapp.onStart()
+
+            wx.CallLater(2500, app.ExitMainLoop)
+            app.MainLoop()
+            # no errors blocked the run, so we should have executed and finished.
+            # we're now on the success screen.
+            self.assertEqual(gapp.state['image'], gapp.state['images']['successIcon'])
+            # and indeed no errors were written to the UI
+            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')
+            # and we find the expected output written to the console
+            # rather than some unexpected error
+            self.assertIn('DONE', frame.FindWindowByName("console").getText())
+
+
+    def test_lifecycle_handlers(self):
+        cases = [
+            {'input': 'happy path', 'expected_stdout': 'DONE', 'expected_update': 'success'},
+            {'input': 'fail', 'expected_stdout': 'EXCEPTION', 'expected_update': 'error'}
+        ]
+        from gooey.tests.dynamics.files import lifecycles
+        params = {
+            'target': '{} -u {}'.format(sys.executable, lifecycles.__file__),
+            'use_events': [Events.ON_SUCCESS, Events.ON_ERROR],
+            'show_success_modal': False,
+            'show_failure_modal': False
+        }
+        for case in cases:
+            with self.subTest(case):
+                with instrumentGooey(lifecycles.make_parser(), **params) as (app, frame, gapp):
+                    gapp.getActiveConfig().widgetsMap['foo'].setValue(case['input'])
+                    gapp.onStart()
+                    # give everything a chance to run
+                    wx.CallLater(2000, app.ExitMainLoop)
+                    app.MainLoop()
+                    # `lifecycle.py` is set up to raise an exception for certain inputs
+                    # so we check that we find our expected stdout here
+                    console = frame.FindWindowByName("console")
+                    self.assertIn(case['expected_stdout'], console.getText())
+
+                    # Now, based on what happened during the run (success/exception) our
+                    # respective lifecycle handler should have been called. These are
+                    # configured to update the form field in the UI with a relevant value.
+                    # Thus we we're checking here to see that out input has changed, and now
+                    # matches the value we expect from the handler
+                    textfield = gapp.getActiveFormState()[0]
+                    print(case['expected_update'], textfield['value'])
+                    self.assertEqual(case['expected_update'], textfield['value'])
+
diff --git a/gooey/tests/dynamics/tmp.txt b/gooey/tests/dynamics/tmp.txt
new file mode 100644
index 00000000..80c673c7
--- /dev/null
+++ b/gooey/tests/dynamics/tmp.txt
@@ -0,0 +1 @@
+['C:\\Users\\Chris\\Documents\\Gooey\\gooey\\tests\\dynamics\\files\\basic.py', '--ignore-gooey', '--', '10']
\ No newline at end of file
diff --git a/gooey/tests/harness.py b/gooey/tests/harness.py
index bd5d2894..36553ad2 100644
--- a/gooey/tests/harness.py
+++ b/gooey/tests/harness.py
@@ -2,19 +2,19 @@
 
 import time
 from threading import Thread
+from typing import Tuple
 
 import wx
 
-from gooey.gui import application
-from python_bindings.config_generator import create_from_parser
-from python_bindings.gooey_decorator import defaults
-from util.functional import merge
-
-
+from gooey.gui import bootstrap
+from gooey.python_bindings.config_generator import create_from_parser
+from gooey.python_bindings.parameters import gooey_params
+from gooey.util.functional import merge
+from gooey.gui.application.application import RGooey
 
 
 @contextmanager
-def instrumentGooey(parser, **kwargs):
+def instrumentGooey(parser, **kwargs) -> Tuple[wx.App, wx.Frame, RGooey]:
     """
     Context manager used during testing for setup/tear down of the
     WX infrastructure during subTests.
@@ -26,13 +26,19 @@ def instrumentGooey(parser, **kwargs):
         raise Exception("App instance has not been created! This is likely due to "
                         "you forgetting to add the magical import which makes all these "
                         "tests work. See the module doc in gooey.tests.__init__ for guidance")
-    buildspec = create_from_parser(parser, "", **merge(defaults, kwargs))
-    app, gooey = application._build_app(buildspec, app)
-    app.SetTopWindow(gooey)
+    buildspec = create_from_parser(parser, "", **gooey_params(**kwargs))
+    app, frame = bootstrap._build_app(buildspec, app)
+    app.SetTopWindow(frame)
     try:
-        yield (app, gooey)
+        # we need to run the main loop temporarily to get it to
+        # apply any pending updates from the initial creation.
+        # The UI state will be stale otherwise
+        # this works because CallLater just enqueues the message to
+        # be processed. The MainLoop starts running, picks it up, and
+        # then exists
+        wx.CallLater(1, app.ExitMainLoop)
+        app.MainLoop()
+        yield (app, frame, frame._instance)
     finally:
-        wx.CallAfter(app.ExitMainLoop)
-        gooey.Destroy()
-        app.SetTopWindow(None)
-        del gooey
+        frame.Destroy()
+        del frame
diff --git a/gooey/tests/processor/__init__.py b/gooey/tests/processor/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/tests/processor/files/__init__.py b/gooey/tests/processor/files/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gooey/tests/processor/files/ignore_break.py b/gooey/tests/processor/files/ignore_break.py
new file mode 100644
index 00000000..f18457dd
--- /dev/null
+++ b/gooey/tests/processor/files/ignore_break.py
@@ -0,0 +1,20 @@
+"""
+Python file for Processor test suite
+
+Short 1s loop which purposefully ignores
+Keyboard Interrupts in order to continue
+executing
+"""
+
+import time
+import signal
+
+def ignored_it(*args):
+    print("INTERRUPT")
+
+signal.signal(signal.SIGBREAK, ignored_it)
+
+
+while True:
+    print(time.time())
+    time.sleep(0.1)
\ No newline at end of file
diff --git a/gooey/tests/processor/files/ignore_interrupt.py b/gooey/tests/processor/files/ignore_interrupt.py
new file mode 100644
index 00000000..1b2d7d97
--- /dev/null
+++ b/gooey/tests/processor/files/ignore_interrupt.py
@@ -0,0 +1,25 @@
+"""
+Python file for Processor test suite
+
+Infinite loop which purposefully ignores
+Keyboard Interrupts in order to continue
+executing. The only way to kill it is via
+SIGTERM family signals.
+"""
+
+import time
+import sys
+
+if sys.platform.startswith('win'):
+    import ctypes
+    kernel32 = ctypes.WinDLL('kernel32')
+    kernel32.SetConsoleCtrlHandler(None, 0)
+
+
+while True:
+    try:
+        print(time.time())
+        time.sleep(0.1)
+    except KeyboardInterrupt:
+        # Ignored!
+        print("INTERRUPT")
diff --git a/gooey/tests/processor/files/infinite_loop.py b/gooey/tests/processor/files/infinite_loop.py
new file mode 100644
index 00000000..8ba82043
--- /dev/null
+++ b/gooey/tests/processor/files/infinite_loop.py
@@ -0,0 +1,18 @@
+"""
+Python file for Processor test suite
+
+Infinite loop which would continue forever if not
+interrupted.
+"""
+import time
+import sys
+
+if sys.platform.startswith('win'):
+    import ctypes
+    kernel32 = ctypes.WinDLL('kernel32')
+    kernel32.SetConsoleCtrlHandler(None, 0)
+
+
+while True:
+    print(time.time())
+    time.sleep(0.1)
\ No newline at end of file
diff --git a/gooey/tests/processor/test_processor.py b/gooey/tests/processor/test_processor.py
new file mode 100644
index 00000000..eb6f4bf3
--- /dev/null
+++ b/gooey/tests/processor/test_processor.py
@@ -0,0 +1,125 @@
+import re
+import signal
+import subprocess
+import sys
+import unittest
+import os
+import time
+
+import wx
+
+from gooey.gui import events, processor
+from gooey.gui.pubsub import pub
+from gooey.gui.processor import ProcessController
+
+
+class TestProcessor(unittest.TestCase):
+
+    def test_extract_progress(self):
+        # should pull out a number based on the supplied
+        # regex and expression
+        processor = ProcessController(r"^progress: (\d+)%$", None, False, 'utf-8')
+        self.assertEqual(processor._extract_progress(b'progress: 50%'), 50)
+
+        processor = ProcessController(r"total: (\d+)%$", None, False, 'utf-8')
+        self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)
+
+    def test_extract_progress_returns_none_if_no_regex_supplied(self):
+        processor = ProcessController(None, None, False, 'utf-8')
+        self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))
+
+
+    def test_extract_progress_returns_none_if_no_match_found(self):
+        processor = ProcessController(r'(\d+)%$', None, False, 'utf-8')
+        self.assertIsNone(processor._extract_progress(b'No match in dis string'))
+
+
+    def test_eval_progress(self):
+        # given a match in the string, should eval the result
+        regex = r'(\d+)/(\d+)$'
+        processor = ProcessController(regex, r'x[0] / x[1]', False,False, 'utf-8')
+        match = re.search(regex, '50/50')
+        self.assertEqual(processor._eval_progress(match), 1.0)
+    def test_eval_progress_returns_none_on_failure(self):
+        # given a match in the string, should eval the result
+        regex = r'(\d+)/(\d+)$'
+        processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,'utf-8')
+        match = re.search(regex, '50/50')
+        self.assertIsNone(processor._eval_progress(match))
+
+
+    def test_all_interrupts_halt_process(self):
+        """
+        TODO: These tests are hella flaky. I'm confident that the feature works. However, getting
+        signals, subprocesses and unittest to all play together reliably is proving tricky. It
+        primarily seems to come down to how long the time.sleep() is before sending the shutdown
+        signal.
+        """
+
+        cmd = 'python ' + os.path.join(os.getcwd(), 'files', 'infinite_loop.py')
+
+        try:
+            import _winapi
+            signals = [signal.SIGTERM, signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT]
+        except ModuleNotFoundError:
+            signals = [signal.SIGTERM, signal.SIGINT]
+        try:
+            for sig in signals:
+                print('sig', sig)
+                processor = ProcessController(None, None, False, 'utf-8', True, shutdown_signal=sig)
+
+                processor.run(cmd)
+                self.assertTrue(processor.running())
+
+                # super-duper important sleep so that the
+                # signal is actually received by the child process
+                # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest
+                time.sleep(1)
+                processor.stop()
+                max_wait = time.time() + 4
+                while processor.running() and time.time() < max_wait:
+                    time.sleep(0.1)
+                self.assertFalse(processor.running())
+        except KeyboardInterrupt:
+            pass
+
+
+    def test_ignore_sigint_family_signals(self):
+        try:
+            import _winapi
+            signals = [signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT]
+            programs = ['ignore_break.py', 'ignore_interrupt.py']
+        except ModuleNotFoundError:
+            signals = [signal.SIGINT]
+            programs = ['ignore_interrupt.py']
+
+
+        for program, sig in zip(programs, signals):
+            cmd = sys.executable + ' ' + os.path.join(os.getcwd(), 'files', program)
+            process = processor = ProcessController(None, None, False, 'utf-8', True, shutdown_signal=sig, testmode=True)
+            process.run(cmd)
+            # super-duper important sleep so that the
+            # signal is actually received by the child process
+            # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest
+            time.sleep(1)
+            process.send_shutdown_signal()
+            # wait to give stdout enough time to write
+            time.sleep(1)
+            # now our signal should have been received, but rejected.
+            self.assertTrue(processor.running())
+            # so we sigterm to actually shut down the process.
+            process._send_signal(signal.SIGTERM)
+            # sanity wait
+            max_wait = time.time() + 2
+            while processor.running() and time.time() < max_wait:
+                time.sleep(0.1)
+            # now we should be shut down due to killing the process.
+            self.assertFalse(processor.running())
+            # and we'll see in the stdout out from the process that our
+            # interrupt was received
+            output = process._process.stdout.read().decode('utf-8')
+            self.assertIn("INTERRUPT", str(output))
+            # but indeed ignored. It continued running and writing to stdout after
+            # receiving the signal
+            self.assertTrue(output.index("INTERRUPT") < len(output))
+
diff --git a/gooey/tests/test_application.py b/gooey/tests/test_application.py
index ccc12a1a..c6bcc4bb 100644
--- a/gooey/tests/test_application.py
+++ b/gooey/tests/test_application.py
@@ -2,6 +2,7 @@
 import unittest
 from argparse import ArgumentParser
 from collections import namedtuple
+from pprint import pprint
 from unittest.mock import patch
 from unittest.mock import MagicMock
 
@@ -16,8 +17,8 @@ def testFullscreen(self):
         parser = self.basicParser()
         for shouldShow in [True, False]:
             with self.subTest('Should set full screen: {}'.format(shouldShow)):
-                with instrumentGooey(parser, fullscreen=shouldShow) as (app, gapp):
-                    self.assertEqual(gapp.IsFullScreen(), shouldShow)
+                with instrumentGooey(parser, fullscreen=shouldShow) as (app, frame, gapp):
+                    self.assertEqual(frame.IsFullScreen(), shouldShow)
 
 
     @patch("gui.containers.application.modals.confirmForceStop")
@@ -39,12 +40,12 @@ def testGooeyRequestsConfirmationWhenShowStopWarningModalTrue(self, mockModal):
         for case in testcases:
             mockModal.reset_mock()
             parser = self.basicParser()
-            with instrumentGooey(parser, show_stop_warning=case.show_warning) as (app, gapp):
+            with instrumentGooey(parser, show_stop_warning=case.show_warning) as (app, frame, gapp):
                 mockClientRunner = MagicMock()
                 mockModal.return_value = case.userChooses
                 gapp.clientRunner = mockClientRunner
 
-                gapp.onStopExecution()
+                gapp.handleInterrupt()
 
                 if case.shouldSeeConfirm:
                     mockModal.assert_called()
@@ -56,21 +57,21 @@ def testGooeyRequestsConfirmationWhenShowStopWarningModalTrue(self, mockModal):
                 else:
                     mockClientRunner.stop.assert_not_called()
 
-    @patch("gui.containers.application.modals.confirmForceStop")
-    def testOnCloseShutsDownActiveClients(self, mockModal):
-        """
-        Issue 592: Closing the UI should clean up any actively running programs
-        """
-        parser = self.basicParser()
-        with instrumentGooey(parser) as (app, gapp):
-            gapp.clientRunner = MagicMock()
-            gapp.destroyGooey = MagicMock()
-            # mocking that the user clicks "yes shut down" in the warning modal
-            mockModal.return_value = True
-            gapp.onClose()
-
-            mockModal.assert_called()
-            gapp.destroyGooey.assert_called()
+    # @patch("gui.containers.application.modals.confirmForceStop")
+    # def testOnCloseShutsDownActiveClients(self, mockModal):
+    #     """
+    #     Issue 592: Closing the UI should clean up any actively running programs
+    #     """
+    #     parser = self.basicParser()
+    #     with instrumentGooey(parser) as (app, frame):
+    #         frame.clientRunner = MagicMock()
+    #         frame.destroyGooey = MagicMock()
+    #         # mocking that the user clicks "yes shut down" in the warning modal
+    #         mockModal.return_value = True
+    #         frame._instance.handleClose()
+    #
+    #         mockModal.assert_called()
+    #         frame.destroyGooey.assert_called()
 
 
     def testTerminalColorChanges(self):
@@ -78,8 +79,8 @@ def testTerminalColorChanges(self):
         parser = self.basicParser()
         expectedColors = [(255, 0, 0, 255), (255, 255, 255, 255), (100, 100, 100,100)]
         for expectedColor in expectedColors:
-            with instrumentGooey(parser, terminal_panel_color=expectedColor) as (app, gapp):
-                foundColor = gapp.console.GetBackgroundColour()
+            with instrumentGooey(parser, terminal_panel_color=expectedColor) as (app, frame, gapp):
+                foundColor = gapp.consoleRef.instance.GetBackgroundColour()
                 self.assertEqual(tuple(foundColor), expectedColor)
 
 
@@ -87,11 +88,33 @@ def testFontWeightsGetSet(self):
         ## Issue #625 font weight wasn't being correctly passed to the terminal
         for weight in [constants.FONTWEIGHT_LIGHT, constants.FONTWEIGHT_BOLD]:
             parser = self.basicParser()
-            with instrumentGooey(parser, terminal_font_weight=weight) as (app, gapp):
-                terminal = gapp.console.textbox
+            with instrumentGooey(parser, terminal_font_weight=weight) as (app, frame, gapp):
+                terminal = gapp.consoleRef.instance.textbox
                 self.assertEqual(terminal.GetFont().GetWeight(), weight)
 
 
+    def testProgressBarHiddenWhenDisabled(self):
+        options = [
+            {'disable_progress_bar_animation': True},
+            {'disable_progress_bar_animation': False},
+            {}
+        ]
+        for kwargs in options:
+            parser = self.basicParser()
+            with instrumentGooey(parser, **kwargs) as (app, frame, gapp):
+                mockClientRunner = MagicMock()
+                frame.clientRunner = mockClientRunner
+
+                # transition's Gooey to the running state using the now mocked processor.
+                # so that we can make assertions about the visibility of footer buttons
+                gapp.onStart()
+
+                # the progress bar flag is awkwardly inverted (is_disabled, rather than
+                # is_enabled). Thus inverting the expectation here. When disabled is true,
+                # shown should be False,
+                expect_shown = not kwargs.get('disable_progress_bar_animation', False)
+                self.assertEqual(gapp.state['progress']['show'], expect_shown)
+
     def basicParser(self):
         parser = ArgumentParser()
         parser.add_argument('--foo')
diff --git a/gooey/tests/test_argparse_to_json.py b/gooey/tests/test_argparse_to_json.py
index e1d1b4fd..2505a27c 100644
--- a/gooey/tests/test_argparse_to_json.py
+++ b/gooey/tests/test_argparse_to_json.py
@@ -1,12 +1,15 @@
 import argparse
 import sys
 import unittest
-from argparse import ArgumentParser
+from argparse import ArgumentParser, FileType
 
 from gooey import GooeyParser
 from gooey.python_bindings import argparse_to_json
 from gooey.util.functional import getin
 from gooey.tests import *
+from gui.components.options.options import FileChooser
+from gui.components.widgets import FileSaver
+
 
 class TestArgparse(unittest.TestCase):
 
@@ -149,12 +152,32 @@ def test_suppress_is_removed_as_default_value(self):
         parser.add_argument("--foo", default=argparse.SUPPRESS)
         parser.add_argument('--version', action='version', version='1.0')
 
-        result = argparse_to_json.convert(parser, num_required_cols=2, num_optional_cols=2)
+        result = argparse_to_json.convert(parser, required_cols=2, optional_cols=2)
         groups = getin(result, ['widgets', 'test_program', 'contents'])
         for item in groups[0]['items']:
             self.assertEqual(getin(item, ['data', 'default']), None)
 
 
+    def test_version_maps_to_checkbox(self):
+        testcases = [
+            [['--version'], {}, 'TextField'],
+            # we only remap if the action is version
+            # i.e. we don't care about the argument name itself
+            [['--version'], {'action': 'store'}, 'TextField'],
+            # should get mapped to CheckBox because of the action
+            [['--version'], {'action': 'version'}, 'CheckBox'],
+            # ditto, even through the 'name' isn't 'version'
+            [['--foobar'], {'action': 'version'}, 'CheckBox'],
+        ]
+        for args, kwargs, expectedType in testcases:
+            with self.subTest([args, kwargs]):
+                parser = argparse.ArgumentParser(prog='test')
+                parser.add_argument(*args, **kwargs)
+                result = argparse_to_json.convert(parser, required_cols=2, optional_cols=2)
+                contents = getin(result, ['widgets', 'test', 'contents'])[0]
+                self.assertEqual(contents['items'][0]['type'], expectedType)
+
+
     def test_textinput_with_list_default_mapped_to_cli_friendly_value(self):
         """
         Issue: #500
@@ -190,7 +213,7 @@ def test_textinput_with_list_default_mapped_to_cli_friendly_value(self):
                 parser = ArgumentParser(prog='test_program')
                 parser.add_argument('--foo', nargs=case['nargs'], default=case['default'])
                 action = parser._actions[-1]
-                result = argparse_to_json.handle_default(action, case['w'])
+                result = argparse_to_json.handle_initial_values(action, case['w'], action.default)
                 self.assertEqual(result, case['gooey_default'])
 
     def test_nargs(self):
@@ -239,3 +262,31 @@ def test_nargs(self):
             choices=["one", "two"],
             default="one",
         )
+
+
+    def test_filetype_chooses_good_widget(self):
+        """
+        #743 chose the picker type based on the FileType mode
+        when available.
+        """
+        cases = [
+            (FileType(), 'FileChooser'),
+            (FileType('r'), 'FileChooser'),
+            (FileType('rb'), 'FileChooser'),
+            (FileType('rt'), 'FileChooser'),
+            (FileType('w'), 'FileSaver'),
+            (FileType('wt'), 'FileSaver'),
+            (FileType('wb'), 'FileSaver'),
+            (FileType('a'), 'FileSaver'),
+            (FileType('x'), 'FileSaver'),
+            (FileType('+'), 'FileSaver'),
+        ]
+
+        for filetype, expected_widget in cases:
+            with self.subTest(f'expect {filetype} to produce {expected_widget})'):
+                parser = ArgumentParser()
+                parser.add_argument('foo', type=filetype)
+                action = [parser._actions[-1]]
+                result = next(argparse_to_json.categorize(action, {}, {}))
+                self.assertEqual(result['type'], expected_widget)
+
diff --git a/gooey/tests/test_checkbox.py b/gooey/tests/test_checkbox.py
new file mode 100644
index 00000000..0b780d26
--- /dev/null
+++ b/gooey/tests/test_checkbox.py
@@ -0,0 +1,58 @@
+import unittest
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+
+
+class TestCheckbox(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument(
+            '--widget',
+            action='store_true',
+            **kwargs)
+        return parser
+
+
+    def testInitialValue(self):
+        cases = [
+            # `initial` should supersede `default`
+            {'inputs': {'default': False,
+                        'widget': 'CheckBox',
+                        'gooey_options': {'initial_value': True}},
+             'expect': True},
+
+            {'inputs': {'gooey_options': {'initial_value': True},
+                        'widget': 'CheckBox'},
+             'expect': True},
+
+            {'inputs': {'gooey_options': {'initial_value': False},
+                        'widget': 'CheckBox'},
+             'expect': False},
+
+            {'inputs': {'default': True,
+                        'widget': 'CheckBox',
+                        'gooey_options': {}},
+             'expect': True},
+
+            {'inputs': {'default': True,
+                        'widget': 'CheckBox'},
+             'expect': True},
+
+            {'inputs': {'widget': 'CheckBox'},
+             'expect': False}
+        ]
+        for case in cases:
+            with self.subTest(case):
+                parser = self.makeParser(**case['inputs'])
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    widget = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_cli.py b/gooey/tests/test_cli.py
new file mode 100644
index 00000000..85f540f8
--- /dev/null
+++ b/gooey/tests/test_cli.py
@@ -0,0 +1,21 @@
+import unittest
+from gooey.gui import cli
+
+
+class TestCliStringCreation(unittest.TestCase):
+
+    # TODO: exercise the formValidationCase (which will require tedious test data creation)
+    def test_cli(self):
+        print(cli.buildCliString('target', 'cmd', ['pos1', 'pos2'], ['-a 1', '-b 2']))
+
+        positionals = [
+            {'clitype': 'positional', 'cmd': 'pos1', 'required': True},
+            {'clitype': 'positional', 'cmd': 'pos2', 'required': True}
+        ]
+
+        optionals = [
+            {'clitype': 'optional', 'cmd': '-a 1', 'required': False},
+            {'clitype': 'optional', 'cmd': '-b 2', 'required': False},
+        ]
+
+        # print(cli.formValidationCmd('target', 'cmd', positionals, optionals))
\ No newline at end of file
diff --git a/gooey/tests/test_common.py b/gooey/tests/test_common.py
new file mode 100644
index 00000000..87f5bcd8
--- /dev/null
+++ b/gooey/tests/test_common.py
@@ -0,0 +1,54 @@
+import unittest
+from collections import namedtuple
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+Case = namedtuple('Case', 'inputs initialExpected')
+
+
+class TestCommonProperties(unittest.TestCase):
+    """
+    Test options and functionality
+    common across all widgets.
+    """
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--widget', **kwargs)
+        return parser
+
+    def testInitialValue(self):
+        widgets = ['ColourChooser',
+                   'CommandField',
+                   'DateChooser', 'DirChooser', 'FileChooser', 'FileSaver',
+                   'FilterableDropdown',  'MultiDirChooser', 'MultiFileChooser',
+                   'PasswordField',  'TextField', 'Textarea', 'TimeChooser']
+
+        cases = [
+            # initial_value supersedes, default
+            Case(
+                {'default': 'default', 'gooey_options': {'initial_value': 'some val'}},
+                'some val'),
+            Case(
+                {'gooey_options': {'initial_value': 'some val'}},
+                 'some val'),
+            Case(
+                {'default': 'default', 'gooey_options': {}},
+                 'default'),
+            Case({'default': 'default'},
+                 'default')
+        ]
+
+        for widgetName in widgets:
+            with self.subTest(widgetName):
+                for case in cases:
+                    parser = self.makeParser(widget=widgetName, **case.inputs)
+                    with instrumentGooey(parser) as (app, frame, gapp):
+                        widget = gapp.getActiveConfig().reifiedWidgets[0]
+                        self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/gooey/tests/test_config_generator.py b/gooey/tests/test_config_generator.py
index f2538b57..09258d04 100644
--- a/gooey/tests/test_config_generator.py
+++ b/gooey/tests/test_config_generator.py
@@ -4,6 +4,8 @@
 from python_bindings import constants
 from python_bindings.config_generator import create_from_parser
 from gooey.tests import *
+from gooey.python_bindings.parameters import gooey_params
+
 
 class TextConfigGenerator(unittest.TestCase):
 
@@ -15,16 +17,17 @@ def test_program_description(self):
 
         parser = ArgumentParser(description="Parser Description")
         # when supplied explicitly, we assign it as the description
-        buildspec = create_from_parser(parser, "", program_description='Custom Description')
+        params = gooey_params(program_description='Custom Description')
+        buildspec = create_from_parser(parser, "", **params)
         self.assertEqual(buildspec['program_description'], 'Custom Description')
 
         # when no explicit program_definition supplied, we fallback to the parser's description
-        buildspec = create_from_parser(parser, "")
+        buildspec = create_from_parser(parser, "", **gooey_params())
         self.assertEqual(buildspec['program_description'], 'Parser Description')
 
         # if no description is provided anywhere, we just set it to be an empty string.
         blank_parser = ArgumentParser()
-        buildspec = create_from_parser(blank_parser, "")
+        buildspec = create_from_parser(blank_parser, "", **gooey_params())
         self.assertEqual(buildspec['program_description'], '')
 
     def test_valid_font_weights(self):
@@ -34,13 +37,14 @@ def test_valid_font_weights(self):
         all_valid_weights = range(100, 1001, 100)
         for weight in all_valid_weights:
             parser = ArgumentParser(description="test parser")
-            buildspec = create_from_parser(parser, "", terminal_font_weight=weight)
+            params = gooey_params(terminal_font_weight=weight)
+            buildspec = create_from_parser(parser, "", **params)
             self.assertEqual(buildspec['terminal_font_weight'], weight)
 
     def test_font_weight_defaults_to_normal(self):
         parser = ArgumentParser(description="test parser")
         # no font_weight explicitly provided
-        buildspec = create_from_parser(parser, "")
+        buildspec = create_from_parser(parser, "", **gooey_params())
         self.assertEqual(buildspec['terminal_font_weight'], constants.FONTWEIGHT_NORMAL)
 
 
@@ -48,7 +52,8 @@ def test_invalid_font_weights_throw_error(self):
         parser = ArgumentParser(description="test parser")
         with self.assertRaises(ValueError):
             invalid_weight = 9123
-            buildspec = create_from_parser(parser, "", terminal_font_weight=invalid_weight)
+            params = gooey_params(terminal_font_weight=invalid_weight)
+            buildspec = create_from_parser(parser, "", **params)
 
 
 if __name__ == '__main__':
diff --git a/gooey/tests/test_control.py b/gooey/tests/test_control.py
new file mode 100644
index 00000000..5345dbd1
--- /dev/null
+++ b/gooey/tests/test_control.py
@@ -0,0 +1,235 @@
+import json
+import unittest
+from argparse import ArgumentParser
+from contextlib import contextmanager
+from pprint import pprint
+from typing import Dict, List
+from unittest.mock import MagicMock, patch
+
+import sys
+import shlex
+
+from wx._core import CommandEvent
+
+from gooey import GooeyParser
+from python_bindings.coms import decode_payload, deserialize_inbound
+from python_bindings.dynamics import patch_argument, check_value
+from gooey.python_bindings import control
+from gooey.python_bindings.parameters import gooey_params
+from gooey.gui import state as s
+from gooey.python_bindings.schema import validate_public_state
+from python_bindings.types import FormField
+
+from tests.harness import instrumentGooey
+
+from gooey.tests import *
+
+
+def custom_type(x):
+    if x == '1234':
+        return x
+    else:
+        raise Exception('KABOOM!')
+
+
+class TestControl(unittest.TestCase):
+
+    def tearDown(self):
+        """
+        Undoes the monkey patching after every tests
+        """
+        if hasattr(ArgumentParser, 'original_parse_args'):
+            ArgumentParser.parse_args = ArgumentParser.original_parse_args
+
+    def test_validate_form(self):
+        """
+        Testing the major validation cases we support.
+        """
+        writer = MagicMock()
+        exit = MagicMock()
+        monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)
+        ArgumentParser.original_parse_args = ArgumentParser.parse_args
+        ArgumentParser.parse_args = monkey_patch
+
+        parser = GooeyParser()
+        # examples:
+        # ERROR: mismatched builtin type
+        parser.add_argument('a', type=int, gooey_options={'initial_value': 'not-an-int'})
+        # ERROR: mismatched custom type
+        parser.add_argument('b', type=custom_type, gooey_options={'initial_value': 'not-a-float'})
+        # ERROR: missing required positional arg
+        parser.add_argument('c')
+        # ERROR: missing required 'optional' arg
+        parser.add_argument('--oc', required=True)
+        # VALID: This is one of the bizarre cases which are possible
+        # but don't make much sense. It should pass through as valid
+        # because there's no way for us to send a 'not present optional value'
+        parser.add_argument('--bo', action='store_true', required=True)
+        # ERROR: a required mutex group, with no args supplied.
+        # Should flag all as missing.
+        group = parser.add_mutually_exclusive_group(required=True)
+        group.add_argument('--gp1-a', type=str)
+        group.add_argument('--gp1-b', type=str)
+
+        # ERROR: required mutex group with a default option but nothing
+        # selected will still fail
+        group2 = parser.add_mutually_exclusive_group(required=True)
+        group2.add_argument('--gp2-a', type=str)
+        group2.add_argument('--gp2-b', type=str, default='Heeeeyyyyy')
+
+        # VALID: now, same as above, but now the option is actually enabled via
+        # the initial selection. No error.
+        group3 = parser.add_mutually_exclusive_group(required=True, gooey_options={'initial_selection': 1})
+        group3.add_argument('--gp3-a', type=str)
+        group3.add_argument('--gp3-b', type=str, default='Heeeeyyyyy')
+        # VALID: optional mutex.
+        group4 = parser.add_mutually_exclusive_group()
+        group4.add_argument('--gp4-a', type=str)
+        group4.add_argument('--gp4-b', type=str)
+        # VALID: arg present and type satisfied
+        parser.add_argument('ga', type=str, gooey_options={'initial_value': 'whatever'})
+        # VALID: arg present and custom type satisfied
+        parser.add_argument('gb', type=custom_type, gooey_options={'initial_value': '1234'})
+        # VALID: optional
+        parser.add_argument('--gc')
+
+        # now we're adding the same
+        with instrumentGooey(parser, target='test') as (app, frame, gapp):
+            # we start off with no errors
+            self.assertFalse(s.has_errors(gapp.fullState()))
+
+            # now we feed our form-validation
+            cmd = s.buildFormValidationCmd(gapp.fullState())
+            asdf = shlex.split(cmd)[1:]
+            parser.parse_args(shlex.split(cmd)[1:])
+            assert writer.called
+            assert exit.called
+
+
+        result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
+        # Host->Gooey communication is all done over the PublicGooeyState schema
+        # as such, we coarsely validate it's shape here
+        validate_public_state(result)
+
+        # manually merging the two states back together
+        nextState = s.mergeExternalState(gapp.fullState(), result)
+        # and now we find that we have errors!
+        self.assertTrue(s.has_errors(nextState))
+        items = s.activeFormState(nextState)
+        self.assertIn('invalid literal', get_by_id(items, 'a')['error'])
+        self.assertIn('KABOOM!', get_by_id(items, 'b')['error'])
+        self.assertIn('required', get_by_id(items, 'c')['error'])
+        self.assertIn('required', get_by_id(items, 'oc')['error'])
+        for item in get_by_id(items, 'group_gp1_a_gp1_b')['options']:
+            self.assertIsNotNone(item['error'])
+        for item in get_by_id(items, 'group_gp2_a_gp2_b')['options']:
+            self.assertIsNotNone(item['error'])
+
+        for item in get_by_id(items, 'group_gp3_a_gp3_b')['options']:
+            self.assertIsNone(item['error'])
+        # should be None, since this one was entirely optional
+        for item in get_by_id(items, 'group_gp4_a_gp4_b')['options']:
+            self.assertIsNone(item['error'])
+        self.assertIsNone(get_by_id(items, 'bo')['error'])
+        self.assertIsNone(get_by_id(items, 'ga')['error'])
+        self.assertIsNone(get_by_id(items, 'gb')['error'])
+        self.assertIsNone(get_by_id(items, 'gc')['error'])
+
+
+    def test_subparsers(self):
+        """
+        Making sure that subparsers are handled correctly and
+        all validations still work as expected.
+        """
+        writer = MagicMock()
+        exit = MagicMock()
+        monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)
+        ArgumentParser.original_parse_args = ArgumentParser.parse_args
+        ArgumentParser.parse_args = monkey_patch
+
+        def build_parser():
+            # we build a new parser for each subtest
+            # since we monkey patch the hell out of it
+            # each time
+            parser = GooeyParser()
+            subs = parser.add_subparsers()
+            foo = subs.add_parser('foo')
+            foo.add_argument('a')
+            foo.add_argument('b')
+            foo.add_argument('p')
+
+            bar = subs.add_parser('bar')
+            bar.add_argument('a')
+            bar.add_argument('b')
+            bar.add_argument('z')
+            return parser
+
+        parser = build_parser()
+        with instrumentGooey(parser, target='test') as (app, frame, gapp):
+            with self.subTest('first subparser'):
+                # we start off with no errors
+                self.assertFalse(s.has_errors(gapp.fullState()))
+
+                cmd = s.buildFormValidationCmd(gapp.fullState())
+                parser.parse_args(shlex.split(cmd)[1:])
+                assert writer.called
+                assert exit.called
+
+                result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
+                nextState = s.mergeExternalState(gapp.fullState(), result)
+                # by default, the subparser defined first, 'foo', is selected.
+                self.assertIn('foo', nextState['forms'])
+                # and we should find its attributes
+                expected = {'a', 'b', 'p'}
+                actual = {x['id'] for x in nextState['forms']['foo']}
+                self.assertEqual(expected, actual)
+
+
+        parser = build_parser()
+        with instrumentGooey(parser, target='test') as (app, frame, gapp):
+            with self.subTest('Second subparser'):
+                # mocking a 'selection change' event to select
+                # the second subparser
+                event = MagicMock()
+                event.Selection = 1
+                gapp.handleSelectAction(event)
+
+                # Flushing our events by running the main loop
+                wx.CallLater(1, app.ExitMainLoop)
+                app.MainLoop()
+
+                cmd = s.buildFormValidationCmd(gapp.fullState())
+                parser.parse_args(shlex.split(cmd)[1:])
+                assert writer.called
+                assert exit.called
+
+                result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
+                nextState = s.mergeExternalState(gapp.fullState(), result)
+                # Now our second subparer, 'bar', should be present.
+                self.assertIn('bar', nextState['forms'])
+                # and we should find its attributes
+                expected = {'a', 'b', 'z'}
+                actual = {x['id'] for x in nextState['forms']['bar']}
+                self.assertEqual(expected, actual)
+
+
+    def test_ignore_gooey(self):
+        parser = GooeyParser()
+        subs = parser.add_subparsers()
+        foo = subs.add_parser('foo')
+        foo.add_argument('a')
+        foo.add_argument('b')
+        foo.add_argument('p')
+
+        bar = subs.add_parser('bar')
+        bar.add_argument('a')
+        bar.add_argument('b')
+        bar.add_argument('z')
+
+        control.bypass_gooey(gooey_params())(parser)
+
+def get_by_id(items: List[FormField], id: str):
+    return [x for x in items if x['id'] == id][0]
+
+
+
diff --git a/gooey/tests/test_counter.py b/gooey/tests/test_counter.py
new file mode 100644
index 00000000..10ef7236
--- /dev/null
+++ b/gooey/tests/test_counter.py
@@ -0,0 +1,51 @@
+import unittest
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+
+
+class TestCounter(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument(
+            '--widget',
+            action='count',
+            widget="Counter",
+            **kwargs)
+        return parser
+
+
+    def testInitialValue(self):
+        cases = [
+            # `initial` should supersede `default`
+            {'inputs': {'default': 1,
+                        'gooey_options': {'initial_value': 3}},
+             'expect': '3'},
+
+            {'inputs': {'gooey_options': {'initial_value': 1}},
+             'expect': '1'},
+
+            {'inputs': {'default': 2,
+                        'gooey_options': {}},
+             'expect': '2'},
+
+            {'inputs': {'default': 1},
+             'expect': '1'},
+
+            {'inputs': {},
+             'expect': None}
+        ]
+        for case in cases:
+            with self.subTest(case):
+                parser = self.makeParser(**case['inputs'])
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    widget = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_decoration.py b/gooey/tests/test_decoration.py
new file mode 100644
index 00000000..de2cb03c
--- /dev/null
+++ b/gooey/tests/test_decoration.py
@@ -0,0 +1,55 @@
+import unittest
+from argparse import ArgumentParser
+from functools import wraps
+
+from python_bindings.types import TimingOptions
+
+
+# TODO:
+
+# def decor(f=None, *gargs, **gkwargs):
+#     @wraps(f)
+#     def inner(*args, **kwargs):
+#         print('hello from decorator', gargs, gkwargs)
+#         # choose handler
+#         # monkey patch parser
+#
+#         return f(*args, **kwargs)
+#
+#     def inner2(func):
+#         return decor(func, *gargs, **gkwargs)
+#
+#     return inner if callable(f) else inner2
+#
+#
+# def handle_success(params):
+#     def parse_args(self: ArgumentParser, args=None, namespace=None):
+#         return self._original_parse_args()
+#     return parse_args
+#
+#
+# # @decor
+# def main(*args, **kwargs):
+#     """Hellow world!!!!!"""
+#     print('sup from main', args, kwargs)
+#
+#     # ArgumentParser._original_parse_args = ArgumentParser.parse_args
+#     # ArgumentParser.parse_args = handle_success(ArgumentParser.parse_args)
+#
+#     parser = ArgumentParser()
+#     parser.add_argument('-f', '--foo', help='is foo')
+#     subs = parser.add_subparsers()
+#     sp = subs.add_parser('hh')
+#     sp.add_argument('-f', '--foo', help='sp.foo')
+#     print(parser.parse_args(['hh', '-f', 'asdf']))
+#
+#     print(TimingOptions(show_time_remaining=True, hide_time_remaining_on_complete=True).hide_time_remaining_on_complete)
+#
+#
+#
+#
+# class Testie(unittest.TestCase):
+#
+#     def test_thing(self, **kwargs):
+#         print(main(1, 2))
+#         print(help(main))
\ No newline at end of file
diff --git a/gooey/tests/test_dropdown.py b/gooey/tests/test_dropdown.py
index 4c2f11f9..8c3c84d0 100644
--- a/gooey/tests/test_dropdown.py
+++ b/gooey/tests/test_dropdown.py
@@ -2,58 +2,90 @@
 from argparse import ArgumentParser
 from unittest.mock import patch
 
+from gooey import GooeyParser
 from tests.harness import instrumentGooey
 from gooey.tests import *
 
 class TestGooeyDropdown(unittest.TestCase):
 
-    def make_parser(self, **kwargs):
-        parser = ArgumentParser(description='description')
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
         parser.add_argument('--dropdown', **kwargs)
         return parser
 
 
-    @patch("gui.containers.application.seeder.fetchDynamicProperties")
-    def test_dropdown_behavior(self, mock):
-        """
-        Testing that:
-            - default values are used as the initial selection (when present)
-            - Initial selection defaults to placeholder when no defaults supplied
-            - selection is preserved (when possible) across dynamic updates
-        """
-        testcases = [
-            # tuples of [choices, default, initalSelection, dynamicUpdate, expectedFinalSelection]
-            [['1', '2'], None, 'Select Option', ['1', '2','3'], 'Select Option'],
-            [['1', '2'], '2', '2', ['1', '2','3'],  '2'],
-            [['1', '2'], '1', '1', ['1', '2','3'], '1'],
-            # dynamic updates removed our selected value; defaults back to placeholder
-            [['1', '2'], '2', '2', ['1', '3'], 'Select Option'],
-            # TODO: this test case is currently passing wrong data for the dynamic
-            # TODO: update due to a bug where Gooey doesn't apply the same ingestion
-            # TODO: rules for data received dynamically as it does for parsers.
-            # TODO: In short, Gooey should be able to handle a list of bools [True, False]
-            # TODO: from dynamics just like it does in parser land. It doesn't currently
-            # TODO: do this, so I'm manually casting it to strings for now.
-            [[True, False], True, 'True', ['True', 'False'], 'True']
+    # @patch("gui.containers.application.seeder.fetchDynamicProperties")
+    # def test_dropdown_behavior(self, mock):
+    #     """
+    #     Testing that:
+    #         - default values are used as the initial selection (when present)
+    #         - Initial selection defaults to placeholder when no defaults supplied
+    #         - selection is preserved (when possible) across dynamic updates
+    #     """
+    #     testcases = [
+    #         # tuples of [choices, default, initalSelection, dynamicUpdate, expectedFinalSelection]
+    #         [['1', '2'], None, 'Select Option', ['1', '2','3'], 'Select Option'],
+    #         [['1', '2'], '2', '2', ['1', '2','3'],  '2'],
+    #         [['1', '2'], '1', '1', ['1', '2','3'], '1'],
+    #         # dynamic updates removed our selected value; defaults back to placeholder
+    #         [['1', '2'], '2', '2', ['1', '3'], 'Select Option'],
+    #         # TODO: this test case is currently passing wrong data for the dynamic
+    #         # TODO: update due to a bug where Gooey doesn't apply the same ingestion
+    #         # TODO: rules for data received dynamically as it does for parsers.
+    #         # TODO: In short, Gooey should be able to handle a list of bools [True, False]
+    #         # TODO: from dynamics just like it does in parser land. It doesn't currently
+    #         # TODO: do this, so I'm manually casting it to strings for now.
+    #         [[True, False], True, 'True', ['True', 'False'], 'True']
+    #     ]
+    #
+    #     for choices, default, initalSelection, dynamicUpdate, expectedFinalSelection in testcases:
+    #         parser = self.makeParser(choices=choices, default=default)
+    #         with instrumentGooey(parser) as (app, frame):
+    #             dropdown = frame.configs[0].reifiedWidgets[0]
+    #             # ensure that default values (when supplied) are selected in the UI
+    #             self.assertEqual(dropdown.widget.GetValue(), initalSelection)
+    #             # fire a dynamic update with the mock values
+    #             mock.return_value = {'--dropdown': dynamicUpdate}
+    #             frame.fetchExternalUpdates()
+    #             # the values in the UI now reflect those returned from the update
+    #             # note: we're appending the ['select option'] bit here as it gets automatically added
+    #             # in the UI.
+    #             expectedValues = ['Select Option'] + dynamicUpdate
+    #             self.assertEqual(dropdown.widget.GetItems(), expectedValues)
+    #             # and our selection is what we expect
+    #             self.assertEqual(dropdown.widget.GetValue(), expectedFinalSelection)
 
-        ]
 
-        for choices, default, initalSelection, dynamicUpdate, expectedFinalSelection in testcases:
-            parser = self.make_parser(choices=choices, default=default)
-            with instrumentGooey(parser) as (app, gooeyApp):
-                dropdown = gooeyApp.configs[0].reifiedWidgets[0]
-                # ensure that default values (when supplied) are selected in the UI
-                self.assertEqual(dropdown.widget.GetValue(), initalSelection)
-                # fire a dynamic update with the mock values
-                mock.return_value = {'--dropdown': dynamicUpdate}
-                gooeyApp.fetchExternalUpdates()
-                # the values in the UI now reflect those returned from the update
-                # note: we're appending the ['select option'] bit here as it gets automatically added
-                # in the UI.
-                expectedValues = ['Select Option'] + dynamicUpdate
-                self.assertEqual(dropdown.widget.GetItems(), expectedValues)
-                # and our selection is what we expect
-                self.assertEqual(dropdown.widget.GetValue(), expectedFinalSelection)
+    def testInitialValue(self):
+        cases = [
+            # `initial` should supersede `default`
+            {'inputs': {'default': 'b',
+                        'choices': ['a', 'b', 'c'],
+                        'gooey_options': {'initial_value': 'a'}},
+             'expect': 'a'},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'gooey_options': {'initial_value': 'a'}},
+             'expect': 'a'},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'default': 'b',
+                        'gooey_options': {}},
+             'expect': 'b'},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'default': 'b'},
+             'expect': 'b'},
+
+            {'inputs': {'choices': ['a', 'b', 'c']},
+             'expect': None}
+        ]
+        for case in cases:
+            with self.subTest(case):
+                parser = self.makeParser(**case['inputs'])
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    widget = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])
 
 
 if __name__ == '__main__':
diff --git a/gooey/tests/test_filterable_dropdown.py b/gooey/tests/test_filterable_dropdown.py
index faa55ba3..1291466d 100644
--- a/gooey/tests/test_filterable_dropdown.py
+++ b/gooey/tests/test_filterable_dropdown.py
@@ -18,8 +18,8 @@ def make_parser(self, **kwargs):
 
     def test_input_spawns_popup(self):
         parser = self.make_parser(choices=['alpha1', 'alpha2', 'beta', 'gamma'])
-        with instrumentGooey(parser) as (app, gooeyApp):
-            dropdown = gooeyApp.configs[0].reifiedWidgets[0]
+        with instrumentGooey(parser) as (app, frame, gapp):
+            dropdown = gapp.getActiveConfig().reifiedWidgets[0]
 
             event = wx.CommandEvent(wx.wxEVT_TEXT, wx.Window.NewControlId())
             event.SetEventObject(dropdown.widget.GetTextCtrl())
@@ -30,37 +30,6 @@ def test_input_spawns_popup(self):
                 dropdown.listbox.IsShown()
             )
 
-    def test_relevant_suggestions_shown(self):
-        choices = ['alpha1', 'alpha2', 'beta', 'gamma']
-        cases = [['a', choices[:2]],
-                 ['A', choices[:2]],
-                 ['AlPh', choices[:2]],
-                 ['Alpha1', choices[:1]],
-                 ['b', choices[2:3]],
-                 ['g', choices[-1:]]]
-
-        parser = self.make_parser(choices=choices)
-        with instrumentGooey(parser) as (app, gooeyApp):
-            for input, expected in cases:
-                with self.subTest(f'given input {input}, expect: {expected}'):
-                    dropdown = gooeyApp.configs[0].reifiedWidgets[0]
-
-                    event = wx.CommandEvent(wx.wxEVT_TEXT, wx.Window.NewControlId())
-                    event.SetString(input)
-                    dropdown.widget.GetTextCtrl().ProcessEvent(event)
-                    # model and UI agree
-                    self.assertTrue(
-                        dropdown.model.suggestionsVisible,
-                        dropdown.listbox.IsShown()
-                    )
-                    # model and UI agree
-                    self.assertEqual(
-                        dropdown.model.suggestions,
-                        dropdown.listbox.GetItems(),
-                    )
-                    self.assertEqual(dropdown.model.suggestions,expected)
-
-
     def test_arrow_key_selection_cycling(self):
         """
         Testing that the up/down arrow keys spawn the dropdown
@@ -91,8 +60,8 @@ def test_arrow_key_selection_cycling(self):
 
         for actions in scenarios:
             parser = self.make_parser(choices=choices)
-            with instrumentGooey(parser) as (app, gooeyApp):
-                dropdown = gooeyApp.configs[0].reifiedWidgets[0]
+            with instrumentGooey(parser) as (app, frame, gapp):
+                dropdown = gapp.getActiveConfig().reifiedWidgets[0]
                 # sanity check we're starting from our known initial state
                 self.assertEqual(dropdown.model.suggestionsVisible, initial.expectVisible)
                 self.assertEqual(dropdown.model.displayValue, initial.expectedDisplayValue)
@@ -126,9 +95,9 @@ def pressButton(self, dropdown, keycode):
 
 def mockKeyEvent(keycode):
     """
-    Manually bypassing the setters as they don'y allow
+    Manually bypassing the setters as they don't allow
     the non wx.wxXXX event variants by default.
-    The internal WX post/prcess machinery doesn't handle key
+    The internal WX post/process machinery doesn't handle key
     codes well for some reason, thus has to be mocked and
     manually passed to the relevant handler.
     """
diff --git a/gooey/tests/test_filtering.py b/gooey/tests/test_filtering.py
new file mode 100644
index 00000000..892fb221
--- /dev/null
+++ b/gooey/tests/test_filtering.py
@@ -0,0 +1,92 @@
+import unittest
+
+from gooey import PrefixTokenizers
+from gui.components.filtering.prefix_filter import SearchOptions, PrefixSearch
+from collections import namedtuple
+
+TestData = namedtuple('TestData', [
+    'options',
+    'input_string',
+    'expected_results',
+])
+
+Places = namedtuple('Places', [
+    'kabul',
+    'tirana',
+    'kyoto',
+    'tokyo'
+])
+
+class TestPrefixFilter(unittest.TestCase):
+
+
+    def setUp(self):
+        self.testdata = Places(
+            'Afghanistan Kabul',
+            'Albania Tirana',
+            'Japan Kyoto',
+            'Japan Tokyo'
+        )
+
+    def test_prefix_searching(self):
+        p = self.testdata
+        cases = [
+            TestData({'ignore_case': True}, 'a', [p.kabul, p.tirana]),
+            TestData({'ignore_case': True}, 'A', [p.kabul, p.tirana]),
+            TestData({'ignore_case': False}, 'a', []),
+            TestData({'ignore_case': False}, 'A', [p.kabul, p.tirana]),
+
+            # when using the phrase tokenizer, the search input must
+            # match starting from the beginning. So we find Afghanistan
+            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Afghan', [p.kabul]),
+            # but we cannot look up Kyoto because the phrase begins with "Japan"
+            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Kyoto', []),
+            # So if we start with "Japan K" it'll be returned
+            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Japan K', [p.kyoto]),
+
+
+
+            # word tokenizer will split on all whitespace and index
+            # each choice one for each UNIQUE word
+            # so passing in 'a' will match "Af" and "Al" as usual
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS}, 'a', [p.kabul, p.tirana]),
+            # but now we can also find Kyoto without prefixing "japan" as we'd
+            # need to do with the phrase tokenizer
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS}, 'kyo', [p.kyoto]),
+
+            # if we tokenize the input, we're perform two searches against the index
+            # The default operator is AND, which means all the words in your search
+            # input must match the choice for it to count as as a hit.
+            # In this example, we index the choices under PHRASE, but set the input
+            # tokenizer to WORDS. Our input 'Japan K' gets tokenized to ['Japan', 'K']
+            # There is no phrase which starts with Both "Japan" and "K" so we get no
+            # matches returned
+            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE,
+                      'input_tokenizer': PrefixTokenizers.WORDS}, 'Japan K', []),
+            # Tokenize the choices by WORDS means we can now filter on both words
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,
+                      'input_tokenizer': PrefixTokenizers.WORDS}, 'Jap K', [p.kyoto]),
+            # the default AND behavior can be swapped to OR to facilitate matching across
+            # different records in the index.
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,
+                      'input_tokenizer': PrefixTokenizers.WORDS,
+                      'operator': 'OR'}, 'Kyo Tok', [p.kyoto, p.tokyo]),
+
+            # Turning on Suffix indexing allow matching anywhere within a word.
+            # Now 'kyo' will match both the beginning 'Kyoto' and substring 'ToKYO'
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,
+                      'input_tokenizer': PrefixTokenizers.WORDS,
+                      'index_suffix': True}, 'kyo ', [p.kyoto, p.tokyo]),
+
+            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,
+                      'input_tokenizer': PrefixTokenizers.WORDS,
+                      'index_suffix': True}, 'j kyo ', [p.kyoto, p.tokyo]),
+        ]
+
+        for case in cases:
+            with self.subTest(case):
+                searcher = PrefixSearch(self.testdata, case.options)
+                result = searcher.findMatches(case.input_string)
+                self.assertEqual(result, case.expected_results)
+
+
diff --git a/gooey/tests/test_formatters.py b/gooey/tests/test_formatters.py
index a3e175f2..a2aef9c9 100644
--- a/gooey/tests/test_formatters.py
+++ b/gooey/tests/test_formatters.py
@@ -47,7 +47,7 @@ def test_counter_formatter(self):
     def test_multifilechooser_formatter(self):
         """
         Should return files (quoted), separated by spaces if there is more
-        than one, preceeded by optional command if the argument is optional.
+        than one, preceded by optional command if the argument is optional.
 
         Assumes the argument has been created with some form of nargs, which
         only makes sense for possibly choosing multiple values.
diff --git a/gooey/tests/test_header.py b/gooey/tests/test_header.py
index e63b46dc..ae0be0d7 100644
--- a/gooey/tests/test_header.py
+++ b/gooey/tests/test_header.py
@@ -20,16 +20,16 @@ def test_header_visibility(self):
         """
         for testdata in self.testcases():
             with self.subTest(testdata):
-                with instrumentGooey(self.make_parser(), **testdata) as (app, gooeyApp):
-                    header = gooeyApp.header
+                with instrumentGooey(self.make_parser(), **testdata) as (app, frame, gapp):
+                    frame: wx.Frame = frame
 
                     self.assertEqual(
-                        header._header.IsShown(),
+                        frame.FindWindowByName("header_title").IsShown(),
                         testdata.get('header_show_title', True)
                     )
 
                     self.assertEqual(
-                        header._subheader.IsShown(),
+                        frame.FindWindowByName("header_subtitle").IsShown(),
                         testdata.get('header_show_subtitle', True)
                     )
 
@@ -40,9 +40,9 @@ def test_header_string(self):
         placed into the UI.
         """
         parser = ArgumentParser(description='Foobar')
-        with instrumentGooey(parser, program_name='BaZzEr') as (app, gooeyApp):
-            self.assertEqual(gooeyApp.header._header.GetLabelText(), 'BaZzEr')
-            self.assertEqual(gooeyApp.header._subheader.GetLabelText(), 'Foobar')
+        with instrumentGooey(parser, program_name='BaZzEr') as (app, frame, gapp):
+            self.assertEqual(frame.FindWindowByName("header_title").GetLabel(), 'BaZzEr')
+            self.assertEqual(frame.FindWindowByName("header_subtitle").GetLabel(), 'Foobar')
 
 
     def testcases(self):
diff --git a/gooey/tests/test_listbox.py b/gooey/tests/test_listbox.py
new file mode 100644
index 00000000..79b64085
--- /dev/null
+++ b/gooey/tests/test_listbox.py
@@ -0,0 +1,58 @@
+import unittest
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+
+
+class TestListbox(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument(
+            '--widget',
+            widget="Listbox",
+            nargs="*",
+            **kwargs)
+        return parser
+
+    def testInitialValue(self):
+        cases = [
+            # `initial` should supersede `default`
+            {'inputs': {'default': 'b',
+                        'choices': ['a', 'b', 'c'],
+                        'gooey_options': {'initial_value': 'a'}},
+             'expect': ['a']},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'gooey_options': {'initial_value': 'a'}},
+             'expect': ['a']},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'gooey_options': {'initial_value': ['a', 'c']}},
+             'expect': ['a', 'c']},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'default': 'b',
+                        'gooey_options': {}},
+             'expect': ['b']},
+
+            {'inputs': {'choices': ['a', 'b', 'c'],
+                        'default': 'b'},
+             'expect': ['b']},
+
+            {'inputs': {'choices': ['a', 'b', 'c']},
+             'expect': []}
+        ]
+        for case in cases:
+            with self.subTest(case):
+                parser = self.makeParser(**case['inputs'])
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    widget = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_numeric_inputs.py b/gooey/tests/test_numeric_inputs.py
new file mode 100644
index 00000000..e24edc44
--- /dev/null
+++ b/gooey/tests/test_numeric_inputs.py
@@ -0,0 +1,111 @@
+import unittest
+from random import randint
+from unittest.mock import patch
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+class TestNumbericInputs(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--input', **kwargs)
+        return parser
+
+
+    def testDefault(self):
+        cases = [
+            [{'widget': 'IntegerField'}, 0],
+            [{'default': 0, 'widget': 'IntegerField'}, 0],
+            [{'default': 10, 'widget': 'IntegerField'}, 10],
+            [{'default': 76, 'widget': 'IntegerField'}, 76],
+            # note that WX caps the value
+            # unless explicitly widened via gooey_options
+            [{'default': 81234, 'widget': 'IntegerField'}, 100],
+            # here we set the max to something higher than
+            # the default and all works as expected.
+            # this is a TODO for validation
+            [{'default': 81234, 'widget': 'IntegerField', 'gooey_options': {'max': 99999}}, 81234],
+            # Initial Value cases
+            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 0}}, 0],
+            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 10}}, 10],
+            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 76}}, 76],
+            # note that WX caps the value
+            # unless explicitly widened via gooey_options
+            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234}}, 100],
+            # here we set the max to something higher than
+            # the default and all works as expected.
+            # this is a TODO for validation
+            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
+
+            [{'widget': 'DecimalField'}, 0],
+            [{'default': 0, 'widget': 'DecimalField'}, 0],
+            [{'default': 81234, 'widget': 'DecimalField'}, 100],
+            [{'default': 81234, 'widget': 'DecimalField', 'gooey_options': {'max': 99999}}, 81234],
+            # Initial Value cases
+            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 0}}, 0],
+            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 10}}, 10],
+            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 76}}, 76],
+            # note that WX caps the value
+            # unless explicitly widened via gooey_options
+            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234}}, 100],
+            # here we set the max to something higher than
+            # the default and all works as expected.
+            # this is a TODO for validation
+            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
+        ]
+        for inputs, expected in cases:
+            with self.subTest(inputs):
+                parser = self.makeParser(**inputs)
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    input = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(input.getValue()['rawValue'], expected)
+
+    def testGooeyOptions(self):
+        cases = [
+            {'widget': 'DecimalField', 'gooey_options': {'min': -100, 'max': 1234, 'increment': 1.240}},
+            {'widget': 'DecimalField', 'gooey_options': {'min': 1234, 'max': 3456, 'increment': 2.2}},
+            {'widget': 'IntegerField', 'gooey_options': {'min': -100, 'max': 1234}},
+            {'widget': 'IntegerField', 'gooey_options': {'min': 1234, 'max': 3456}}
+        ];
+        using = {
+            'min': lambda widget: widget.GetMin(),
+            'max': lambda widget: widget.GetMax(),
+            'increment': lambda widget: widget.GetIncrement(),
+
+        }
+        for case in cases:
+            with self.subTest(case):
+                parser = self.makeParser(**case)
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    wxWidget = gapp.getActiveConfig().reifiedWidgets[0].widget
+                    for option, value in case['gooey_options'].items():
+                        self.assertEqual(using[option](wxWidget), value)
+
+
+    def testZerosAreReturned(self):
+        """
+        Originally the formatter was dropping '0' due to
+        it being interpreted as falsey
+        """
+        parser = self.makeParser(widget='IntegerField')
+        with instrumentGooey(parser) as (app, frame, gapp):
+            field = gapp.getActiveConfig().reifiedWidgets[0]
+            result = field.getValue()
+            self.assertEqual(result['rawValue'], 0)
+            self.assertIsNotNone(result['cmd'])
+
+    def testNoLossOfPrecision(self):
+        parser = self.makeParser(widget='DecimalField', default=12.23534, gooey_options={'precision': 20})
+        with instrumentGooey(parser) as (app, frame, gapp):
+            field = gapp.getActiveConfig().reifiedWidgets[0]
+            result = field.getValue()
+            self.assertEqual(result['rawValue'], 12.23534)
+            self.assertIsNotNone(result['cmd'])
+
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_options.py b/gooey/tests/test_options.py
new file mode 100644
index 00000000..5438093f
--- /dev/null
+++ b/gooey/tests/test_options.py
@@ -0,0 +1,54 @@
+import unittest
+
+from gooey.gui.components.options import options
+
+class TestPrefixFilter(unittest.TestCase):
+
+    def test_doc_schenanigans(self):
+        """Sanity check that my docstring wrappers all behave as expected"""
+        @options._include_layout_docs
+        def no_self_docstring():
+            pass
+
+        @options._include_layout_docs
+        def yes_self_docstring():
+            """sup"""
+            pass
+
+        # gets attached to functions even if they don't have a docstring
+        self.assertIn(options.LayoutOptions.__doc__, no_self_docstring.__doc__)
+        # gets attached to the *end* of existing doc strings
+        self.assertTrue(yes_self_docstring.__doc__.startswith('sup'))
+        self.assertIn(options.LayoutOptions.__doc__, yes_self_docstring.__doc__)
+
+
+    def test_clean_method(self):
+        """
+        _clean should drop any keys with None values
+        and flatten the layout_option kwargs to the root level
+        """
+        result = options._clean({'a': None, 'b': 123, 'c': 0})
+        self.assertEqual(result, {'b': 123, 'c': 0})
+
+        result = options._clean({'root_level': 123, 'layout_options': {
+            'nested': 'hello',
+            'another': 1234
+        }})
+        self.assertEqual(result, {'root_level': 123, 'nested': 'hello', 'another': 1234})
+
+    def test_only_provided_arguments_included(self):
+        """
+        More sanity checking that the internal use of locals()
+        does the Right Thing
+        """
+        option = options.LayoutOptions(label_color='#ffffff')
+        self.assertIn('label_color', option)
+
+        option = options.LayoutOptions()
+        self.assertNotIn('label_color', option)
+
+        option = options.TextField(label_color='#ffffff')
+        self.assertIn('label_color', option)
+
+        option = options.TextField()
+        self.assertNotIn('label_color', option)
\ No newline at end of file
diff --git a/gooey/tests/test_password.py b/gooey/tests/test_password.py
new file mode 100644
index 00000000..4487141a
--- /dev/null
+++ b/gooey/tests/test_password.py
@@ -0,0 +1,32 @@
+import unittest
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+class TestPasswordField(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--widget', widget="PasswordField", **kwargs)
+        return parser
+
+
+    def testPlaceholder(self):
+        cases = [
+            [{}, ''],
+            [{'placeholder': 'Hello'}, 'Hello']
+        ]
+        for options, expected in cases:
+            parser = self.makeParser(gooey_options=options)
+            with instrumentGooey(parser) as (app, frame, gapp):
+                # because of how poorly designed the Gooey widgets are
+                # we have to reach down 3 levels in order to find the
+                # actual WX object we need to test.
+                widget = gapp.getActiveConfig().reifiedWidgets[0].widget
+                self.assertEqual(widget.widget.GetHint(), expected)
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_processor.py b/gooey/tests/test_processor.py
deleted file mode 100644
index 0fd86668..00000000
--- a/gooey/tests/test_processor.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import re
-import unittest
-
-from gooey.gui.processor import ProcessController
-
-
-class TestProcessor(unittest.TestCase):
-
-    def test_extract_progress(self):
-        # should pull out a number based on the supplied
-        # regex and expression
-        processor = ProcessController(r"^progress: (\d+)%$", None, False, 'utf-8')
-        self.assertEqual(processor._extract_progress(b'progress: 50%'), 50)
-
-        processor = ProcessController(r"total: (\d+)%$", None, False, 'utf-8')
-        self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)
-
-    def test_extract_progress_returns_none_if_no_regex_supplied(self):
-        processor = ProcessController(None, None, False, 'utf-8')
-        self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))
-
-
-    def test_extract_progress_returns_none_if_no_match_found(self):
-        processor = ProcessController(r'(\d+)%$', None, False, 'utf-8')
-        self.assertIsNone(processor._extract_progress(b'No match in dis string'))
-
-
-    def test_eval_progress(self):
-        # given a match in the string, should eval the result
-        regex = r'(\d+)/(\d+)$'
-        processor = ProcessController(regex, r'x[0] / x[1]', False,False, 'utf-8')
-        match = re.search(regex, '50/50')
-        self.assertEqual(processor._eval_progress(match), 1.0)
-    def test_eval_progress_returns_none_on_failure(self):
-        # given a match in the string, should eval the result
-        regex = r'(\d+)/(\d+)$'
-        processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,'utf-8')
-        match = re.search(regex, '50/50')
-        self.assertIsNone(processor._eval_progress(match))
diff --git a/gooey/tests/test_radiogroup.py b/gooey/tests/test_radiogroup.py
index 847271f5..37aa7a3a 100644
--- a/gooey/tests/test_radiogroup.py
+++ b/gooey/tests/test_radiogroup.py
@@ -50,8 +50,8 @@ def test_initial_selection_options(self):
         for options, expected in testCases:
             parser = self.mutext_group(options)
             with self.subTest(options):
-                with instrumentGooey(parser) as (app, gooeyApp):
-                    radioGroup = gooeyApp.configs[0].reifiedWidgets[0]
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    radioGroup = gapp.getActiveConfig().reifiedWidgets[0]
 
                     # verify that the checkboxes themselves are correct
                     if expected['selected'] is not None:
@@ -88,8 +88,8 @@ def test_optional_radiogroup_click_behavior(self):
                 # wire up the parse with our test case options
                 parser = self.mutext_group(testcase['input'])
 
-                with instrumentGooey(parser) as (app, gooeyApp):
-                    radioGroup = gooeyApp.configs[0].reifiedWidgets[0]
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    radioGroup = gapp.getActiveConfig().reifiedWidgets[0]
 
                     for scenario in testcase['scenario']:
                         targetButton = scenario['clickButton']
diff --git a/gooey/tests/test_slider.py b/gooey/tests/test_slider.py
new file mode 100644
index 00000000..94557c90
--- /dev/null
+++ b/gooey/tests/test_slider.py
@@ -0,0 +1,64 @@
+import unittest
+from unittest.mock import patch
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+class TestGooeySlider(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--slider', widget="Slider", **kwargs)
+        return parser
+
+
+    def testSliderDefault(self):
+        cases = [
+            [{}, 0],
+            [{'default': 0}, 0],
+            [{'default': 10}, 10],
+            [{'default': 76}, 76],
+            # note that WX caps the value
+            # unless explicitly widened via gooey_options
+            [{'default': 81234}, 100],
+            # here we set the max to something higher than
+            # the default and all works as expected.
+            # this is a TODO for validation
+            [{'default': 81234, 'gooey_options': {'max': 99999}}, 81234],
+
+            # Initial Value cases
+            [{}, 0],
+            [{'gooey_options': {'initial_value': 0}}, 0],
+            [{'gooey_options': {'initial_value': 10}}, 10],
+            [{'gooey_options': {'initial_value': 76}}, 76],
+            # note that WX caps the value
+            # unless explicitly widened via gooey_options
+            [{'gooey_options': {'initial_value': 81234}}, 100],
+            # here we set the max to something higher than
+            # the default and all works as expected.
+            # this is a TODO for validation
+            [{'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
+        ]
+        for inputs, expected in cases:
+            with self.subTest(inputs):
+                parser = self.makeParser(**inputs)
+                with instrumentGooey(parser) as (app, frame, gapp):
+                    slider = gapp.getActiveConfig().reifiedWidgets[0]
+                    self.assertEqual(slider.getValue()['rawValue'], expected)
+
+    def testZerosAreReturned(self):
+        """
+        Originally the formatter was dropping '0' due to
+        it being interpreted as falsey
+        """
+        parser = self.makeParser()
+        with instrumentGooey(parser) as (app, frame, gapp):
+            field = gapp.getActiveConfig().reifiedWidgets[0]
+            result = field.getValue()
+            self.assertEqual(result['rawValue'], 0)
+            self.assertIsNotNone(result['cmd'])
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_textarea.py b/gooey/tests/test_textarea.py
new file mode 100644
index 00000000..8d56e6a6
--- /dev/null
+++ b/gooey/tests/test_textarea.py
@@ -0,0 +1,32 @@
+import unittest
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+class TestTextarea(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--widget', widget="Textarea", **kwargs)
+        return parser
+
+
+    def testPlaceholder(self):
+        cases = [
+            [{}, ''],
+            [{'placeholder': 'Hello'}, 'Hello']
+        ]
+        for options, expected in cases:
+            parser = self.makeParser(gooey_options=options)
+            with instrumentGooey(parser) as (app, frame, gapp):
+                # because of how poorly designed the Gooey widgets are
+                # we have to reach down 3 levels in order to find the
+                # actual WX object we need to test.
+                widget = gapp.getActiveConfig().reifiedWidgets[0]
+                self.assertEqual(widget.widget.GetHint(), expected)
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_textfield.py b/gooey/tests/test_textfield.py
new file mode 100644
index 00000000..1dc35759
--- /dev/null
+++ b/gooey/tests/test_textfield.py
@@ -0,0 +1,61 @@
+import unittest
+from collections import namedtuple
+
+from tests.harness import instrumentGooey
+from gooey import GooeyParser
+from gooey.tests import *
+
+Case = namedtuple('Case', 'inputs initialExpected expectedAfterClearing')
+
+class TestTextField(unittest.TestCase):
+
+    def makeParser(self, **kwargs):
+        parser = GooeyParser(description='description')
+        parser.add_argument('--widget', widget="TextField", **kwargs)
+        return parser
+
+
+    def testPlaceholder(self):
+        cases = [
+            [{}, ''],
+            [{'placeholder': 'Hello'}, 'Hello']
+        ]
+        for options, expected in cases:
+            parser = self.makeParser(gooey_options=options)
+            with instrumentGooey(parser) as (app, frame, gapp):
+                # because of how poorly designed the Gooey widgets are
+                # we have to reach down 3 levels in order to find the
+                # actual WX object we need to test.
+                widget = gapp.getActiveConfig().reifiedWidgets[0].widget
+                self.assertEqual(widget.widget.GetHint(), expected)
+
+
+
+
+    def testDefaultAndInitialValue(self):
+        cases = [
+            # initial_value takes precedence when both are present
+            Case(
+                {'default': 'default_val', 'gooey_options': {'initial_value': 'some val'}},
+                'some val',
+                None),
+            # when no default is present
+            # Case({'gooey_options': {'initial_value': 'some val'}},
+            #  'some val',
+            #  ''),
+            # [{'default': 'default', 'gooey_options': {}},
+            #  'default'],
+            # [{'default': 'default'},
+            #  'default'],
+        ]
+        for case in cases:
+            parser = self.makeParser(**case.inputs)
+            with instrumentGooey(parser) as (app, frame, gapp):
+                widget = gapp.getActiveConfig().reifiedWidgets[0]
+                self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)
+                widget.setValue('')
+                print(widget.getValue())
+                self.assertEqual(widget.getValue()['cmd'], case.expectedAfterClearing)
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_time_remaining.py b/gooey/tests/test_time_remaining.py
index edf98e74..fe67e29e 100644
--- a/gooey/tests/test_time_remaining.py
+++ b/gooey/tests/test_time_remaining.py
@@ -2,11 +2,13 @@
 import unittest
 from argparse import ArgumentParser
 from itertools import *
-
+from gooey.gui import state as s
 from tests.harness import instrumentGooey
 
 
 from gooey.tests import *
+from gooey.util.functional import identity
+
 
 class TestFooterTimeRemaining(unittest.TestCase):
 
@@ -17,28 +19,32 @@ def make_parser(self):
     def test_time_remaining_visibility(self):
         for testdata in self.testcases():
             with self.subTest(testdata):
-                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
+                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp):
 
-                    gooeyApp.showConsole()
-                    footer = gooeyApp.footer
+                    gapp.set_state(s.consoleScreen(identity, gapp.state))
+                    app: wx.App = app
+                    wx.CallLater(1, app.ExitMainLoop)
+                    app.MainLoop()
 
                     self.assertEqual(
-                        footer.time_remaining_text.Shown,
+                        frame.FindWindowByName('timing').Shown,
                         testdata.get('show_time_remaining',False)
                     )
 
     def test_time_remaining_visibility_on_complete(self):
         for testdata in self.testcases():
             with self.subTest(testdata):
-                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
+                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp):
 
-                    gooeyApp.showComplete()
-                    footer = gooeyApp.footer
+                    gapp.set_state(s.successScreen(identity, gapp.state))
+                    app: wx.App = app
+                    wx.CallLater(1, app.ExitMainLoop)
+                    app.MainLoop()
 
 
                     if not testdata.get('show_time_remaining') and testdata:
                         self.assertEqual(
-                            footer.time_remaining_text.Shown,
+                            frame.FindWindowByName('timing').Shown,
                             testdata.get('hide_time_remaining_on_complete',True)
                         )
                     else:
diff --git a/gooey/tests/tmmmmp.py b/gooey/tests/tmmmmp.py
new file mode 100644
index 00000000..249acfe1
--- /dev/null
+++ b/gooey/tests/tmmmmp.py
@@ -0,0 +1,20 @@
+from gooey import GooeyParser, Gooey
+
+
+
+def main():
+    parser = GooeyParser()
+    subs = parser.add_subparsers()
+    foo = subs.add_parser('foo')
+    foo.add_argument('a')
+    foo.add_argument('b')
+    foo.add_argument('p')
+
+    bar = subs.add_parser('bar')
+    bar.add_argument('a')
+    bar.add_argument('b')
+    bar.add_argument('z')
+    parser.parse_args(['foo'])
+
+
+main()
\ No newline at end of file
diff --git a/gooey/tests/tmp.txt b/gooey/tests/tmp.txt
new file mode 100644
index 00000000..80c673c7
--- /dev/null
+++ b/gooey/tests/tmp.txt
@@ -0,0 +1 @@
+['C:\\Users\\Chris\\Documents\\Gooey\\gooey\\tests\\dynamics\\files\\basic.py', '--ignore-gooey', '--', '10']
\ No newline at end of file
diff --git a/gooey/util/functional.py b/gooey/util/functional.py
index 5120dfdc..c0b53efd 100644
--- a/gooey/util/functional.py
+++ b/gooey/util/functional.py
@@ -1,9 +1,12 @@
 """
 A collection of functional utilities/helpers
 """
-from functools import reduce
+from functools import reduce, wraps
 from copy import deepcopy
 from itertools import chain, dropwhile
+from typing import Tuple, Any, List, Union
+
+from gooey.python_bindings.types import Try, Success, Failure
 
 
 def getin(m, path, default=None):
@@ -23,6 +26,10 @@ def assoc(m, key, val):
     cpy[key] = val
     return cpy
 
+def dissoc(m, key, val):
+    cpy = deepcopy(m)
+    del cpy[key]
+    return cpy
 
 def associn(m, path, value):
     """ Copy-on-write associates a value in a nested dict """
@@ -34,6 +41,17 @@ def assoc_recursively(m, path, value):
     return assoc_recursively(m, path, value)
 
 
+def associnMany(m, *args: Tuple[Union[str, List[str]], Any]):
+    def apply(_m, change: Tuple[Union[str, List[str]], Any]):
+        path, value = change
+        if isinstance(path, list):
+            return associn(_m, path, value)
+        else:
+            return associn(_m, path.split('.'), value)
+    return reduce(apply, args, m)
+
+
+
 def merge(*maps):
     """Merge all maps left to right"""
     copies = map(deepcopy, maps)
@@ -55,12 +73,6 @@ def indexunique(f, coll):
     return zipmap(map(f, coll), coll)
 
 
-def findfirst(f, coll):
-    """Return first occurrence matching f, otherwise None"""
-    result = list(dropwhile(f, coll))
-    return result[0] if result else None
-
-
 def zipmap(keys, vals):
     """Return a map from keys to values"""
     return dict(zip(keys, vals))
@@ -68,7 +80,10 @@ def zipmap(keys, vals):
 
 def compact(coll):
     """Returns a new list with all falsy values removed"""
-    return list(filter(None, coll))
+    if isinstance(coll, dict):
+        return {k:v for k,v in coll.items() if v is not None}
+    else:
+        return list(filter(None, coll))
 
 
 def ifPresent(f):
@@ -92,3 +107,14 @@ def unit(val):
 
 def bind(val, f):
     return f(val) if val else None
+
+
+def lift(f):
+    @wraps(f)
+    def inner(x) -> Try:
+        try:
+            return Success(f(x))
+        except Exception as e:
+            return Failure(e)
+    return inner
+
diff --git a/requirements.txt b/requirements.txt
index 0e71166f..e138af4f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,7 @@ wxpython>=4.1.0
 Pillow>=4.3.0
 psutil>=5.4.2
 colored>=1.3.93
+pygtrie>=2.3.3
+re-wx>=0.0.2
+typing-extensions==3.10.0.2
+mypy-extensions==0.4.3
diff --git a/setup.py b/setup.py
index 9072549b..e2d82e68 100644
--- a/setup.py
+++ b/setup.py
@@ -6,18 +6,19 @@
 with open('README.md') as readme:
     long_description = readme.read()
 
-version = '1.0.5'
+version = '1.2.0-ALPHA'
 
 deps = [
     'Pillow>=4.3.0',
     'psutil>=5.4.2',
-    'colored>=1.3.93'
+    'colored>=1.3.93',
+    'pygtrie>=2.3.3',
+    're-wx>=0.0.9',
+    'typing-extensions==3.10.0.2',
+    'wxpython>=4.1.0',
+    "dataclasses>=0.8;python_version<'3.7'",
 ]
 
-if sys.version[0] == '3':
-    deps.append('wxpython==4.1.0')
-
-
 setup(
     name='Gooey',
     version=version,
@@ -27,6 +28,7 @@
     description=('Turn (almost) any command line program into a full GUI '
                  'application with one line'),
     license='MIT',
+    python_requires='>=3.6',
     packages=find_packages(),
     install_requires=deps,
     include_package_data=True,
@@ -37,7 +39,6 @@
         'Topic :: Desktop Environment',
         'Topic :: Software Development :: Build Tools',
         'Topic :: Software Development :: Widget Sets',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
         'License :: OSI Approved :: MIT License'
     ],