diff --git a/package/gluon-config-mode-contact-info/luasrc/lib/gluon/config-mode/wizard/0500-contact-info.lua b/package/gluon-config-mode-contact-info/luasrc/lib/gluon/config-mode/wizard/0500-contact-info.lua
index a47b604371715df06bbdd4beaf90574531fa4d90..357d966e9f750c7cf544e61b57e736273edaaf3c 100644
--- a/package/gluon-config-mode-contact-info/luasrc/lib/gluon/config-mode/wizard/0500-contact-info.lua
+++ b/package/gluon-config-mode-contact-info/luasrc/lib/gluon/config-mode/wizard/0500-contact-info.lua
@@ -21,7 +21,6 @@ return function(form, uci)
 	o.optional = true
 	function o:write(data)
 		uci:set("gluon-node-info", owner, "contact", data)
+		uci:save("gluon-node-info")
 	end
-
-	return {'gluon-node-info'}
 end
diff --git a/package/gluon-config-mode-core/luasrc/lib/gluon/config-mode/model/gluon-config-mode/wizard.lua b/package/gluon-config-mode-core/luasrc/lib/gluon/config-mode/model/gluon-config-mode/wizard.lua
index d32b56ac31f9befe852d61404b9373e1ada68e73..dfc4ab4c046826a480ff60b502d289ad50113fca 100644
--- a/package/gluon-config-mode-core/luasrc/lib/gluon/config-mode/model/gluon-config-mode/wizard.lua
+++ b/package/gluon-config-mode-core/luasrc/lib/gluon/config-mode/model/gluon-config-mode/wizard.lua
@@ -1,15 +1,6 @@
 local util = require "gluon.util"
 local uci = require("simple-uci").cursor()
 
-
-local wizard = {}
-for _, entry in ipairs(util.glob('/lib/gluon/config-mode/wizard/*')) do
-	local f = assert(loadfile(entry))
-	setfenv(f, getfenv())
-	local w = f()
-	table.insert(wizard, w)
-end
-
 local f = Form(translate("Welcome!"))
 f.submit = translate('Save & restart')
 f.reset = false
@@ -18,21 +9,10 @@ local s = f:section(Section)
 s.template = "wizard/welcome"
 s.package = "gluon-config-mode-core"
 
-local commit = {'gluon-setup-mode'}
-local run = {}
-
-for _, w in ipairs(wizard) do
-	for _, c in ipairs(w(f, uci) or {}) do
-		if type(c) == 'string' then
-			if not util.contains(commit, c) then
-				table.insert(commit, c)
-			end
-		elseif type(c) == 'function' then
-			table.insert(run, c)
-		else
-			error('invalid wizard module return')
-		end
-	end
+for _, entry in ipairs(util.glob('/lib/gluon/config-mode/wizard/*')) do
+	local section = assert(loadfile(entry))
+	setfenv(section, getfenv())
+	section()(f, uci)
 end
 
 function f:write()
@@ -40,13 +20,9 @@ function f:write()
 	local unistd = require 'posix.unistd'
 
 	uci:set("gluon-setup-mode", uci:get_first("gluon-setup-mode", "setup_mode"), "configured", true)
+	uci:save("gluon-setup-mode")
 
-	for _, c in ipairs(commit) do
-		uci:commit(c)
-	end
-	for _, r in ipairs(run) do
-		r()
-	end
+	os.execute('gluon-reconfigure')
 
 	f.template = "wizard/reboot"
 	f.package = "gluon-config-mode-core"
diff --git a/package/gluon-config-mode-domain-select/luasrc/lib/gluon/config-mode/wizard/0200-domain-select.lua b/package/gluon-config-mode-domain-select/luasrc/lib/gluon/config-mode/wizard/0200-domain-select.lua
index 14ba4c637280c69f82df298b701fbc8d1aceeb30..72c3529e8325c0d7e7921e458e4b204ba5236f18 100644
--- a/package/gluon-config-mode-domain-select/luasrc/lib/gluon/config-mode/wizard/0200-domain-select.lua
+++ b/package/gluon-config-mode-domain-select/luasrc/lib/gluon/config-mode/wizard/0200-domain-select.lua
@@ -49,20 +49,8 @@ return function(form, uci)
 		o:value(domain.domain_code, domain.domain_name)
 	end
 
-	local domain_changed = false
-
 	function o:write(data)
-		if data ~= selected_domain then
-			domain_changed = true
-			uci:set('gluon', 'core', 'domain', data)
-		end
-	end
-
-	local function reconfigure()
-		if domain_changed then
-			os.execute('gluon-reconfigure')
-		end
+		uci:set('gluon', 'core', 'domain', data)
+		uci:save('gluon')
 	end
-
-	return {'gluon', reconfigure}
 end
diff --git a/package/gluon-config-mode-geo-location/luasrc/lib/gluon/config-mode/wizard/0400-geo-location.lua b/package/gluon-config-mode-geo-location/luasrc/lib/gluon/config-mode/wizard/0400-geo-location.lua
index 04ebf63aa37c9db66110e9613c012ffc5f29c2d7..5287e4fb84261450840ab11a4ac3883f87e0e978 100644
--- a/package/gluon-config-mode-geo-location/luasrc/lib/gluon/config-mode/wizard/0400-geo-location.lua
+++ b/package/gluon-config-mode-geo-location/luasrc/lib/gluon/config-mode/wizard/0400-geo-location.lua
@@ -100,5 +100,7 @@ return function(form, uci)
 		end
 	end
 
-	return {'gluon-node-info'}
+	function s:write()
+		uci:save("gluon-node-info")
+	end
 end
diff --git a/package/gluon-config-mode-hostname/luasrc/lib/gluon/config-mode/wizard/0100-hostname.lua b/package/gluon-config-mode-hostname/luasrc/lib/gluon/config-mode/wizard/0100-hostname.lua
index 1c9a0af37186deb0a3f8d027c11a279194d0370a..5f31dd8fc69a23702a0ea0bef0b4fc7c6b55eb04 100644
--- a/package/gluon-config-mode-hostname/luasrc/lib/gluon/config-mode/wizard/0100-hostname.lua
+++ b/package/gluon-config-mode-hostname/luasrc/lib/gluon/config-mode/wizard/0100-hostname.lua
@@ -30,7 +30,6 @@ return function(form, uci)
 
 	function o:write(data)
 		pretty_hostname.set(uci, data or default_hostname)
+		uci:save('system')
 	end
-
-	return {'system'}
 end
diff --git a/package/gluon-config-mode-mesh-vpn/luasrc/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua b/package/gluon-config-mode-mesh-vpn/luasrc/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
index 84482bb899a07623e3e38f3d346e6f7b1c4bca36..70a1307b522017e6ce5af01f1498c8efb11fb292 100644
--- a/package/gluon-config-mode-mesh-vpn/luasrc/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
+++ b/package/gluon-config-mode-mesh-vpn/luasrc/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
@@ -58,11 +58,7 @@ return function(form, uci)
 		uci:set("gluon", "mesh_vpn", "limit_egress", data * 1000)
 	end
 
-	function s:handle()
-		Section.handle(s)
+	function s:write()
 		uci:save('gluon')
-		os.execute('exec /lib/gluon/mesh-vpn/update-config')
 	end
-
-	return {'gluon', 'fastd', 'tunneldigger', 'simple-tc'}
 end
diff --git a/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua b/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
index 6a653df85963126137106e5b5ebf0ea88e1cf469..2fbc0fba4fc22329cf2e67552344c5bc2bbfe117 100644
--- a/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
+++ b/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
@@ -44,10 +44,6 @@ return function(form, uci)
 				end
 				uci:save('wireless')
 			end
-
-			os.execute('/lib/gluon/upgrade/200-wireless')
 		end
 	end
-
-	return {'gluon', 'network', 'wireless'}
 end
diff --git a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/classes.lua b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/classes.lua
index 07c9ef5e971cae00129404e44201b45da611581d..2ec0d71fa648a76f8d289d81224f11c9d04e7a31 100644
--- a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/classes.lua
+++ b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/classes.lua
@@ -53,6 +53,7 @@ function Node:__init__(name, title, description)
 	self.name = name
 	self.index = nil
 	self.parent = nil
+	self.state = M.FORM_NODATA
 	self.package = 'gluon-web-model'
 end
 
@@ -73,17 +74,32 @@ function Node:id()
 end
 
 function Node:reset_node()
+	self.state = M.FORM_NODATA
 	for _, child in ipairs(self.children) do
 		child:reset_node()
 	end
 end
 
 function Node:parse(http)
+	self.state = M.FORM_VALID
 	for _, child in ipairs(self.children) do
 		child:parse(http)
 	end
 end
 
+function Node:propagate_state()
+	if self.state == M.FORM_NODATA then
+		return
+	end
+
+	for _, child in ipairs(self.children) do
+		child:propagate_state()
+		if child.state == M.FORM_INVALID then
+			self.state = M.FORM_INVALID
+		end
+	end
+end
+
 function Node:render(renderer, scope)
 	if self.template then
 		local env = setmetatable({
@@ -158,9 +174,16 @@ function Node:resolve_node_depends()
 	return true
 end
 
+-- will be overridden: write(value)
+function Node:write()
+end
+
 function Node:handle()
-	for _, node in ipairs(self.children) do
-		node:handle()
+	if self.state == M.FORM_VALID then
+		for _, node in ipairs(self.children) do
+			node:handle()
+		end
+		self:write(self.data)
 	end
 end
 
@@ -187,7 +210,6 @@ function AbstractValue:__init__(...)
 	self.template  = "model/valuewrapper"
 
 	self.error = false
-	self.state = M.FORM_NODATA
 end
 
 function AbstractValue:defaultvalue()
@@ -250,16 +272,6 @@ function AbstractValue:validate()
 
 end
 
-function AbstractValue:handle()
-	if self.state == M.FORM_VALID then
-		self:write(self.data)
-	end
-end
-
--- will be overridden: write(value)
-function AbstractValue:write()
-end
-
 
 local Value = class(AbstractValue)
 M.Value = Value
@@ -438,26 +450,7 @@ function Form:parse(http)
 
 	while self:resolve_depends() do end
 
-	for _, s in ipairs(self.children) do
-		for _, v in ipairs(s.children) do
-			if v.state == M.FORM_INVALID then
-				self.state = M.FORM_INVALID
-				return
-			end
-		end
-	end
-
-	self.state = M.FORM_VALID
-end
-
-function Form:handle()
-	if self.state == M.FORM_VALID then
-		Node.handle(self)
-		self:write()
-	end
-end
-
-function Form:write()
+	self:propagate_state()
 end
 
 function Form:section(t, ...)