diff --git a/src/js/rulesAssistantOptions.tw b/src/js/rulesAssistantOptions.tw
new file mode 100644
index 0000000000000000000000000000000000000000..a35ea6f15c49f3252710151c408cd7f6205b9165
--- /dev/null
+++ b/src/js/rulesAssistantOptions.tw
@@ -0,0 +1,163 @@
+:: Rules Assistant Options [script]
+
+// rewrite of the rules assistant options page in javascript
+// uses an object-oriented widget pattern
+// wrapped in a closure so as not to polute the global namespace
+// the widgets are generic enough to be reusable; if similar user interfaces are ported to JS, we could move the classes to the global scope
+
+window.rulesAssistantOptions = (function() {
+	"use strict"
+	
+	let V
+	let r = ""
+	let root
+
+	function rulesAssistantOptions() {
+		V = State.variables
+		V.nextButton = "Back to Main"
+		V.nextLink = "Main"
+		V.returnTo = "Main"
+		V.showEncyclopedia = 1
+		V.encyclopedia = "Personal Assistant"
+		
+		root = new Element(document.querySelector("#passage-rules-assistant"))
+		let tmp = document.createElement("p")
+		tmp.innerHTML = `${propertTitle()}, I will review your slaves and make changes that will have a beneficial effect. Apologies, ${properTitle()}, but this function is... not fully complete. It may have some serious limitations. Please use the 'no default setting' option to identify areas I should not address.`
+		root.appendChild(new Element(tmp))
+	}
+
+	// helper function returning PC's title
+	function properTitle() {
+		if (V.PC.customTitle) return V.PC.customTitle
+		else if (V.PC.title !== 0) return "Sir"
+		else return "Ma'am"
+	}
+
+	// the Element class wraps around a DOM element and adds extra functionality
+	// this is safer than extending DOM objects directly
+	// it also turns DOM manipulation into an implementation detail
+	class Element {
+		constructor(...args) {
+			this.parent = null
+			this.element = this.render(...args)
+			this.children = []
+		}
+
+		appendChild(child) {
+			child.parent = this
+			this.children.push(child)
+			this.element.appendChild(child.element)
+		}
+
+		// return the first argument to simplify creation of basic container items
+		render(...args) {
+			return args[0]
+		}
+	}
+
+	// list of clickable elements
+	// has a short explanation (the prefix) and a value display
+	// value display can optionally be an editable text input field
+	// it can be "bound" to a variable by setting its "onchange" method
+	class List extends Element {
+		constructor(prefix, textinput=false) {
+			super(prefix, textarea)
+			this.selectedItem = null
+			this.value = this.element.querySelector(".rajs-value")
+		}
+		
+		render(prefix, textinput) {
+			const elem = document.createElement("div")
+			const prefix = document.createElement("span")
+			prefix.innerHTML = prefix
+			let value
+			if (textinput) {
+				value = document.createElement("input")
+				value.classList.add("rajs-value")
+				// call the variable binding when the input field is no longer being edited, and when the enter key is pressed
+				value.onfocusout = () => { this.propagateChange() }
+				value.onkeypress = (e) => { if (e.keyCode === 13) this.propagateChange() }
+			} else {
+				value = document.createElement("strong")
+			}
+			value.setAttribute("type", "text")
+			elem.appendChild(prefix)
+			elem.appendChild(value)
+			elem.classList.add("rajs-list")
+			return elem
+		}
+		
+		selectItem(item) {
+			if (this.selectedItem) this.selectedItem.deselect()
+			this.selectedItem = item
+			setValue(item.setValue)
+			this.propagateChange()
+		}
+
+		setValue(what) {
+			if (this.value.tagName === "input")
+				this.value.value = what
+			else
+				this.value.innerHTML = what
+		}
+
+		getValue(what) {
+			return (this.value.tagName === "input" ? this.parse(this.value.value) : this.value.innerHTML)
+		}
+
+		// customisable input field parser / sanity checker
+		parse(what) { return what }
+
+		propagateChange() {
+			if (this.onchange instanceof Function)
+				this.onchange(this.getValue())
+		}
+			
+	}
+
+	// a clickable item of a list
+	class ListItem extends Element {
+		constructor(displayvalue, setvalue) {
+			super(displayvalue)
+			this.setvalue = setvalue
+			this.selected = false
+		}
+		
+		render(displayvalue) {
+			const elem = document.createElement("span")
+			elem.classList.add("rajs-listitem")
+			elem.innerHTML = displayvalue
+			elem.onclick = () => { return this.select() }
+			return elem
+		}
+
+		select() {
+			if (this.selected) return false
+			this.parent.selectItem(this)
+			this.elem.classList.add("selected")
+			this.selected = true
+		}
+
+		deselect() {
+			this.elem.classList.remove("selected")
+			this.selected = false
+		}
+	}
+
+	// a way to organise lists with too many elements in subsections
+	// children are bound to the master list
+	class ListSubSection extends Element {
+		render(label) {
+			const elem document.createElement("em")
+			elem.innerText = label + ":"
+			return elem
+		}
+		
+		appendChild(child) {
+			super.appendChild(child)
+			child.parent = this.parent
+		}
+	}
+
+	return rulesAssistantOptions
+})()