diff --git a/Changes.md b/Changes.md
index 3a568d3..0bcdc64 100644
--- a/Changes.md
+++ b/Changes.md
@@ -3,6 +3,12 @@ WebPerl Changelog
 =================
 
 
+2019-08-03: v0.11-beta
+----------------------
+
+- Updated for Emscripten 1.38.31 / latest Fastcomp (1.38.40) and Perl v5.30.0
+
+
 2019-03-03: v0.09-beta
 ----------------------
 
diff --git a/build/build.pl b/build/build.pl
index cadafc5..42b8fd8 100755
--- a/build/build.pl
+++ b/build/build.pl
@@ -95,9 +95,11 @@ =head1 Author, Copyright, and License
 	# first, we need to take a guess which version of the patch to apply.
 	my $libraryjs = file($ENV{EMSCRIPTEN}, 'src', 'library.js')->slurp;
 	my $patchf;
-	if ( $libraryjs=~/\b\QERRNO_CODES.EAGAIN\E\b/ )
+	if ( $libraryjs=~/\b\Q___setErrNo(ERRNO_CODES.\E(EAGAIN|ENOTSUP)\b/ )
 		{ $patchf = 'emscripten_1.38.10_eagain.patch' }
-	elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ )
+	elsif ( $libraryjs=~/no shell available\s+setErrNo\Q({{{ cDefine('EAGAIN') }}})\E/ )
+		{ $patchf = 'emscripten_1.39.16_eagain.patch' }
+	elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) # note that this appears in 1.38.1* versions too
 		{ $patchf = 'emscripten_1.38.28_eagain.patch' }
 	else { die "Could not figure out which library.js patch to use" }
 	#TODO Later: we should probably verify the Emscripten version too, and in the future we may need different patches for different versions
@@ -161,6 +163,7 @@ =head1 Author, Copyright, and License
 		unless $tags=~/^\Q$C{PERLVER}\E$/m;
 	my $branches = git 'branch', '--list', {show_cmd=>$VERBOSE};
 	die "could not find branch '$C{PERL_BRANCH}', is this the right repository?"
+		. " (or the WebPerl author forgot to push tags to the emperl5 repo)"
 		unless $branches=~/^\*?\s*\b\Q$C{PERL_BRANCH}\E$/m;
 	say STDERR "# Found tag '$C{PERLVER}' and branch '$C{PERL_BRANCH}' in $C{PERLSRCDIR}";
 }
diff --git a/build/emperl_config.sh b/build/emperl_config.sh
index 6930493..52d86ad 100755
--- a/build/emperl_config.sh
+++ b/build/emperl_config.sh
@@ -27,7 +27,7 @@ export EMPERL_OUTPUTDIR="$BASEDIR/work/outputperl"
 
 # Don't edit the following options unless you know what you're doing!
 # Note to self: In build.pl, we take advantage of the fact that on Perls >=v5.10.0, "$^V" is the same as the tag name.
-export EMPERL_PERLVER="v5.28.1"
+export EMPERL_PERLVER="v5.30.0"
 export EMPERL_PREFIX="/opt/perl"
 # Note: strace shows this is how file_packager.py is called: ["/usr/bin/python", "/home/haukex/emsdk/emscripten/1.38.28/tools/file_packager.py", "emperl.data", "--from-emcc", "--export-name=Module", "--preload", "/home/haukex/code/webperl/work/outputperl/opt/perl@/opt/perl", "--no-heap-copy"]
 export EMPERL_PRELOAD_FILE="$EMPERL_OUTPUTDIR$EMPERL_PREFIX@$EMPERL_PREFIX"
diff --git a/build/emscripten_1.39.16_eagain.patch b/build/emscripten_1.39.16_eagain.patch
new file mode 100644
index 0000000..66ebfd4
--- /dev/null
+++ b/build/emscripten_1.39.16_eagain.patch
@@ -0,0 +1,19 @@
+--- library.js.orig     2020-05-18 17:14:18.682328912 +0200
++++ library.js  2020-05-18 17:14:48.366639562 +0200
+@@ -271,7 +271,7 @@
+     // pid_t fork(void);
+     // http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
+     // We don't support multiple processes.
+-    setErrNo({{{ cDefine('EAGAIN') }}});
++    setErrNo({{{ cDefine('ENOTSUP') }}});
+     return -1;
+   },
+   vfork: 'fork',
+@@ -696,7 +696,7 @@
+     // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
+     // Can't call external programs.
+     if (!command) return 0; // no shell available
+-    setErrNo({{{ cDefine('EAGAIN') }}});
++    setErrNo({{{ cDefine('ENOTSUP') }}});
+     return -1;
+   },
diff --git a/experiments/gui_basic/.gitignore b/experiments/gui_basic/.gitignore
new file mode 100644
index 0000000..b6bf89b
--- /dev/null
+++ b/experiments/gui_basic/.gitignore
@@ -0,0 +1,5 @@
+/database.db
+/web/webperl.js
+/web/emperl.*
+/gui_basic
+/gui_basic.exe
diff --git a/experiments/gui_basic/README.md b/experiments/gui_basic/README.md
new file mode 100644
index 0000000..08b3c51
--- /dev/null
+++ b/experiments/gui_basic/README.md
@@ -0,0 +1,50 @@
+
+WebPerl Basic GUI Example
+=========================
+
+This is a demo of a very basic GUI using WebPerl. It consists of a
+local web server, which includes code to access an SQLite database,
+and this web server also serves up WebPerl code to a browser, where
+the GUI is implemented as HTML with Perl.
+
+To get this to work, you will need to copy the `webperl.js` and the
+three `emperl.*` files from the main `web` directory to the `web`
+subdirectory in this project.
+
+Note that this should not be considered production-ready, as there
+are several key features missing, such as HTTPS or access control.
+
+Also, a limitation is that the server does not know when the browser
+window is closed, so it must be stopped manually.
+
+You can pack this application into a single executable using:
+
+	DOING_PAR_PACKER=1 pp -o gui_basic -z 9 -x -a gui_basic_app.psgi -a web gui_basic.pl
+
+Note: I'm not yet sure why, but sometimes this fails with errors such
+as *"error extracting info from -c/-x file"*, in that case just try
+the above command again.
+
+
+Author, Copyright, and License
+==============================
+
+**WebPerl - <http://webperl.zero-g.net>**
+
+Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
+at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
+Berlin, Germany, <http://www.igb-berlin.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself: either the GNU General Public
+License as published by the Free Software Foundation (either version 1,
+or, at your option, any later version), or the "Artistic License" which
+comes with Perl 5.
+
+This program is distributed in the hope that it will be useful, but
+**WITHOUT ANY WARRANTY**; without even the implied warranty of
+**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
+See the licenses for details.
+
+You should have received a copy of the licenses along with this program.
+If not, see <http://perldoc.perl.org/index-licence.html>.
diff --git a/experiments/gui_basic/gui_basic.pl b/experiments/gui_basic/gui_basic.pl
new file mode 100755
index 0000000..b23006d
--- /dev/null
+++ b/experiments/gui_basic/gui_basic.pl
@@ -0,0 +1,50 @@
+#!/usr/bin/env perl
+use warnings;
+use 5.018;
+use FindBin;
+use File::Spec::Functions qw/catdir/;
+use Plack::Runner ();
+use Starman ();
+use Browser::Open qw/open_browser/;
+
+# This just serves up gui_basic_app.psgi in the Starman web server.
+# You can also say "plackup gui_basic_app.psgi" instead.
+
+BEGIN {
+	my $dir = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
+	chdir $dir or die "chdir $dir: $!";
+}
+
+my $SERV_PORT = 5000;
+my $THE_APP = 'gui_basic_app.psgi';
+
+# AFAICT, both Plack::Runner->new(@args) and ->parse_options(@argv) set
+# options, and these options are shared between "Starman::Server"
+# (documented in "starman") and "Plack::Runner" (documented in "plackup").
+my @args = (
+	server => 'Starman', loader => 'Delayed', env => 'development',
+	version_cb => sub { print "Starman $Starman::VERSION\n" } );
+my @argv = ( '--listen', "localhost:$SERV_PORT", $THE_APP );
+my $runner = Plack::Runner->new(@args);
+$runner->parse_options(@argv);
+$runner->set_options(argv => \@argv);
+die "loader shouldn't be Restarter" if $runner->{loader} eq 'Restarter';
+
+if ($ENV{DOING_PAR_PACKER}) {
+	require Plack::Util;
+	Plack::Util::load_psgi($THE_APP); # for dependency resolution
+	# arrange to have the server shut down in a few moments
+	my $procpid = $$;
+	my $pid = fork();
+	if (!defined $pid) { die "fork failed" }
+	elsif ($pid==0) { sleep 5; kill 'INT', $procpid; exit; } # child
+	print "====> Please wait a few seconds...\n";
+}
+else {
+	# There's a small chance here that the browser could open before the server
+	# starts up. In that case, a reload of the browser window is needed.
+	print "Attempting to open in browser: http://localhost:$SERV_PORT/\n";
+	open_browser("http://localhost:$SERV_PORT/");
+}
+
+$runner->run;
diff --git a/experiments/gui_basic/gui_basic_app.psgi b/experiments/gui_basic/gui_basic_app.psgi
new file mode 100644
index 0000000..d75a32a
--- /dev/null
+++ b/experiments/gui_basic/gui_basic_app.psgi
@@ -0,0 +1,67 @@
+#!/usr/bin/env perl
+use warnings;
+use 5.018;
+use Plack::MIME;
+use Plack::Builder qw/builder enable mount/;
+use Plack::Request ();
+use Plack::Response (); # declare compile-time dependency
+use Cpanel::JSON::XS qw/decode_json encode_json/;
+use DBI ();
+use DBD::SQLite (); # declare compile-time dependency
+use HTML::Tiny ();
+
+# This is the server-side code.
+
+# note we rely on gui_basic.pl to set the working directory correctly
+my $SERV_ROOT = 'web';
+my $DB_FILE = 'database.db';
+
+my $dbh = DBI->connect("DBI:SQLite:dbname=$DB_FILE",
+	undef, undef, { RaiseError=>1, AutoCommit=>1 });
+
+$dbh->do(q{ CREATE TABLE IF NOT EXISTS FooBar (
+	foo VARCHAR(255), bar VARCHAR(255) ) });
+
+# This sends HTML to the browser, but we could also send JSON
+# and build the HTML table dynamically in the browser.
+my $app_select = sub {
+	state $html = HTML::Tiny->new;
+	state $sth_select = $dbh->prepare(q{ SELECT rowid,foo,bar FROM FooBar });
+	$sth_select->execute;
+	my $data = $sth_select->fetchall_arrayref;
+	my $out = $html->table(
+		[ \'tr',
+			[ \'th', 'rowid', 'foo', 'bar' ],
+			map { [ \'td', @$_ ] } @$data
+		] );
+	return [ 200, [ "Content-Type"=>"text/html" ], [ $out ] ];
+};
+
+# This is an example of one way to communicate with JSON.
+my $app_insert = sub {
+	my $req = Plack::Request->new(shift);
+	state $sth_insert = $dbh->prepare(q{ INSERT INTO FooBar (foo,bar) VALUES (?,?) });
+	my $rv = eval { # catch errors and return as 500 Server Error
+		my $content = decode_json( $req->content );
+		$sth_insert->execute($content->{foo}, $content->{bar});
+		{ ok=>1 }; # return value from eval, sent to client as JSON
+	}; my $e = $@||'unknown error';
+	my $res = $req->new_response($rv ? 200 : 500);
+	$res->content_type($rv ? 'application/json' : 'text/plain');
+	$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
+	return $res->finalize;
+};
+
+Plack::MIME->add_type(".js"   => "application/javascript");
+Plack::MIME->add_type(".data" => "application/octet-stream");
+Plack::MIME->add_type(".mem"  => "application/octet-stream");
+Plack::MIME->add_type(".wasm" => "application/wasm");
+
+builder {
+	enable 'SimpleLogger';
+	enable 'Static',
+		path => sub { s#\A/\z#/index.html#; /\.(?:html?|js|css|data|mem|wasm|pl)\z/i },
+		root => $SERV_ROOT;
+	mount '/select' => $app_select;
+	mount '/insert' => $app_insert;
+}
diff --git a/experiments/gui_basic/web/index.html b/experiments/gui_basic/web/index.html
new file mode 100644
index 0000000..89acfa1
--- /dev/null
+++ b/experiments/gui_basic/web/index.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en-us">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<title>WebPerl GUI Demo</title>
+	<script src="webperl.js"></script>
+	<script type="text/perl" src="web.pl"></script>
+</head>
+<body style="font-family:sans-serif;">
+<h1>WebPerl GUI Demo</h1>
+
+<div id="datatable"><i>No data loaded yet...</i></div>
+<div><button id="reload_data">Reload Data</button></div>
+
+<div style="margin-top:1em">
+	<div>
+		<label for="input_foo">foo</label>
+		<input type="text" id="input_foo">
+	</div>
+	<div>
+		<label for="input_bar">bar</label>
+		<input type="text" id="input_bar">
+	</div>
+	<div>
+		<button id="do_insert">Insert Data</button>
+	</div>
+</div>
+
+<p>Powered by <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a> (beta)</p>
+
+</body>
+</html>
diff --git a/experiments/gui_basic/web/web.pl b/experiments/gui_basic/web/web.pl
new file mode 100644
index 0000000..d4fb476
--- /dev/null
+++ b/experiments/gui_basic/web/web.pl
@@ -0,0 +1,69 @@
+#!perl
+use warnings;
+use 5.028;
+use WebPerl qw/js js_new sub1 encode_json/;
+
+# This is the code that WebPerl runs in the browser. It is loaded by index.html.
+
+sub do_xhr {
+	my %args = @_;
+	die "must specify a url" unless $args{url};
+	$args{fail} ||= sub { js('window')->alert(shift) };
+	my $xhr = js_new('XMLHttpRequest');
+	$xhr->addEventListener("error", sub1 {
+			$args{fail}->("XHR Error on $args{url}: ".(shift->{textContent}||"unknown"));
+			return;
+		});
+	$xhr->addEventListener("load", sub1 {
+			if ($xhr->{status}==200) {
+				$args{done}->($xhr->{response}) if $args{done};
+			}
+			else {
+				$args{fail}->("XHR Error on $args{url}: ".$xhr->{status}." ".$xhr->{statusText});
+			}
+			return;
+		});
+	$xhr->addEventListener("loadend", sub1 {
+			$args{always}->() if $args{always};
+			return;
+		});
+	# when given data, default to POST (JSON), otherwise GET
+	if ($args{data}) {
+		$xhr->open($args{method}||'POST', $args{url});
+		$xhr->setRequestHeader('Content-Type', 'application/json');
+		$xhr->send(encode_json($args{data}));
+	}
+	else {
+		$xhr->open($args{method}||'GET', $args{url});
+		$xhr->send();
+	}
+	return;
+}
+
+my $document = js('document');
+
+my $btn_reload = $document->getElementById('reload_data');
+sub do_reload {
+	state $dtbl = $document->getElementById('datatable');
+	$btn_reload->{disabled} = 1;
+	do_xhr(url => 'select',
+		done   => sub { $dtbl->{innerHTML} = shift; },
+		always => sub { $btn_reload->{disabled} = 0; } );
+	return;
+}
+$btn_reload->addEventListener("click", \&do_reload);
+
+my $btn_insert = $document->getElementById('do_insert');
+sub do_insert {
+	state $txt_foo = $document->getElementById('input_foo');
+	state $txt_bar = $document->getElementById('input_bar');
+	$btn_insert->{disabled} = 1;
+	do_xhr(url => 'insert',
+		data => { foo=>$txt_foo->{value}, bar=>$txt_bar->{value} },
+		always => sub { $btn_insert->{disabled} = 0; do_reload; } );
+	return;
+}
+$btn_insert->addEventListener("click", \&do_insert);
+
+do_reload; # initial load
+
diff --git a/experiments/gui_sweet/.gitignore b/experiments/gui_sweet/.gitignore
new file mode 100644
index 0000000..4dd1fbf
--- /dev/null
+++ b/experiments/gui_sweet/.gitignore
@@ -0,0 +1,4 @@
+/public/webperl.js
+/public/emperl.*
+/gui_sweet
+/gui_sweet.exe
diff --git a/experiments/gui_sweet/README.md b/experiments/gui_sweet/README.md
new file mode 100644
index 0000000..04a43b1
--- /dev/null
+++ b/experiments/gui_sweet/README.md
@@ -0,0 +1,44 @@
+
+WebPerl Advanced GUI Example
+============================
+
+Similar to the "WebPerl Basic GUI Example", this is a demo of a GUI
+using WebPerl, but using [Bootstrap](https://getbootstrap.com/)
+and [jQuery](https://jquery.com/) instead of plain JavaScript,
+and [Mojolicious](https://mojolicious.org/) instead of plain Plack.
+
+To get this to work, you will need to copy the `webperl.js` and the
+three `emperl.*` files from the main `web` directory to the `public`
+subdirectory in this project.
+
+Also, a limitation is that the server does not know when the browser
+window is closed, so it must be stopped manually.
+
+You can pack this application into a single executable using `do_pp.pl`.
+Note: I'm not yet sure why, but sometimes this fails with errors such
+as *"error extracting info from -c/-x file"*, in that case just try
+the command again.
+
+
+Author, Copyright, and License
+==============================
+
+**WebPerl - <http://webperl.zero-g.net>**
+
+Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
+at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
+Berlin, Germany, <http://www.igb-berlin.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself: either the GNU General Public
+License as published by the Free Software Foundation (either version 1,
+or, at your option, any later version), or the "Artistic License" which
+comes with Perl 5.
+
+This program is distributed in the hope that it will be useful, but
+**WITHOUT ANY WARRANTY**; without even the implied warranty of
+**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
+See the licenses for details.
+
+You should have received a copy of the licenses along with this program.
+If not, see <http://perldoc.perl.org/index-licence.html>.
diff --git a/experiments/gui_sweet/do_pp.pl b/experiments/gui_sweet/do_pp.pl
new file mode 100755
index 0000000..18b5a68
--- /dev/null
+++ b/experiments/gui_sweet/do_pp.pl
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw/fileparse/;
+use File::Spec::Functions qw/catfile/;
+use File::Temp qw/tempfile/;
+
+# this attempts to locate Mojo's default server.crt/server.key files
+chomp( my $dir = `perldoc -l Mojo::IOLoop::Server` );
+die "perldoc -l failed, \$?=$?" if $? || !-e $dir;
+(undef, $dir) = fileparse($dir);
+
+# set up a file for pp's -A switch
+my ($tfh, $tfn) = tempfile(UNLINK=>1);
+print {$tfh} catfile($dir,'resources','server.crt'),";server.crt\n";
+print {$tfh} catfile($dir,'resources','server.key'),";server.key\n";
+close $tfh;
+
+my @args = (qw/ -a public -a templates -A /, $tfn);
+
+local $ENV{DOING_PAR_PACKER}=1;
+system(qw/ pp -o gui_sweet -z 9 -x /,@args,'gui_sweet.pl')==0
+	or die "pp failed, \$?=$?";
diff --git a/experiments/gui_sweet/gui_sweet.pl b/experiments/gui_sweet/gui_sweet.pl
new file mode 100755
index 0000000..fdb6169
--- /dev/null
+++ b/experiments/gui_sweet/gui_sweet.pl
@@ -0,0 +1,77 @@
+#!/usr/bin/env perl
+use Mojolicious::Lite;
+use Mojo::Util qw/md5_sum/;
+use FindBin;
+use File::Spec::Functions qw/catdir/;
+use Browser::Open qw/open_browser/;
+
+# This is the server-side code.
+
+my $SERV_PORT = 3000;
+
+my ($SSLCERTS,$HOMEDIR);
+BEGIN {
+	$HOMEDIR = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
+	chdir $HOMEDIR or die "chdir $HOMEDIR: $!";
+	# do_pp.pl pulls the default Mojo SSL certs into the archive for us
+	$SSLCERTS = $ENV{PAR_TEMP} ? '?cert=./server.crt&key=./server.key' : '';
+}
+
+app->static->paths([catdir($HOMEDIR,'public')]);
+app->renderer->paths([catdir($HOMEDIR,'templates')]);
+app->secrets(['Hello, Perl World!']);
+app->types->type(js   => "application/javascript");
+app->types->type(data => "application/octet-stream");
+app->types->type(mem  => "application/octet-stream");
+app->types->type(wasm => "application/wasm");
+
+# Authentication and browser-launching stuff (optional)
+my $TOKEN = md5_sum(rand(1e15).time);
+hook before_server_start => sub {
+	my ($server, $app) = @_;
+	my @urls = map {Mojo::URL->new($_)->query(token=>$TOKEN)} @{$server->listen};
+	my $url = shift @urls or die "No urls?";
+	if ($ENV{DOING_PAR_PACKER}) {
+		# arrange to have the server shut down in a few moments
+		my $procpid = $$;
+		my $pid = fork();
+		if (!defined $pid) { die "fork failed" }
+		elsif ($pid==0) { sleep 5; kill 'USR1', $procpid; exit; } # child
+		print "====> Please wait a few seconds...\n";
+		$SIG{USR1} = sub { $server->stop; exit };
+	}
+	else {
+		print "Attempting to open in browser: $url\n";
+		open_browser($url);
+	}
+};
+under sub {
+	my $c = shift;
+	return 1 if ($c->param('token')//'') eq $TOKEN;
+	$c->render(text => 'Bad token!', status => 403);
+	return undef;
+};
+
+get '/' => sub { shift->render } => 'index';
+
+post '/example' => sub {
+	my $c = shift;
+	my $data = $c->req->json;
+	# can do anything here, this is just an example
+	$data->{string} = reverse $data->{string};
+	$c->render(json => $data);
+};
+
+app->start('daemon', '-l', "https://localhost:$SERV_PORT$SSLCERTS");
+
+__DATA__
+
+@@ index.html.ep
+% layout 'main', title => 'WebPerl GUI Demo';
+<main role="main" class="container">
+	<div>
+		<h1>WebPerl Advanced GUI Demo</h1>
+		<p class="lead">Hello, Perl World!</p>
+		<div id="buttons"></div>
+	</div>
+</main>
diff --git a/experiments/gui_sweet/public/web.pl b/experiments/gui_sweet/public/web.pl
new file mode 100644
index 0000000..7a6239a
--- /dev/null
+++ b/experiments/gui_sweet/public/web.pl
@@ -0,0 +1,44 @@
+#!perl
+use warnings;
+use 5.028;
+use WebPerl qw/js sub1 encode_json/;
+
+# This is the code that WebPerl runs in the browser. It is loaded by index.html.
+
+my $window = js('window');
+my $document = js('document');
+my $jq = js('jQuery');
+
+sub do_ajax {
+	my %args = @_;
+	die "must specify a url" unless $args{url};
+	$args{fail} ||= sub { $window->alert(shift) };
+	$jq->ajax( $args{url}, {
+		$args{data} # when given data, default to POST (JSON), otherwise GET
+			? ( method=>$args{method}||'POST',
+				data=>encode_json($args{data}) )
+			: ( method=>$args{method}||'GET' ),
+	} )->done( sub1 {
+		$args{done}->(shift) if $args{done};
+	} )->fail( sub1 {
+		my ($jqXHR, $textStatus, $errorThrown) = @_;
+		$args{fail}->("AJAX Failed! ($errorThrown)");
+	} )->always( sub1 {
+		$args{always}->() if $args{always};
+	} );
+	return;
+}
+
+# slightly hacky way to get the access token, but it works fine
+my ($token) = $window->{location}{search}=~/\btoken=([a-fA-F0-9]+)\b/;
+
+my $btn = $jq->('<button>', { text=>"Click me!" } );
+$btn->click(sub {
+	$btn->prop('disabled',1);
+	do_ajax( url=>"/example?token=$token",
+		data => { string=>"rekcaH lreP rehtonA tsuJ" },
+		done => sub { $window->alert("The server says: ".shift->{string}) },
+		always => sub { $btn->prop('disabled',0); } );
+} );
+$btn->appendTo( $jq->('#buttons') );
+
diff --git a/experiments/gui_sweet/templates/layouts/main.html.ep b/experiments/gui_sweet/templates/layouts/main.html.ep
new file mode 100644
index 0000000..b6219be
--- /dev/null
+++ b/experiments/gui_sweet/templates/layouts/main.html.ep
@@ -0,0 +1,50 @@
+<!doctype html>
+<html lang="en-us">
+<head>
+	<meta charset="utf-8">
+	<title><%= title %></title>
+	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
+	<style>
+		body { padding-top: 5rem; }
+	</style>
+</head>
+<body>
+	<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
+		<a class="navbar-brand" href="#"><%= title %></a>
+		<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
+			<span class="navbar-toggler-icon"></span>
+		</button>
+		<div class="collapse navbar-collapse" id="navbarCollapse">
+			<ul class="navbar-nav mr-auto">
+				<li class="nav-item active">
+					<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
+				</li>
+				<li class="nav-item">
+					<a class="nav-link" href="#">Link</a>
+				</li>
+				<li class="nav-item">
+					<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
+				</li>
+				<li class="nav-item dropdown">
+					<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
+					<div class="dropdown-menu" aria-labelledby="dropdown01">
+						<a class="dropdown-item" href="#">Action</a>
+						<a class="dropdown-item" href="#">Another action</a>
+						<a class="dropdown-item" href="#">Something else here</a>
+					</div>
+				</li>
+			</ul>
+		</div>
+	</nav>
+	
+	<%= content %>
+	
+	<!-- Bootstrap wants its script tags at the end of the body element, so we'll put everything here: -->
+	<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
+	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
+	<script src="webperl.js"></script>
+	<script type="text/perl" src="web.pl"></script>
+</body>
+</html>
diff --git a/experiments/use_http.html b/experiments/use_http.html
new file mode 100644
index 0000000..f891234
--- /dev/null
+++ b/experiments/use_http.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<html lang="en-us">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>WebPerl Sync HTTP Demo</title>
+
+<!--
+This is a demo of dynamically loading modules via synchronous
+XMLHttpRequests.
+
+WARNING: Please note that https://xhr.spec.whatwg.org/ says:
+"Synchronous XMLHttpRequest outside of workers is in the process of
+being removed from the web platform as it has detrimental effects to
+the end user’s experience. (This is a long process that takes many
+years.)"
+
+The method was first described by LanX at
+https://www.perlmonks.org/?node_id=1225490
+Thank you! :-)
+-->
+
+<script src="webperl.js"></script>
+<script type="text/perl">
+use warnings;
+use 5.028;
+use WebPerl qw/js js_new/;
+
+BEGIN {
+	push @INC, sub {
+		my (undef,$file) = @_;
+		
+		# sadly, MetaCPAN doesn't send CORS headers (yet)
+		#my $url = 'https://fastapi.metacpan.org/v1/source/'
+		#	. ( $file =~ s/\//::/r =~ s/\.pm$//ir );
+		
+		# this requires one to copy Dump.pm into web/Data/:
+		my $url = $file;
+		
+		my $xhr = js_new('XMLHttpRequest');
+		$xhr->open('GET', $url, 0);
+		$xhr->send();
+		if ($xhr->{status}==200)
+			{ return \$xhr->{responseText} }
+		else { return }
+	};
+}
+
+use Data::Dump 'pp';
+js('window')->alert(pp({Hello=>"World!"}));
+
+</script>
+
+</head>
+<body>
+
+<p>Output: see JS console</p>
+
+</body>
+</html>
diff --git a/web/mini_ide/emscr_ide.js b/web/mini_ide/emscr_ide.js
index 7d60fd6..6b0249d 100644
--- a/web/mini_ide/emscr_ide.js
+++ b/web/mini_ide/emscr_ide.js
@@ -123,6 +123,7 @@ function make_emscr_ide (textarea, callbacks) {
 		document.body.appendChild(link);
 		link.click();
 		document.body.removeChild(link);
+		//TODO: do we need to do URL.revokeObjectURL(link.href); ?
 	});
 	
 	file_open.click(function () {
diff --git a/web/webperl.js b/web/webperl.js
index c427043..3ed51fc 100644
--- a/web/webperl.js
+++ b/web/webperl.js
@@ -28,7 +28,7 @@ var Perl = {
 	trace: false,         // user may enable this
 	endAfterMain: false,  // user may enable this (before Perl.init)
 	noMountIdbfs: false,  // user may enable this (before Perl.start)
-	WebPerlVersion: 'v0.09-beta',  // user may read (only!) this
+	WebPerlVersion: 'v0.11-beta',  // user may read (only!) this
 	state: "Uninitialized",  // user may read (only!) this
 	exitStatus: undefined,   // user may read (only!) this
 	Util: {},